blob: 80f28c544b8d3e9756c42be7fda8774e24634d2f [file] [log] [blame]
Jan Tattermusch320bd612015-09-15 12:44:35 -07001#!/usr/bin/env python
2# Copyright 2015, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Run interop (cross-language) tests in parallel."""
32
33import argparse
Jan Tattermusch91ad0182015-10-01 09:22:03 -070034import dockerjob
Jan Tattermusch320bd612015-09-15 12:44:35 -070035import itertools
36import xml.etree.cElementTree as ET
37import jobset
Jan Tattermusch210a0ea2015-10-02 15:05:36 -070038import multiprocessing
Jan Tattermusch8266c672015-09-17 09:18:03 -070039import os
40import subprocess
41import sys
Jan Tattermusch91ad0182015-10-01 09:22:03 -070042import tempfile
Jan Tattermusch8266c672015-09-17 09:18:03 -070043import time
Jan Tattermusch91ad0182015-10-01 09:22:03 -070044import uuid
Jan Tattermusch320bd612015-09-15 12:44:35 -070045
Jan Tattermusch91ad0182015-10-01 09:22:03 -070046ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
47os.chdir(ROOT)
48
49_DEFAULT_SERVER_PORT=8080
Jan Tattermuschf49936a2015-09-16 15:44:26 -070050
51_CLOUD_TO_PROD_BASE_ARGS = [
52 '--server_host_override=grpc-test.sandbox.google.com',
53 '--server_host=grpc-test.sandbox.google.com',
Jan Tattermusch7f24fb32015-10-19 10:11:58 -070054 '--server_port=443',
55 '--use_tls=true']
Jan Tattermuschf49936a2015-09-16 15:44:26 -070056
Jan Tattermusch8266c672015-09-17 09:18:03 -070057_CLOUD_TO_CLOUD_BASE_ARGS = [
Jan Tattermusch7f24fb32015-10-19 10:11:58 -070058 '--server_host_override=foo.test.google.fr',
Jan Tattermusch75d2ee02015-10-20 17:09:34 -070059 '--use_tls=true',
60 '--use_test_ca=true']
Jan Tattermusch8266c672015-09-17 09:18:03 -070061
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070062# TOOD(jtattermusch) wrapped languages use this variable for location
Jan Tattermuschf49936a2015-09-16 15:44:26 -070063# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
64# supported by C core SslCredentials instead.
65_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
66
Jan Tattermusch8266c672015-09-17 09:18:03 -070067
Jan Tattermuschf49936a2015-09-16 15:44:26 -070068class CXXLanguage:
69
70 def __init__(self):
71 self.client_cmdline_base = ['bins/opt/interop_client']
72 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -070073 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -070074 self.safename = 'cxx'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070075
76 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -070077 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschf49936a2015-09-16 15:44:26 -070078
Jan Tattermusch8266c672015-09-17 09:18:03 -070079 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -070080 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch8266c672015-09-17 09:18:03 -070081
Jan Tattermuschf49936a2015-09-16 15:44:26 -070082 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070083 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070084
Jan Tattermusch91ad0182015-10-01 09:22:03 -070085 def server_args(self):
Jan Tattermusch28bf5592015-10-02 13:50:24 -070086 return ['bins/opt/interop_server', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070087
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070088 def global_env(self):
89 return {}
90
Jan Tattermuschf49936a2015-09-16 15:44:26 -070091 def __str__(self):
92 return 'c++'
93
94
95class CSharpLanguage:
96
97 def __init__(self):
98 self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
99 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700100 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700101 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700102
103 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700104 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700105
Jan Tattermusch8266c672015-09-17 09:18:03 -0700106 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700107 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700108
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700109 def cloud_to_prod_env(self):
110 return _SSL_CERT_ENV
111
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700112 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -0700113 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700114
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700115 def global_env(self):
116 return {}
117
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700118 def __str__(self):
119 return 'csharp'
120
121
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700122class JavaLanguage:
123
124 def __init__(self):
125 self.client_cmdline_base = ['./run-test-client.sh']
126 self.client_cwd = '../grpc-java'
127 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700128 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700129
130 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700131 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700132
133 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700134 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700135
136 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700137 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700138
139 def server_args(self):
140 return ['./run-test-server.sh', '--use_tls=true']
141
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700142 def global_env(self):
143 return {}
144
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700145 def __str__(self):
146 return 'java'
147
148
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700149class GoLanguage:
150
151 def __init__(self):
152 self.client_cmdline_base = ['go', 'run', 'client.go']
153 # TODO: this relies on running inside docker
154 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
155 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700156 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700157
158 def cloud_to_prod_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700159 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700160
161 def cloud_to_cloud_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700162 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700163
164 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700165 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700166
167 def server_args(self):
168 return ['go', 'run', 'server.go', '--use_tls=true']
169
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700170 def global_env(self):
171 return {}
172
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700173 def __str__(self):
174 return 'go'
175
176
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700177class NodeLanguage:
178
179 def __init__(self):
180 self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
181 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700182 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700183 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700184
185 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700186 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700187
Jan Tattermusch8266c672015-09-17 09:18:03 -0700188 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700189 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700190
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700191 def cloud_to_prod_env(self):
192 return _SSL_CERT_ENV
193
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700194 def server_args(self):
195 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
196
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700197 def global_env(self):
198 return {}
199
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700200 def __str__(self):
201 return 'node'
202
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700203
204class PHPLanguage:
205
206 def __init__(self):
207 self.client_cmdline_base = ['src/php/bin/interop_client.sh']
208 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700209 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700210
211 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700212 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700213
Jan Tattermusch8266c672015-09-17 09:18:03 -0700214 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700215 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700216
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700217 def cloud_to_prod_env(self):
218 return _SSL_CERT_ENV
219
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700220 def global_env(self):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700221 return {}
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700222
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700223 def __str__(self):
224 return 'php'
225
226
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700227class RubyLanguage:
228
229 def __init__(self):
230 self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
231 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700232 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700233 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700234
235 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700236 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700237
Jan Tattermusch8266c672015-09-17 09:18:03 -0700238 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700239 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700240
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700241 def cloud_to_prod_env(self):
242 return _SSL_CERT_ENV
243
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700244 def server_args(self):
Jan Tattermusch73b3eea2015-10-15 17:52:06 -0700245 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700246
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700247 def global_env(self):
248 return {}
249
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700250 def __str__(self):
251 return 'ruby'
252
253
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700254class PythonLanguage:
255
256 def __init__(self):
257 self.client_cmdline_base = ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
258 self.client_cwd = None
259 self.server_cwd = None
260 self.safename = str(self)
261
262 def cloud_to_prod_args(self):
Jan Tattermusch7f24fb32015-10-19 10:11:58 -0700263 return self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700264
265 def cloud_to_cloud_args(self):
Jan Tattermusch75d2ee02015-10-20 17:09:34 -0700266 return self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700267
268 def cloud_to_prod_env(self):
269 return _SSL_CERT_ENV
270
271 def server_args(self):
Jan Tattermusch785efd42015-10-15 17:20:22 -0700272 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls=true']
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700273
274 def global_env(self):
275 return {'LD_LIBRARY_PATH': 'libs/opt'}
276
277 def __str__(self):
278 return 'python'
279
280
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700281_LANGUAGES = {
282 'c++' : CXXLanguage(),
283 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700284 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700285 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700286 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700287 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700288 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700289 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700290}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700291
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700292# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700293_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700294
Jan Tattermusch320bd612015-09-15 12:44:35 -0700295_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700296 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700297 'cancel_after_begin', 'cancel_after_first_response',
298 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700299
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700300_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
301 'oauth2_auth_token', 'per_rpc_creds']
302
Jan Tattermusch8266c672015-09-17 09:18:03 -0700303
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700304def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
305 """Wraps given cmdline array to create 'docker run' cmdline from it."""
306 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
307
308 # turn environ into -e docker args
309 if environ:
310 for k,v in environ.iteritems():
311 docker_cmdline += ['-e', '%s=%s' % (k,v)]
312
313 # set working directory
314 workdir = '/var/local/git/grpc'
315 if cwd:
316 workdir = os.path.join(workdir, cwd)
317 docker_cmdline += ['-w', workdir]
318
319 docker_cmdline += docker_args + [image] + cmdline
320 return docker_cmdline
321
322
323def bash_login_cmdline(cmdline):
324 """Creates bash -l -c cmdline from args list."""
325 # Use login shell:
326 # * rvm and nvm require it
327 # * makes error messages clearer if executables are missing
328 return ['bash', '-l', '-c', ' '.join(cmdline)]
329
330
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700331def add_auth_options(language, test_case, cmdline, env):
332 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
333
334 language = str(language)
335 cmdline = list(cmdline)
336 env = env.copy()
337
338 # TODO(jtattermusch): this file path only works inside docker
339 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
340 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
341 key_file_arg = '--service_account_key_file=%s' % key_filepath
342 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
343
344 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
Jan Tattermusch3b6fef12015-10-19 19:33:24 -0700345 if language in ['csharp', 'node', 'php', 'python', 'ruby']:
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700346 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
347 else:
348 cmdline += [key_file_arg]
349
350 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
351 cmdline += [oauth_scope_arg]
352
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700353 if test_case == 'oauth2_auth_token' and language == 'c++':
354 # C++ oauth2 test uses GCE creds and thus needs to know the default account
355 cmdline += [default_account_arg]
356
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700357 if test_case == 'compute_engine_creds':
358 cmdline += [oauth_scope_arg, default_account_arg]
359
360 return (cmdline, env)
361
362
Jan Tattermusche2686282015-10-08 16:27:07 -0700363def _job_kill_handler(job):
364 if job._spec.container_name:
365 dockerjob.docker_kill(job._spec.container_name)
366
367
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700368def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700369 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700370 cmdline = language.cloud_to_prod_args() + ['--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700371 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700372 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700373 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700374 if auth:
375 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
376 cmdline = bash_login_cmdline(cmdline)
377
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700378 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700379 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700380 cmdline = docker_run_cmdline(cmdline,
381 image=docker_image,
382 cwd=cwd,
383 environ=environ,
384 docker_args=['--net=host',
385 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700386 cwd = None
387 environ = None
388
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700389 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700390 test_job = jobset.JobSpec(
391 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700392 cwd=cwd,
393 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700394 shortname='%s:%s:%s' % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700395 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700396 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700397 timeout_retries=2 if args.allow_flakes else 0,
398 kill_handler=_job_kill_handler)
399 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700400 return test_job
401
Jan Tattermusch8266c672015-09-17 09:18:03 -0700402
403def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700404 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700405 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700406 cmdline = bash_login_cmdline(language.cloud_to_cloud_args() +
407 ['--test_case=%s' % test_case,
408 '--server_host=%s' % server_host,
409 '--server_port=%s' % server_port ])
410 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700411 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700412 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700413 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700414 cmdline = docker_run_cmdline(cmdline,
415 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700416 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700417 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700418 docker_args=['--net=host',
419 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700420 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700421
Jan Tattermusch8266c672015-09-17 09:18:03 -0700422 test_job = jobset.JobSpec(
423 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700424 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700425 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700426 shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700427 test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700428 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700429 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700430 timeout_retries=2 if args.allow_flakes else 0,
431 kill_handler=_job_kill_handler)
432 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700433 return test_job
434
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700435
436def server_jobspec(language, docker_image):
437 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700438 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700439 cmdline = bash_login_cmdline(language.server_args() +
440 ['--port=%s' % _DEFAULT_SERVER_PORT])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700441 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700442 docker_cmdline = docker_run_cmdline(cmdline,
443 image=docker_image,
444 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700445 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700446 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700447 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700448 server_job = jobset.JobSpec(
449 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700450 environ=environ,
Adele Zhoue4c35612015-10-16 15:34:23 -0700451 shortname='interop_server_%s' % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700452 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700453 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700454 return server_job
455
456
457def build_interop_image_jobspec(language, tag=None):
458 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700459 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700460 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700461 env = {'INTEROP_IMAGE': tag,
462 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700463 if not args.travis:
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700464 env['TTY_FLAG'] = '-t'
465 # This env variable is used to get around the github rate limit
466 # error when running the PHP `composer install` command
467 # TODO(stanleycheung): find a more elegant way to do this
Stanley Cheungf565dfb2015-10-15 17:57:09 -0700468 if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700469 env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
Adele Zhoue4c35612015-10-16 15:34:23 -0700470 '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700471 build_job = jobset.JobSpec(
472 cmdline=['tools/jenkins/build_interop_image.sh'],
Stanley Cheung22b6bed2015-10-15 16:54:52 -0700473 environ=env,
Adele Zhoue4c35612015-10-16 15:34:23 -0700474 shortname='build_docker_%s' % (language),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700475 timeout_seconds=30*60)
476 build_job.tag = tag
477 return build_job
478
479
Adele Zhoue4c35612015-10-16 15:34:23 -0700480# TODO(adelez): Use mako template.
481def fill_one_test_result(shortname, resultset, html_str):
482 if shortname in resultset:
483 result = resultset[shortname]
484 if result.state == 'PASSED':
485 html_str = '%s<td bgcolor=\"green\">PASS</td>\n' % html_str
486 else:
487 tooltip = ''
488 if result.returncode > 0 or result.message:
489 if result.returncode > 0:
490 tooltip = 'returncode: %d ' % result.returncode
491 if result.message:
492 tooltip = '%smessage: %s' % (tooltip, result.message)
493 if result.state == 'FAILED':
494 html_str = '%s<td bgcolor=\"red\">' % html_str
495 if tooltip:
496 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
497 'data-placement=\"auto\" title=\"%s\">FAIL</a></td>\n' %
498 (html_str, tooltip))
499 else:
500 html_str = '%sFAIL</td>\n' % html_str
501 elif result.state == 'TIMEOUT':
502 html_str = '%s<td bgcolor=\"yellow\">' % html_str
503 if tooltip:
504 html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
505 'data-placement=\"auto\" title=\"%s\">TIMEOUT</a></td>\n'
506 % (html_str, tooltip))
507 else:
508 html_str = '%sTIMEOUT</td>\n' % html_str
509 else:
510 html_str = '%s<td bgcolor=\"magenta\">Not implemented</td>\n' % html_str
511
512 return html_str
513
514
515def render_html_report(test_cases, client_langs, server_langs, resultset,
516 num_failures):
517 """Generate html report."""
518 sorted_test_cases = sorted(test_cases)
519 sorted_client_langs = sorted(client_langs)
520 print sorted_client_langs
521 sorted_server_langs = sorted(server_langs)
522 html_str = ('<!DOCTYPE html>\n'
523 '<html lang=\"en\">\n'
524 '<head><title>Interop Test Result</title></head>\n'
525 '<body>\n')
526 if num_failures > 1:
527 html_str = (
528 '%s<p><h2><font color=\"red\">%d tests failed!</font></h2></p>\n' %
529 (html_str, num_failures))
530 elif num_failures:
531 html_str = (
532 '%s<p><h2><font color=\"red\">%d test failed!</font></h2></p>\n' %
533 (html_str, num_failures))
534 else:
535 html_str = (
536 '%s<p><h2><font color=\"green\">All tests passed!</font></h2></p>\n' %
537 html_str)
538 if args.cloud_to_prod_auth or args.cloud_to_prod:
539 # Each column header is the client language.
540 html_str = ('%s<h2>Cloud to Prod</h2>\n'
541 '<table style=\"width:100%%\" border=\"1\">\n'
542 '<tr bgcolor=\"#00BFFF\">\n'
543 '<th/>\n') % html_str
544 for client_lang in sorted_client_langs:
545 html_str = '%s<th>%s\n' % (html_str, client_lang)
546 html_str = '%s</tr>\n' % html_str
547 for test_case in sorted_test_cases:
548 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, test_case)
549 for client_lang in sorted_client_langs:
550 if args.cloud_to_prod:
551 shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case)
552 else:
553 shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case)
554 html_str = fill_one_test_result(shortname, resultset, html_str)
555 html_str = '%s</tr>\n' % html_str
556 html_str = '%s</table>\n' % html_str
557 if servers:
558 for test_case in sorted_test_cases:
559 # Each column header is the client language.
560 html_str = ('%s<h2>%s</h2>\n'
561 '<table style=\"width:100%%\" border=\"1\">\n'
562 '<tr bgcolor=\"#00BFFF\">\n'
563 '<th/>\n') % (html_str, test_case)
564 for client_lang in sorted_client_langs:
565 html_str = '%s<th>%s\n' % (html_str, client_lang)
566 html_str = '%s</tr>\n' % html_str
567 # Each row head is the server language.
568 for server_lang in sorted_server_langs:
569 html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, server_lang)
570 # Fill up the cells with test result.
571 for client_lang in sorted_client_langs:
572 shortname = 'cloud_to_cloud:%s:%s_server:%s' % (
573 client_lang, server_lang, test_case)
574 html_str = fill_one_test_result(shortname, resultset, html_str)
575 html_str = '%s</tr>\n' % html_str
576 html_str = '%s</table>\n' % html_str
577
578 html_str = ('%s\n'
579 '<script>\n'
580 '$(document).ready(function(){'
581 '$(\'[data-toggle=\"tooltip\"]\').tooltip();\n'
582 '});\n'
583 '</script>\n'
584 '</body>\n'
585 '</html>') % html_str
586
587 # Write to reports/index.html as set up in Jenkins plugin.
588 html_report_dir = 'reports'
589 if not os.path.exists(html_report_dir):
590 os.mkdir(html_report_dir)
591 html_file_path = os.path.join(html_report_dir, 'index.html')
592 with open(html_file_path, 'w') as f:
593 f.write(html_str)
594
595
Jan Tattermusch320bd612015-09-15 12:44:35 -0700596argp = argparse.ArgumentParser(description='Run interop tests.')
597argp.add_argument('-l', '--language',
598 choices=['all'] + sorted(_LANGUAGES),
599 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700600 default=['all'],
601 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700602argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700603argp.add_argument('--cloud_to_prod',
604 default=False,
605 action='store_const',
606 const=True,
607 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700608argp.add_argument('--cloud_to_prod_auth',
609 default=False,
610 action='store_const',
611 const=True,
612 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700613argp.add_argument('-s', '--server',
614 choices=['all'] + sorted(_SERVERS),
615 action='append',
616 help='Run cloud_to_cloud servers in a separate docker ' +
617 'image. Servers can only be started automatically if ' +
618 '--use_docker option is enabled.',
619 default=[])
620argp.add_argument('--override_server',
621 action='append',
Adele Zhoue4c35612015-10-16 15:34:23 -0700622 type=lambda kv: kv.split('='),
Jan Tattermusch8266c672015-09-17 09:18:03 -0700623 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
624 default=[])
625argp.add_argument('-t', '--travis',
626 default=False,
627 action='store_const',
628 const=True)
629argp.add_argument('--use_docker',
630 default=False,
631 action='store_const',
632 const=True,
633 help='Run all the interop tests under docker. That provides ' +
634 'additional isolation and prevents the need to install ' +
635 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700636argp.add_argument('--allow_flakes',
637 default=False,
638 action='store_const',
639 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700640 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Jan Tattermusch320bd612015-09-15 12:44:35 -0700641args = argp.parse_args()
642
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700643servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700644 if x == 'all' else [x]
645 for x in args.server))
646
647if args.use_docker:
648 if not args.travis:
649 print 'Seen --use_docker flag, will run interop tests under docker.'
650 print
651 print 'IMPORTANT: The changes you are testing need to be locally committed'
652 print 'because only the committed changes in the current branch will be'
653 print 'copied to the docker environment.'
654 time.sleep(5)
655
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700656if not args.use_docker and servers:
Adele Zhoue4c35612015-10-16 15:34:23 -0700657 print 'Running interop servers is only supported with --use_docker option enabled.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700658 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700659
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700660languages = set(_LANGUAGES[l]
661 for l in itertools.chain.from_iterable(
662 _LANGUAGES.iterkeys() if x == 'all' else [x]
663 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700664
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700665docker_images={}
666if args.use_docker:
667 # languages for which to build docker images
668 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
669 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700670
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700671 build_jobs = []
672 for l in languages_to_build:
673 job = build_interop_image_jobspec(l)
674 docker_images[str(l)] = job.tag
675 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700676
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700677 if build_jobs:
678 jobset.message('START', 'Building interop docker images.', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700679 num_failures, _ = jobset.run(
680 build_jobs, newline_on_success=True, maxjobs=args.jobs)
681 if num_failures == 0:
682 jobset.message('SUCCESS', 'All docker images built successfully.',
683 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700684 else:
Adele Zhoue4c35612015-10-16 15:34:23 -0700685 jobset.message('FAILED', 'Failed to build interop docker images.',
686 do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700687 for image in docker_images.itervalues():
688 dockerjob.remove_image(image, skip_nonexistent=True)
689 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700690
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700691# Start interop servers.
692server_jobs={}
693server_addresses={}
694try:
695 for s in servers:
696 lang = str(s)
697 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
698 job = dockerjob.DockerJob(spec)
699 server_jobs[lang] = job
700 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700701
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700702
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700703 jobs = []
704 if args.cloud_to_prod:
705 for language in languages:
706 for test_case in _TEST_CASES:
707 test_job = cloud_to_prod_jobspec(language, test_case,
708 docker_image=docker_images.get(str(language)))
709 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700710
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700711 if args.cloud_to_prod_auth:
712 for language in languages:
713 for test_case in _AUTH_TEST_CASES:
714 test_job = cloud_to_prod_jobspec(language, test_case,
715 docker_image=docker_images.get(str(language)),
716 auth=True)
717 jobs.append(test_job)
718
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700719 for server in args.override_server:
720 server_name = server[0]
721 (server_host, server_port) = server[1].split(':')
722 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700723
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700724 for server_name, server_address in server_addresses.iteritems():
725 (server_host, server_port) = server_address
726 for language in languages:
727 for test_case in _TEST_CASES:
728 test_job = cloud_to_cloud_jobspec(language,
729 test_case,
730 server_name,
731 server_host,
732 server_port,
733 docker_image=docker_images.get(str(language)))
734 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700735
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700736 if not jobs:
Adele Zhoue4c35612015-10-16 15:34:23 -0700737 print 'No jobs to run.'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700738 for image in docker_images.itervalues():
739 dockerjob.remove_image(image, skip_nonexistent=True)
740 sys.exit(1)
741
742 root = ET.Element('testsuites')
743 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
744
Adele Zhoue4c35612015-10-16 15:34:23 -0700745 num_failures, resultset = jobset.run(jobs, newline_on_success=True,
746 maxjobs=args.jobs, xml_report=testsuite)
747 if num_failures:
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700748 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Adele Zhoue4c35612015-10-16 15:34:23 -0700749 else:
750 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700751
752 tree = ET.ElementTree(root)
753 tree.write('report.xml', encoding='UTF-8')
Adele Zhoue4c35612015-10-16 15:34:23 -0700754
755 # Generate HTML report.
756 render_html_report(_TEST_CASES, set([str(l) for l in languages]), servers,
757 resultset, num_failures)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700758
759finally:
760 # Check if servers are still running.
761 for server, job in server_jobs.iteritems():
762 if not job.is_running():
763 print 'Server "%s" has exited prematurely.' % server
764
765 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
766
767 for image in docker_images.itervalues():
768 print 'Removing docker image %s' % image
769 dockerjob.remove_image(image)