blob: 1814ba1abc3be2f45ce4ed69f8881948b1bd3dec [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',
54 '--server_port=443']
55
Jan Tattermusch8266c672015-09-17 09:18:03 -070056_CLOUD_TO_CLOUD_BASE_ARGS = [
57 '--server_host_override=foo.test.google.fr']
58
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070059# TOOD(jtattermusch) wrapped languages use this variable for location
Jan Tattermuschf49936a2015-09-16 15:44:26 -070060# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
61# supported by C core SslCredentials instead.
62_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
63
Jan Tattermuschcc1bde72015-10-05 12:47:45 -070064# TODO(jtattermusch) unify usage of --use_tls and --use_tls=true
Jan Tattermuschcc1bde72015-10-05 12:47:45 -070065# TODO(jtattermusch) go uses --tls_ca_file instead of --use_test_ca
Jan Tattermuschf49936a2015-09-16 15:44:26 -070066
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):
77 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070078 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -070079
Jan Tattermusch8266c672015-09-17 09:18:03 -070080 def cloud_to_cloud_args(self):
81 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070082 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -070083
Jan Tattermuschf49936a2015-09-16 15:44:26 -070084 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070085 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070086
Jan Tattermusch91ad0182015-10-01 09:22:03 -070087 def server_args(self):
Jan Tattermusch28bf5592015-10-02 13:50:24 -070088 return ['bins/opt/interop_server', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070089
Masood Malekghassemi18cc8422015-10-09 17:55:45 -070090 def global_env(self):
91 return {}
92
Jan Tattermuschf49936a2015-09-16 15:44:26 -070093 def __str__(self):
94 return 'c++'
95
96
97class CSharpLanguage:
98
99 def __init__(self):
100 self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
101 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700102 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700103 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700104
105 def cloud_to_prod_args(self):
106 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700107 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700108
Jan Tattermusch8266c672015-09-17 09:18:03 -0700109 def cloud_to_cloud_args(self):
110 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700111 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -0700112
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700113 def cloud_to_prod_env(self):
114 return _SSL_CERT_ENV
115
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700116 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -0700117 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700118
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700119 def global_env(self):
120 return {}
121
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700122 def __str__(self):
123 return 'csharp'
124
125
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700126class JavaLanguage:
127
128 def __init__(self):
129 self.client_cmdline_base = ['./run-test-client.sh']
130 self.client_cwd = '../grpc-java'
131 self.server_cwd = '../grpc-java'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700132 self.safename = str(self)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700133
134 def cloud_to_prod_args(self):
135 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
136 ['--use_tls=true'])
137
138 def cloud_to_cloud_args(self):
139 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
140 ['--use_tls=true', '--use_test_ca=true'])
141
142 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700143 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700144
145 def server_args(self):
146 return ['./run-test-server.sh', '--use_tls=true']
147
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700148 def global_env(self):
149 return {}
150
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700151 def __str__(self):
152 return 'java'
153
154
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700155class GoLanguage:
156
157 def __init__(self):
158 self.client_cmdline_base = ['go', 'run', 'client.go']
159 # TODO: this relies on running inside docker
160 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
161 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700162 self.safename = str(self)
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700163
164 def cloud_to_prod_args(self):
165 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
166 ['--use_tls=true', '--tls_ca_file=""'])
167
168 def cloud_to_cloud_args(self):
169 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
170 ['--use_tls=true'])
171
172 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700173 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700174
175 def server_args(self):
176 return ['go', 'run', 'server.go', '--use_tls=true']
177
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700178 def global_env(self):
179 return {}
180
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700181 def __str__(self):
182 return 'go'
183
184
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700185class NodeLanguage:
186
187 def __init__(self):
188 self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
189 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700190 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700191 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700192
193 def cloud_to_prod_args(self):
194 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
195 ['--use_tls=true'])
196
Jan Tattermusch8266c672015-09-17 09:18:03 -0700197 def cloud_to_cloud_args(self):
198 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
199 ['--use_tls=true', '--use_test_ca=true'])
200
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700201 def cloud_to_prod_env(self):
202 return _SSL_CERT_ENV
203
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700204 def server_args(self):
205 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
206
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700207 def global_env(self):
208 return {}
209
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700210 def __str__(self):
211 return 'node'
212
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700213
214class PHPLanguage:
215
216 def __init__(self):
217 self.client_cmdline_base = ['src/php/bin/interop_client.sh']
218 self.client_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700219 self.safename = str(self)
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700220
221 def cloud_to_prod_args(self):
222 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
223 ['--use_tls'])
224
Jan Tattermusch8266c672015-09-17 09:18:03 -0700225 def cloud_to_cloud_args(self):
226 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
227 ['--use_tls', '--use_test_ca'])
228
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700229 def cloud_to_prod_env(self):
230 return _SSL_CERT_ENV
231
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700232 def global_env(self):
233 return {}
234
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700235 def __str__(self):
236 return 'php'
237
238
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700239class RubyLanguage:
240
241 def __init__(self):
242 self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
243 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700244 self.server_cwd = None
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700245 self.safename = str(self)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700246
247 def cloud_to_prod_args(self):
248 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
249 ['--use_tls'])
250
Jan Tattermusch8266c672015-09-17 09:18:03 -0700251 def cloud_to_cloud_args(self):
252 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
253 ['--use_tls', '--use_test_ca'])
254
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700255 def cloud_to_prod_env(self):
256 return _SSL_CERT_ENV
257
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700258 def server_args(self):
259 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls']
260
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700261 def global_env(self):
262 return {}
263
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700264 def __str__(self):
265 return 'ruby'
266
267
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700268class PythonLanguage:
269
270 def __init__(self):
271 self.client_cmdline_base = ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
272 self.client_cwd = None
273 self.server_cwd = None
274 self.safename = str(self)
275
276 def cloud_to_prod_args(self):
277 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
278 ['--use_tls'])
279
280 def cloud_to_cloud_args(self):
281 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
282 ['--use_tls', '--use_test_ca'])
283
284 def cloud_to_prod_env(self):
285 return _SSL_CERT_ENV
286
287 def server_args(self):
288 return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls']
289
290 def global_env(self):
291 return {'LD_LIBRARY_PATH': 'libs/opt'}
292
293 def __str__(self):
294 return 'python'
295
296
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700297_LANGUAGES = {
298 'c++' : CXXLanguage(),
299 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700300 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700301 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700302 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700303 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700304 'ruby' : RubyLanguage(),
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700305 'python' : PythonLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700306}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700307
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700308# languages supported as cloud_to_cloud servers
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700309_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700310
Jan Tattermusch320bd612015-09-15 12:44:35 -0700311_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch1f1c5c52015-10-09 14:36:28 -0700312 'empty_stream', 'client_streaming', 'server_streaming',
Jan Tattermusch13bf36a2015-10-14 17:01:00 -0700313 'cancel_after_begin', 'cancel_after_first_response',
314 'timeout_on_sleeping_server']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700315
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700316_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
317 'oauth2_auth_token', 'per_rpc_creds']
318
Jan Tattermusch8266c672015-09-17 09:18:03 -0700319
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700320def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
321 """Wraps given cmdline array to create 'docker run' cmdline from it."""
322 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
323
324 # turn environ into -e docker args
325 if environ:
326 for k,v in environ.iteritems():
327 docker_cmdline += ['-e', '%s=%s' % (k,v)]
328
329 # set working directory
330 workdir = '/var/local/git/grpc'
331 if cwd:
332 workdir = os.path.join(workdir, cwd)
333 docker_cmdline += ['-w', workdir]
334
335 docker_cmdline += docker_args + [image] + cmdline
336 return docker_cmdline
337
338
339def bash_login_cmdline(cmdline):
340 """Creates bash -l -c cmdline from args list."""
341 # Use login shell:
342 # * rvm and nvm require it
343 # * makes error messages clearer if executables are missing
344 return ['bash', '-l', '-c', ' '.join(cmdline)]
345
346
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700347def add_auth_options(language, test_case, cmdline, env):
348 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
349
350 language = str(language)
351 cmdline = list(cmdline)
352 env = env.copy()
353
354 # TODO(jtattermusch): this file path only works inside docker
355 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
356 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
357 key_file_arg = '--service_account_key_file=%s' % key_filepath
358 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
359
360 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
361 if language in ['csharp', 'node', 'php', 'ruby']:
362 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
363 else:
364 cmdline += [key_file_arg]
365
366 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
367 cmdline += [oauth_scope_arg]
368
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700369 if test_case == 'oauth2_auth_token' and language == 'c++':
370 # C++ oauth2 test uses GCE creds and thus needs to know the default account
371 cmdline += [default_account_arg]
372
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700373 if test_case == 'compute_engine_creds':
374 cmdline += [oauth_scope_arg, default_account_arg]
375
376 return (cmdline, env)
377
378
Jan Tattermusche2686282015-10-08 16:27:07 -0700379def _job_kill_handler(job):
380 if job._spec.container_name:
381 dockerjob.docker_kill(job._spec.container_name)
382
383
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700384def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700385 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700386 cmdline = language.cloud_to_prod_args() + ['--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700387 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700388 environ = dict(language.cloud_to_prod_env(), **language.global_env())
Jan Tattermusche2686282015-10-08 16:27:07 -0700389 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700390 if auth:
391 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
392 cmdline = bash_login_cmdline(cmdline)
393
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700394 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700395 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusche2686282015-10-08 16:27:07 -0700396 cmdline = docker_run_cmdline(cmdline,
397 image=docker_image,
398 cwd=cwd,
399 environ=environ,
400 docker_args=['--net=host',
401 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700402 cwd = None
403 environ = None
404
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700405 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700406 test_job = jobset.JobSpec(
407 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700408 cwd=cwd,
409 environ=environ,
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700410 shortname="%s:%s:%s" % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700411 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700412 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700413 timeout_retries=2 if args.allow_flakes else 0,
414 kill_handler=_job_kill_handler)
415 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700416 return test_job
417
Jan Tattermusch8266c672015-09-17 09:18:03 -0700418
419def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700420 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700421 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700422 cmdline = bash_login_cmdline(language.cloud_to_cloud_args() +
423 ['--test_case=%s' % test_case,
424 '--server_host=%s' % server_host,
425 '--server_port=%s' % server_port ])
426 cwd = language.client_cwd
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700427 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700428 if docker_image:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700429 container_name = dockerjob.random_name('interop_client_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700430 cmdline = docker_run_cmdline(cmdline,
431 image=docker_image,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700432 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700433 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700434 docker_args=['--net=host',
435 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700436 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700437
Jan Tattermusch8266c672015-09-17 09:18:03 -0700438 test_job = jobset.JobSpec(
439 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700440 cwd=cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700441 environ=environ,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700442 shortname="cloud_to_cloud:%s:%s_server:%s" % (language, server_name,
443 test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700444 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700445 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700446 timeout_retries=2 if args.allow_flakes else 0,
447 kill_handler=_job_kill_handler)
448 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700449 return test_job
450
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700451
452def server_jobspec(language, docker_image):
453 """Create jobspec for running a server"""
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700454 container_name = dockerjob.random_name('interop_server_%s' % language.safename)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700455 cmdline = bash_login_cmdline(language.server_args() +
456 ['--port=%s' % _DEFAULT_SERVER_PORT])
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700457 environ = language.global_env()
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700458 docker_cmdline = docker_run_cmdline(cmdline,
459 image=docker_image,
460 cwd=language.server_cwd,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700461 environ=environ,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700462 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700463 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700464 server_job = jobset.JobSpec(
465 cmdline=docker_cmdline,
Masood Malekghassemi18cc8422015-10-09 17:55:45 -0700466 environ=environ,
Jan Tattermusche2686282015-10-08 16:27:07 -0700467 shortname="interop_server_%s" % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700468 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700469 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700470 return server_job
471
472
473def build_interop_image_jobspec(language, tag=None):
474 """Creates jobspec for building interop docker image for a language"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700475 if not tag:
Jan Tattermusch0a14f622015-10-09 14:34:29 -0700476 tag = 'grpc_interop_%s:%s' % (language.safename, uuid.uuid4())
477 env = {'INTEROP_IMAGE': tag,
478 'BASE_NAME': 'grpc_interop_%s' % language.safename}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700479 if not args.travis:
480 env['TTY_FLAG'] = '-t'
481 build_job = jobset.JobSpec(
482 cmdline=['tools/jenkins/build_interop_image.sh'],
483 environ=env,
484 shortname="build_docker_%s" % (language),
485 timeout_seconds=30*60)
486 build_job.tag = tag
487 return build_job
488
489
Jan Tattermusch320bd612015-09-15 12:44:35 -0700490argp = argparse.ArgumentParser(description='Run interop tests.')
491argp.add_argument('-l', '--language',
492 choices=['all'] + sorted(_LANGUAGES),
493 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700494 default=['all'],
495 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700496argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700497argp.add_argument('--cloud_to_prod',
498 default=False,
499 action='store_const',
500 const=True,
501 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700502argp.add_argument('--cloud_to_prod_auth',
503 default=False,
504 action='store_const',
505 const=True,
506 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700507argp.add_argument('-s', '--server',
508 choices=['all'] + sorted(_SERVERS),
509 action='append',
510 help='Run cloud_to_cloud servers in a separate docker ' +
511 'image. Servers can only be started automatically if ' +
512 '--use_docker option is enabled.',
513 default=[])
514argp.add_argument('--override_server',
515 action='append',
516 type=lambda kv: kv.split("="),
517 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
518 default=[])
519argp.add_argument('-t', '--travis',
520 default=False,
521 action='store_const',
522 const=True)
523argp.add_argument('--use_docker',
524 default=False,
525 action='store_const',
526 const=True,
527 help='Run all the interop tests under docker. That provides ' +
528 'additional isolation and prevents the need to install ' +
529 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700530argp.add_argument('--allow_flakes',
531 default=False,
532 action='store_const',
533 const=True,
534 help="Allow flaky tests to show as passing (re-runs failed tests up to five times)")
Jan Tattermusch320bd612015-09-15 12:44:35 -0700535args = argp.parse_args()
536
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700537servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700538 if x == 'all' else [x]
539 for x in args.server))
540
541if args.use_docker:
542 if not args.travis:
543 print 'Seen --use_docker flag, will run interop tests under docker.'
544 print
545 print 'IMPORTANT: The changes you are testing need to be locally committed'
546 print 'because only the committed changes in the current branch will be'
547 print 'copied to the docker environment.'
548 time.sleep(5)
549
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700550if not args.use_docker and servers:
551 print "Running interop servers is only supported with --use_docker option enabled."
552 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700553
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700554languages = set(_LANGUAGES[l]
555 for l in itertools.chain.from_iterable(
556 _LANGUAGES.iterkeys() if x == 'all' else [x]
557 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700558
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700559docker_images={}
560if args.use_docker:
561 # languages for which to build docker images
562 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
563 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700564
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700565 build_jobs = []
566 for l in languages_to_build:
567 job = build_interop_image_jobspec(l)
568 docker_images[str(l)] = job.tag
569 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700570
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700571 if build_jobs:
572 jobset.message('START', 'Building interop docker images.', do_newline=True)
573 if jobset.run(build_jobs, newline_on_success=True, maxjobs=args.jobs):
574 jobset.message('SUCCESS', 'All docker images built successfully.', do_newline=True)
575 else:
576 jobset.message('FAILED', 'Failed to build interop docker images.', do_newline=True)
577 for image in docker_images.itervalues():
578 dockerjob.remove_image(image, skip_nonexistent=True)
579 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700580
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700581# Start interop servers.
582server_jobs={}
583server_addresses={}
584try:
585 for s in servers:
586 lang = str(s)
587 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
588 job = dockerjob.DockerJob(spec)
589 server_jobs[lang] = job
590 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700591
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700592
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700593 jobs = []
594 if args.cloud_to_prod:
595 for language in languages:
596 for test_case in _TEST_CASES:
597 test_job = cloud_to_prod_jobspec(language, test_case,
598 docker_image=docker_images.get(str(language)))
599 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700600
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700601 if args.cloud_to_prod_auth:
602 for language in languages:
603 for test_case in _AUTH_TEST_CASES:
604 test_job = cloud_to_prod_jobspec(language, test_case,
605 docker_image=docker_images.get(str(language)),
606 auth=True)
607 jobs.append(test_job)
608
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700609 for server in args.override_server:
610 server_name = server[0]
611 (server_host, server_port) = server[1].split(':')
612 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700613
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700614 for server_name, server_address in server_addresses.iteritems():
615 (server_host, server_port) = server_address
616 for language in languages:
617 for test_case in _TEST_CASES:
618 test_job = cloud_to_cloud_jobspec(language,
619 test_case,
620 server_name,
621 server_host,
622 server_port,
623 docker_image=docker_images.get(str(language)))
624 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700625
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700626 if not jobs:
627 print "No jobs to run."
628 for image in docker_images.itervalues():
629 dockerjob.remove_image(image, skip_nonexistent=True)
630 sys.exit(1)
631
632 root = ET.Element('testsuites')
633 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
634
635 if jobset.run(jobs, newline_on_success=True, maxjobs=args.jobs, xml_report=testsuite):
636 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
637 else:
638 jobset.message('FAILED', 'Some tests failed', do_newline=True)
639
640 tree = ET.ElementTree(root)
641 tree.write('report.xml', encoding='UTF-8')
642
643finally:
644 # Check if servers are still running.
645 for server, job in server_jobs.iteritems():
646 if not job.is_running():
647 print 'Server "%s" has exited prematurely.' % server
648
649 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
650
651 for image in docker_images.itervalues():
652 print 'Removing docker image %s' % image
653 dockerjob.remove_image(image)