blob: ef8e29acf12ad689f0bf2ddf1053e2a3b1d1dc24 [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
Jan Tattermusch320bd612015-09-15 12:44:35 -070036import jobset
Jan Tattermusch210a0ea2015-10-02 15:05:36 -070037import multiprocessing
Jan Tattermusch8266c672015-09-17 09:18:03 -070038import os
Adele Zhoua30f8292015-11-02 13:15:46 -080039import report_utils
Jan Tattermusch8266c672015-09-17 09:18:03 -070040import 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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070064 def client_cmd(self, args):
65 return ['bins/opt/interop_client'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070070 def server_cmd(self, args):
71 return ['bins/opt/interop_server', '--use_tls=true'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070090 def client_cmd(self, args):
91 return ['mono', 'Grpc.IntegrationTesting.Client.exe'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -070096 def server_cmd(self, args):
97 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700116 def client_cmd(self, args):
117 return ['./run-test-client.sh'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700122 def server_cmd(self, args):
123 return ['./run-test-server.sh', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700124
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700143 def client_cmd(self, args):
144 return ['go', 'run', 'client.go'] + args
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
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700149 def server_cmd(self, args):
150 return ['go', 'run', 'server.go', '--use_tls=true'] + args
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700151
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
Carl Mastrangelode449102015-10-28 11:05:49 -0700162class Http2Client:
163 """Represents the HTTP/2 Interop Test
164
165 This pretends to be a language in order to be built and run, but really it
166 isn't.
167 """
168 def __init__(self):
169 self.client_cwd = None
170 self.safename = str(self)
171
172 def client_args(self):
173 return ['tools/http2_interop/http2_interop.test']
174
175 def cloud_to_prod_env(self):
176 return {}
177
178 def global_env(self):
179 return {}
180
181 def unimplemented_test_cases(self):
182 return _TEST_CASES
183
184 def __str__(self):
185 return 'http2'
186
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700187class NodeLanguage:
188
189 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700190 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700191 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700192 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700193
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700194 def client_cmd(self, args):
195 return ['node', 'src/node/interop/interop_client.js'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700196
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700197 def cloud_to_prod_env(self):
198 return _SSL_CERT_ENV
199
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700200 def server_cmd(self, args):
201 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700202
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700203 def global_env(self):
204 return {}
205
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700206 def unimplemented_test_cases(self):
207 return []
208
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700209 def __str__(self):
210 return 'node'
211
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700212
213class PHPLanguage:
214
215 def __init__(self):
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700216 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700217 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700218
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700219 def client_cmd(self, args):
220 return ['src/php/bin/interop_client.sh'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700221
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700222 def cloud_to_prod_env(self):
223 return _SSL_CERT_ENV
224
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700225 def global_env(self):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700226 return {}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700227
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700228 def unimplemented_test_cases(self):
229 return []
230
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700231 def __str__(self):
232 return 'php'
233
234
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700235class RubyLanguage:
236
237 def __init__(self):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700238 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700239 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700240 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700241
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700242 def client_cmd(self, args):
243 return ['ruby', 'src/ruby/bin/interop/interop_client.rb'] + args
Jan Tattermusch8266c672015-09-17 09:18:03 -0700244
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700245 def cloud_to_prod_env(self):
246 return _SSL_CERT_ENV
247
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700248 def server_cmd(self, args):
249 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true'] + args
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700250
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700251 def global_env(self):
252 return {}
253
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700254 def unimplemented_test_cases(self):
255 return []
256
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700257 def __str__(self):
258 return 'ruby'
259
260
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700261class PythonLanguage:
262
263 def __init__(self):
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700264 self.client_cwd = None
265 self.server_cwd = None
266 self.safename = str(self)
267
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700268 def client_cmd(self, args):
269 return [
270 'python2.7_virtual_environment/bin/python',
271 'src/python/grpcio/setup.py',
272 'run_interop',
273 '--client',
274 '--args=\'{}\''.format(' '.join(args))
275 ]
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700276
277 def cloud_to_prod_env(self):
278 return _SSL_CERT_ENV
279
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700280 def server_cmd(self, args):
281 return [
282 'python2.7_virtual_environment/bin/python',
283 'src/python/grpcio/setup.py',
284 'run_interop',
285 '--server',
286 '--args=\'{}\''.format(' '.join(args) + ' --use_tls=true')
287 ]
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700288
289 def global_env(self):
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700290 return {'LD_LIBRARY_PATH': '{}/libs/opt'.format(DOCKER_WORKDIR_ROOT)}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700291
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700292 def unimplemented_test_cases(self):
293 return ['jwt_token_creds', 'per_rpc_creds']
294
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700295 def __str__(self):
296 return 'python'
297
298
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700299_LANGUAGES = {
300 'c++' : CXXLanguage(),
301 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700302 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700303 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700304 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700305 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700306 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700307 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700308}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700309
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700310# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700311_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700312
Jan Tattermusch320bd612015-09-15 12:44:35 -0700313_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700314 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700315 'cancel_after_begin', 'cancel_after_first_response',
316 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700317
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700318_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
319 'oauth2_auth_token', 'per_rpc_creds']
320
Carl Mastrangelode449102015-10-28 11:05:49 -0700321_HTTP2_TEST_CASES = ["tls"]
Jan Tattermusch8266c672015-09-17 09:18:03 -0700322
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700323DOCKER_WORKDIR_ROOT = '/var/local/git/grpc'
324
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700325def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
326 """Wraps given cmdline array to create 'docker run' cmdline from it."""
327 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
328
329 # turn environ into -e docker args
330 if environ:
331 for k,v in environ.iteritems():
332 docker_cmdline += ['-e', '%s=%s' % (k,v)]
333
334 # set working directory
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700335 workdir = DOCKER_WORKDIR_ROOT
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700336 if cwd:
337 workdir = os.path.join(workdir, cwd)
338 docker_cmdline += ['-w', workdir]
339
340 docker_cmdline += docker_args + [image] + cmdline
341 return docker_cmdline
342
343
344def bash_login_cmdline(cmdline):
345 """Creates bash -l -c cmdline from args list."""
346 # Use login shell:
347 # * rvm and nvm require it
348 # * makes error messages clearer if executables are missing
349 return ['bash', '-l', '-c', ' '.join(cmdline)]
350
351
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700352def add_auth_options(language, test_case, cmdline, env):
353 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
354
355 language = str(language)
356 cmdline = list(cmdline)
357 env = env.copy()
358
359 # TODO(jtattermusch): this file path only works inside docker
360 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
361 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
362 key_file_arg = '--service_account_key_file=%s' % key_filepath
363 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
364
365 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700366 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700367 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
368 else:
369 cmdline += [key_file_arg]
370
371 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
372 cmdline += [oauth_scope_arg]
373
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700374 if test_case == 'oauth2_auth_token' and language == 'c++':
375 # C++ oauth2 test uses GCE creds and thus needs to know the default account
376 cmdline += [default_account_arg]
377
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700378 if test_case == 'compute_engine_creds':
379 cmdline += [oauth_scope_arg, default_account_arg]
380
381 return (cmdline, env)
382
383
Jan Tattermusche2686282015-10-08 16:27:07 -0700384def _job_kill_handler(job):
385 if job._spec.container_name:
386 dockerjob.docker_kill(job._spec.container_name)
Jan Tattermusch39e3cb32015-10-22 18:21:08 -0700387 # When the job times out and we decide to kill it,
388 # we need to wait a before restarting the job
389 # to prevent "container name already in use" error.
390 # TODO(jtattermusch): figure out a cleaner way to to this.
391 time.sleep(2)
Jan Tattermusche2686282015-10-08 16:27:07 -0700392
393
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700394def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700395 """Creates jobspec for cloud-to-prod interop test"""
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700396 cmdline = language.client_cmd([
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700397 '--server_host_override=grpc-test.sandbox.google.com',
398 '--server_host=grpc-test.sandbox.google.com',
399 '--server_port=443',
400 '--use_tls=true',
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700401 '--test_case=%s' % test_case])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700402 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700403 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700404 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700405 if auth:
406 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
407 cmdline = bash_login_cmdline(cmdline)
408
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700409 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700410 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700411 cmdline = docker_run_cmdline(cmdline,
412 image=docker_image,
413 cwd=cwd,
414 environ=environ,
415 docker_args=['--net=host',
416 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700417 cwd = None
418 environ = None
419
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700420 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700421 test_job = jobset.JobSpec(
422 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700423 cwd=cwd,
424 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700425 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700426 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700427 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700428 timeout_retries=2 if args.allow_flakes else 0,
429 kill_handler=_job_kill_handler)
430 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700431 return test_job
432
Jan Tattermusch8266c672015-09-17 09:18:03 -0700433
434def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700435 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700436 """Creates jobspec for cloud-to-cloud interop test"""
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700437 cmdline = bash_login_cmdline(language.client_cmd([
438 '--server_host_override=foo.test.google.fr',
439 '--use_tls=true',
440 '--use_test_ca=true',
441 '--test_case=%s' % test_case,
442 '--server_host=%s' % server_host,
443 '--server_port=%s' % server_port]))
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700444 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700445 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700446 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700447 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700448 cmdline = docker_run_cmdline(cmdline,
449 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700450 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700451 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700452 docker_args=['--net=host',
453 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700454 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700455
Jan Tattermusch8266c672015-09-17 09:18:03 -0700456 test_job = jobset.JobSpec(
457 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700458 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700459 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700460 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700461 test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700462 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700463 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700464 timeout_retries=2 if args.allow_flakes else 0,
465 kill_handler=_job_kill_handler)
466 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700467 return test_job
468
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700469
470def server_jobspec(language, docker_image):
471 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700472 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Masood Malekghassemi7566c9a2015-10-21 20:29:23 -0700473 cmdline = bash_login_cmdline(
474 language.server_cmd(['--port=%s' % _DEFAULT_SERVER_PORT]))
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700475 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700476 docker_cmdline = docker_run_cmdline(cmdline,
477 image=docker_image,
478 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700479 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700480 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700481 '--name', container_name])
Carl Mastrangelode449102015-10-28 11:05:49 -0700482
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700483 server_job = jobset.JobSpec(
484 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700485 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700486 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700487 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700488 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700489 return server_job
490
491
492def build_interop_image_jobspec(language, tag=None):
493 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700494 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700495 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700496 env = {'INTEROP_IMAGE': tag,
497 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700498 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700499 env['TTY_FLAG'] = '-t'
500 # This env variable is used to get around the github rate limit
501 # error when running the PHP `composer install` command
502 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700503 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700504 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700505 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700506 build_job = jobset.JobSpec(
507 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700508 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700509 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700510 timeout_seconds=30*60)
511 build_job.tag = tag
512 return build_job
513
514
Jan Tattermusch320bd612015-09-15 12:44:35 -0700515argp = argparse.ArgumentParser(description='Run interop tests.')
516argp.add_argument('-l', '--language',
517 choices=['all'] + sorted(_LANGUAGES),
518 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700519 default=['all'],
520 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700521argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700522argp.add_argument('--cloud_to_prod',
523 default=False,
524 action='store_const',
525 const=True,
526 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700527argp.add_argument('--cloud_to_prod_auth',
528 default=False,
529 action='store_const',
530 const=True,
531 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700532argp.add_argument('-s', '--server',
533 choices=['all'] + sorted(_SERVERS),
534 action='append',
535 help='Run cloud_to_cloud servers in a separate docker ' +
536 'image. Servers can only be started automatically if ' +
537 '--use_docker option is enabled.',
538 default=[])
539argp.add_argument('--override_server',
540 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700541 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700542 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
543 default=[])
544argp.add_argument('-t', '--travis',
545 default=False,
546 action='store_const',
547 const=True)
548argp.add_argument('--use_docker',
549 default=False,
550 action='store_const',
551 const=True,
552 help='Run all the interop tests under docker. That provides ' +
553 'additional isolation and prevents the need to install ' +
554 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700555argp.add_argument('--allow_flakes',
556 default=False,
557 action='store_const',
558 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700559 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Carl Mastrangelode449102015-10-28 11:05:49 -0700560argp.add_argument('--http2_interop',
561 default=False,
562 action='store_const',
563 const=True,
564 help='Enable HTTP/2 interop tests')
565
Jan Tattermusch320bd612015-09-15 12:44:35 -0700566args = argp.parse_args()
567
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700568servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700569 if x == 'all' else [x]
570 for x in args.server))
571
572if args.use_docker:
573 if not args.travis:
574 print 'Seen --use_docker flag, will run interop tests under docker.'
575 print
576 print 'IMPORTANT: The changes you are testing need to be locally committed'
577 print 'because only the committed changes in the current branch will be'
578 print 'copied to the docker environment.'
579 time.sleep(5)
580
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700581if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700582 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700583 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700584
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700585languages = set(_LANGUAGES[l]
586 for l in itertools.chain.from_iterable(
587 _LANGUAGES.iterkeys() if x == 'all' else [x]
588 for x in args.language))
Carl Mastrangelode449102015-10-28 11:05:49 -0700589
590http2Interop = Http2Client() if args.http2_interop else None
Jan Tattermusch320bd612015-09-15 12:44:35 -0700591
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700592docker_images={}
593if args.use_docker:
594 # languages for which to build docker images
595 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
596 [s for s in servers]))
Carl Mastrangelode449102015-10-28 11:05:49 -0700597 if args.http2_interop:
598 languages_to_build.add(http2Interop)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700599
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700600 build_jobs = []
601 for l in languages_to_build:
602 job = build_interop_image_jobspec(l)
603 docker_images[str(l)] = job.tag
604 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700605
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700606 if build_jobs:
607 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700608 num_failures, _ = jobset.run(
609 build_jobs, newline_on_success=True, maxjobs=args.jobs)
610 if num_failures == 0:
611 jobset.message('SUCCESS', 'All docker images built successfully.',
612 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700613 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700614 jobset.message('FAILED', 'Failed to build interop docker images.',
615 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700616 for image in docker_images.itervalues():
617 dockerjob.remove_image(image, skip_nonexistent=True)
Carl Mastrangelo7a171402015-10-26 14:01:03 -0700618 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700619
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700620# Start interop servers.
621server_jobs={}
622server_addresses={}
623try:
624 for s in servers:
625 lang = str(s)
626 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
627 job = dockerjob.DockerJob(spec)
628 server_jobs[lang] = job
629 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700630
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700631
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700632 jobs = []
633 if args.cloud_to_prod:
634 for language in languages:
635 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700636 if not test_case in language.unimplemented_test_cases():
637 test_job = cloud_to_prod_jobspec(language, test_case,
638 docker_image=docker_images.get(str(language)))
639 jobs.append(test_job)
Carl Mastrangelode449102015-10-28 11:05:49 -0700640
Carl Mastrangelo3b2e1bd2015-11-06 14:31:55 -0800641 # TODO(carl-mastrangelo): Currently prod TLS terminators aren't spec compliant. Reenable
642 # this once a better solution is in place.
643 if args.http2_interop and False:
Carl Mastrangelode449102015-10-28 11:05:49 -0700644 for test_case in _HTTP2_TEST_CASES:
645 test_job = cloud_to_prod_jobspec(http2Interop, test_case,
646 docker_image=docker_images.get(str(http2Interop)))
647 jobs.append(test_job)
648
Jan Tattermusch320bd612015-09-15 12:44:35 -0700649
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700650 if args.cloud_to_prod_auth:
651 for language in languages:
652 for test_case in _AUTH_TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700653 if not test_case in language.unimplemented_test_cases():
654 test_job = cloud_to_prod_jobspec(language, test_case,
655 docker_image=docker_images.get(str(language)),
656 auth=True)
657 jobs.append(test_job)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700658
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700659 for server in args.override_server:
660 server_name = server[0]
661 (server_host, server_port) = server[1].split(':')
662 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700663
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700664 for server_name, server_address in server_addresses.iteritems():
665 (server_host, server_port) = server_address
666 for language in languages:
667 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700668 if not test_case in language.unimplemented_test_cases():
669 test_job = cloud_to_cloud_jobspec(language,
670 test_case,
671 server_name,
672 server_host,
673 server_port,
674 docker_image=docker_images.get(str(language)))
675 jobs.append(test_job)
Carl Mastrangelode449102015-10-28 11:05:49 -0700676
677 if args.http2_interop:
678 for test_case in _HTTP2_TEST_CASES:
Carl Mastrangelo3b2e1bd2015-11-06 14:31:55 -0800679 if server_name == "go":
680 # TODO(carl-mastrangelo): Reenable after https://github.com/grpc/grpc-go/issues/434
681 continue
Carl Mastrangelode449102015-10-28 11:05:49 -0700682 test_job = cloud_to_cloud_jobspec(http2Interop,
683 test_case,
684 server_name,
685 server_host,
686 server_port,
687 docker_image=docker_images.get(str(http2Interop)))
688 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700689
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700690 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700691 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700692 for image in docker_images.itervalues():
693 dockerjob.remove_image(image, skip_nonexistent=True)
694 sys.exit(1)
695
Adele Zhoue4c35612015-10-16 15:34:23 -0700696 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
Adele Zhou2271ab52015-10-28 13:59:14 -0700697 maxjobs=args.jobs)
Adele Zhoue4c35612015-10-16 15:34:23 -0700698 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700699 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700700 else:
701 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700702
Adele Zhoua30f8292015-11-02 13:15:46 -0800703 report_utils.render_xml_report(resultset, 'report.xml')
Adele Zhoue4c35612015-10-16 15:34:23 -0700704
Adele Zhoua30f8292015-11-02 13:15:46 -0800705 report_utils.render_html_report(
Adele Zhou2271ab52015-10-28 13:59:14 -0700706 set([str(l) for l in languages]), servers, _TEST_CASES, _AUTH_TEST_CASES,
Carl Mastrangelode449102015-10-28 11:05:49 -0700707 _HTTP2_TEST_CASES, resultset, num_failures,
708 args.cloud_to_prod_auth or args.cloud_to_prod, args.http2_interop)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700709
710finally:
711 # Check if servers are still running.
712 for server, job in server_jobs.iteritems():
713 if not job.is_running():
714 print 'Server "%s" has exited prematurely.' % server
715
716 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
717
718 for image in docker_images.itervalues():
719 print 'Removing docker image %s' % image
720 dockerjob.remove_image(image)