blob: 1686942b0ae9eb2ce467922fa75c7027ed60ed14 [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
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
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
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700194 def client_args(self):
195 return ['node', 'src/node/interop/interop_client.js']
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
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700200 def server_args(self):
201 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
202
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
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700219 def client_args(self):
220 return ['src/php/bin/interop_client.sh']
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
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700242 def client_args(self):
243 return ['ruby', 'src/ruby/bin/interop/interop_client.rb']
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
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700248 def server_args(self):
Jan Tattermusch73b3eea2015-10-15 17:52:06 -0700249 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true']
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
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700268 def client_args(self):
269 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700270
271 def cloud_to_prod_env(self):
272 return _SSL_CERT_ENV
273
274 def server_args(self):
Jan Tattermusch785efd42015-10-15 17:20:22 -0700275 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls=true']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700276
277 def global_env(self):
278 return {'LD_LIBRARY_PATH': 'libs/opt'}
279
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700280 def unimplemented_test_cases(self):
281 return ['jwt_token_creds', 'per_rpc_creds']
282
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700283 def __str__(self):
284 return 'python'
285
286
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700287_LANGUAGES = {
288 'c++' : CXXLanguage(),
289 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700290 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700291 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700292 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700293 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700294 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700295 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700296}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700297
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700298# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700299_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700300
Jan Tattermusch320bd612015-09-15 12:44:35 -0700301_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700302 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700303 'cancel_after_begin', 'cancel_after_first_response',
304 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700305
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700306_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
307 'oauth2_auth_token', 'per_rpc_creds']
308
Carl Mastrangelode449102015-10-28 11:05:49 -0700309_HTTP2_TEST_CASES = ["tls"]
Jan Tattermusch8266c672015-09-17 09:18:03 -0700310
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700311def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
312 """Wraps given cmdline array to create 'docker run' cmdline from it."""
313 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
314
315 # turn environ into -e docker args
316 if environ:
317 for k,v in environ.iteritems():
318 docker_cmdline += ['-e', '%s=%s' % (k,v)]
319
320 # set working directory
321 workdir = '/var/local/git/grpc'
322 if cwd:
323 workdir = os.path.join(workdir, cwd)
324 docker_cmdline += ['-w', workdir]
325
326 docker_cmdline += docker_args + [image] + cmdline
327 return docker_cmdline
328
329
330def bash_login_cmdline(cmdline):
331 """Creates bash -l -c cmdline from args list."""
332 # Use login shell:
333 # * rvm and nvm require it
334 # * makes error messages clearer if executables are missing
335 return ['bash', '-l', '-c', ' '.join(cmdline)]
336
337
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700338def add_auth_options(language, test_case, cmdline, env):
339 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
340
341 language = str(language)
342 cmdline = list(cmdline)
343 env = env.copy()
344
345 # TODO(jtattermusch): this file path only works inside docker
346 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
347 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
348 key_file_arg = '--service_account_key_file=%s' % key_filepath
349 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
350
351 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700352 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700353 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
354 else:
355 cmdline += [key_file_arg]
356
357 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
358 cmdline += [oauth_scope_arg]
359
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700360 if test_case == 'oauth2_auth_token' and language == 'c++':
361 # C++ oauth2 test uses GCE creds and thus needs to know the default account
362 cmdline += [default_account_arg]
363
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700364 if test_case == 'compute_engine_creds':
365 cmdline += [oauth_scope_arg, default_account_arg]
366
367 return (cmdline, env)
368
369
Jan Tattermusche2686282015-10-08 16:27:07 -0700370def _job_kill_handler(job):
371 if job._spec.container_name:
372 dockerjob.docker_kill(job._spec.container_name)
Jan Tattermusch39e3cb32015-10-22 18:21:08 -0700373 # When the job times out and we decide to kill it,
374 # we need to wait a before restarting the job
375 # to prevent "container name already in use" error.
376 # TODO(jtattermusch): figure out a cleaner way to to this.
377 time.sleep(2)
Jan Tattermusche2686282015-10-08 16:27:07 -0700378
379
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700380def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700381 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700382 cmdline = language.client_args() + [
383 '--server_host_override=grpc-test.sandbox.google.com',
384 '--server_host=grpc-test.sandbox.google.com',
385 '--server_port=443',
386 '--use_tls=true',
387 '--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700388 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700389 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700390 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700391 if auth:
392 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
393 cmdline = bash_login_cmdline(cmdline)
394
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700395 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700396 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700397 cmdline = docker_run_cmdline(cmdline,
398 image=docker_image,
399 cwd=cwd,
400 environ=environ,
401 docker_args=['--net=host',
402 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700403 cwd = None
404 environ = None
405
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700406 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700407 test_job = jobset.JobSpec(
408 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700409 cwd=cwd,
410 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700411 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700412 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700413 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700414 timeout_retries=2 if args.allow_flakes else 0,
415 kill_handler=_job_kill_handler)
416 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700417 return test_job
418
Jan Tattermusch8266c672015-09-17 09:18:03 -0700419
420def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700421 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700422 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700423 cmdline = bash_login_cmdline(language.client_args() +
424 ['--server_host_override=foo.test.google.fr',
425 '--use_tls=true',
426 '--use_test_ca=true',
427 '--test_case=%s' % test_case,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700428 '--server_host=%s' % server_host,
Jan Tattermuschc8b94412015-10-21 17:57:28 -0700429 '--server_port=%s' % server_port])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700430 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700431 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700432 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700433 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700434 cmdline = docker_run_cmdline(cmdline,
435 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700436 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700437 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700438 docker_args=['--net=host',
439 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700440 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700441
Jan Tattermusch8266c672015-09-17 09:18:03 -0700442 test_job = jobset.JobSpec(
443 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700444 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700445 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700446 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700447 test_case),
Jan Tattermusch29fd0052015-10-22 18:58:57 -0700448 timeout_seconds=90,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700449 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700450 timeout_retries=2 if args.allow_flakes else 0,
451 kill_handler=_job_kill_handler)
452 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700453 return test_job
454
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700455
456def server_jobspec(language, docker_image):
457 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700458 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700459 cmdline = bash_login_cmdline(language.server_args() +
460 ['--port=%s' % _DEFAULT_SERVER_PORT])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700461 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700462 docker_cmdline = docker_run_cmdline(cmdline,
463 image=docker_image,
464 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700465 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700466 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700467 '--name', container_name])
Carl Mastrangelode449102015-10-28 11:05:49 -0700468
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700469 server_job = jobset.JobSpec(
470 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700471 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700472 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700473 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700474 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700475 return server_job
476
477
478def build_interop_image_jobspec(language, tag=None):
479 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700480 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700481 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700482 env = {'INTEROP_IMAGE': tag,
483 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700484 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700485 env['TTY_FLAG'] = '-t'
486 # This env variable is used to get around the github rate limit
487 # error when running the PHP `composer install` command
488 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700489 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700490 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700491 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700492 build_job = jobset.JobSpec(
493 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700494 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700495 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700496 timeout_seconds=30*60)
497 build_job.tag = tag
498 return build_job
499
500
Jan Tattermusch320bd612015-09-15 12:44:35 -0700501argp = argparse.ArgumentParser(description='Run interop tests.')
502argp.add_argument('-l', '--language',
503 choices=['all'] + sorted(_LANGUAGES),
504 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700505 default=['all'],
506 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700507argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700508argp.add_argument('--cloud_to_prod',
509 default=False,
510 action='store_const',
511 const=True,
512 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700513argp.add_argument('--cloud_to_prod_auth',
514 default=False,
515 action='store_const',
516 const=True,
517 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700518argp.add_argument('-s', '--server',
519 choices=['all'] + sorted(_SERVERS),
520 action='append',
521 help='Run cloud_to_cloud servers in a separate docker ' +
522 'image. Servers can only be started automatically if ' +
523 '--use_docker option is enabled.',
524 default=[])
525argp.add_argument('--override_server',
526 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700527 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700528 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
529 default=[])
530argp.add_argument('-t', '--travis',
531 default=False,
532 action='store_const',
533 const=True)
534argp.add_argument('--use_docker',
535 default=False,
536 action='store_const',
537 const=True,
538 help='Run all the interop tests under docker. That provides ' +
539 'additional isolation and prevents the need to install ' +
540 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700541argp.add_argument('--allow_flakes',
542 default=False,
543 action='store_const',
544 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700545 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Carl Mastrangelode449102015-10-28 11:05:49 -0700546argp.add_argument('--http2_interop',
547 default=False,
548 action='store_const',
549 const=True,
550 help='Enable HTTP/2 interop tests')
551
Jan Tattermusch320bd612015-09-15 12:44:35 -0700552args = argp.parse_args()
553
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700554servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700555 if x == 'all' else [x]
556 for x in args.server))
557
558if args.use_docker:
559 if not args.travis:
560 print 'Seen --use_docker flag, will run interop tests under docker.'
561 print
562 print 'IMPORTANT: The changes you are testing need to be locally committed'
563 print 'because only the committed changes in the current branch will be'
564 print 'copied to the docker environment.'
565 time.sleep(5)
566
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700567if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700568 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700569 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700570
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700571languages = set(_LANGUAGES[l]
572 for l in itertools.chain.from_iterable(
573 _LANGUAGES.iterkeys() if x == 'all' else [x]
574 for x in args.language))
Carl Mastrangelode449102015-10-28 11:05:49 -0700575
576http2Interop = Http2Client() if args.http2_interop else None
Jan Tattermusch320bd612015-09-15 12:44:35 -0700577
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700578docker_images={}
579if args.use_docker:
580 # languages for which to build docker images
581 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
582 [s for s in servers]))
Carl Mastrangelode449102015-10-28 11:05:49 -0700583 if args.http2_interop:
584 languages_to_build.add(http2Interop)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700585
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700586 build_jobs = []
587 for l in languages_to_build:
588 job = build_interop_image_jobspec(l)
589 docker_images[str(l)] = job.tag
590 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700591
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700592 if build_jobs:
593 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700594 num_failures, _ = jobset.run(
595 build_jobs, newline_on_success=True, maxjobs=args.jobs)
596 if num_failures == 0:
597 jobset.message('SUCCESS', 'All docker images built successfully.',
598 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700599 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700600 jobset.message('FAILED', 'Failed to build interop docker images.',
601 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700602 for image in docker_images.itervalues():
603 dockerjob.remove_image(image, skip_nonexistent=True)
Carl Mastrangelo7a171402015-10-26 14:01:03 -0700604 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700605
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700606# Start interop servers.
607server_jobs={}
608server_addresses={}
609try:
610 for s in servers:
611 lang = str(s)
612 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
613 job = dockerjob.DockerJob(spec)
614 server_jobs[lang] = job
615 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700616
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700617
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700618 jobs = []
619 if args.cloud_to_prod:
620 for language in languages:
621 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700622 if not test_case in language.unimplemented_test_cases():
623 test_job = cloud_to_prod_jobspec(language, test_case,
624 docker_image=docker_images.get(str(language)))
625 jobs.append(test_job)
Carl Mastrangelode449102015-10-28 11:05:49 -0700626
627 if args.http2_interop:
628 for test_case in _HTTP2_TEST_CASES:
629 test_job = cloud_to_prod_jobspec(http2Interop, test_case,
630 docker_image=docker_images.get(str(http2Interop)))
631 jobs.append(test_job)
632
Jan Tattermusch320bd612015-09-15 12:44:35 -0700633
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700634 if args.cloud_to_prod_auth:
635 for language in languages:
636 for test_case in _AUTH_TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700637 if not test_case in language.unimplemented_test_cases():
638 test_job = cloud_to_prod_jobspec(language, test_case,
639 docker_image=docker_images.get(str(language)),
640 auth=True)
641 jobs.append(test_job)
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700642
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700643 for server in args.override_server:
644 server_name = server[0]
645 (server_host, server_port) = server[1].split(':')
646 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700647
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700648 for server_name, server_address in server_addresses.iteritems():
649 (server_host, server_port) = server_address
650 for language in languages:
651 for test_case in _TEST_CASES:
Jan Tattermusch289b7b92015-10-21 18:09:59 -0700652 if not test_case in language.unimplemented_test_cases():
653 test_job = cloud_to_cloud_jobspec(language,
654 test_case,
655 server_name,
656 server_host,
657 server_port,
658 docker_image=docker_images.get(str(language)))
659 jobs.append(test_job)
Carl Mastrangelode449102015-10-28 11:05:49 -0700660
661 if args.http2_interop:
662 for test_case in _HTTP2_TEST_CASES:
663 test_job = cloud_to_cloud_jobspec(http2Interop,
664 test_case,
665 server_name,
666 server_host,
667 server_port,
668 docker_image=docker_images.get(str(http2Interop)))
669 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700670
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700671 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700672 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700673 for image in docker_images.itervalues():
674 dockerjob.remove_image(image, skip_nonexistent=True)
675 sys.exit(1)
676
Adele Zhoue4c35612015-10-16 15:34:23 -0700677 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
Adele Zhou2271ab52015-10-28 13:59:14 -0700678 maxjobs=args.jobs)
Adele Zhoue4c35612015-10-16 15:34:23 -0700679 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700680 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700681 else:
682 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700683
Adele Zhoua30f8292015-11-02 13:15:46 -0800684 report_utils.render_xml_report(resultset, 'report.xml')
Adele Zhoue4c35612015-10-16 15:34:23 -0700685
Adele Zhoua30f8292015-11-02 13:15:46 -0800686 report_utils.render_html_report(
Adele Zhou2271ab52015-10-28 13:59:14 -0700687 set([str(l) for l in languages]), servers, _TEST_CASES, _AUTH_TEST_CASES,
Carl Mastrangelode449102015-10-28 11:05:49 -0700688 _HTTP2_TEST_CASES, resultset, num_failures,
689 args.cloud_to_prod_auth or args.cloud_to_prod, args.http2_interop)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700690
691finally:
692 # Check if servers are still running.
693 for server, job in server_jobs.iteritems():
694 if not job.is_running():
695 print 'Server "%s" has exited prematurely.' % server
696
697 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
698
699 for image in docker_images.itervalues():
700 print 'Removing docker image %s' % image
701 dockerjob.remove_image(image)