blob: 4d09ae7fcdafc87b2294d19e95d044e31853a848 [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
Jan Tattermuschf49936a2015-09-16 15:44:26 -070059# TOOD(jtattermusch) wrapped languages use this variable for location
60# 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 Tattermuschf49936a2015-09-16 15:44:26 -070074
75 def cloud_to_prod_args(self):
76 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070077 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -070078
Jan Tattermusch8266c672015-09-17 09:18:03 -070079 def cloud_to_cloud_args(self):
80 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
yang-ga006b5f2015-10-07 13:14:35 -070081 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -070082
Jan Tattermuschf49936a2015-09-16 15:44:26 -070083 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -070084 return {}
Jan Tattermuschf49936a2015-09-16 15:44:26 -070085
Jan Tattermusch91ad0182015-10-01 09:22:03 -070086 def server_args(self):
Jan Tattermusch28bf5592015-10-02 13:50:24 -070087 return ['bins/opt/interop_server', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -070088
Jan Tattermuschf49936a2015-09-16 15:44:26 -070089 def __str__(self):
90 return 'c++'
91
92
93class CSharpLanguage:
94
95 def __init__(self):
96 self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
97 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070098 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070099
100 def cloud_to_prod_args(self):
101 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700102 ['--use_tls=true'])
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700103
Jan Tattermusch8266c672015-09-17 09:18:03 -0700104 def cloud_to_cloud_args(self):
105 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
Jan Tattermusch7828e812015-10-07 17:27:48 -0700106 ['--use_tls=true', '--use_test_ca=true'])
Jan Tattermusch8266c672015-09-17 09:18:03 -0700107
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700108 def cloud_to_prod_env(self):
109 return _SSL_CERT_ENV
110
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700111 def server_args(self):
Jan Tattermusch7828e812015-10-07 17:27:48 -0700112 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700113
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700114 def __str__(self):
115 return 'csharp'
116
117
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700118class JavaLanguage:
119
120 def __init__(self):
121 self.client_cmdline_base = ['./run-test-client.sh']
122 self.client_cwd = '../grpc-java'
123 self.server_cwd = '../grpc-java'
124
125 def cloud_to_prod_args(self):
126 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
127 ['--use_tls=true'])
128
129 def cloud_to_cloud_args(self):
130 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
131 ['--use_tls=true', '--use_test_ca=true'])
132
133 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700134 return {}
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700135
136 def server_args(self):
137 return ['./run-test-server.sh', '--use_tls=true']
138
139 def __str__(self):
140 return 'java'
141
142
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700143class GoLanguage:
144
145 def __init__(self):
146 self.client_cmdline_base = ['go', 'run', 'client.go']
147 # TODO: this relies on running inside docker
148 self.client_cwd = '/go/src/google.golang.org/grpc/interop/client'
149 self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
150
151 def cloud_to_prod_args(self):
152 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
153 ['--use_tls=true', '--tls_ca_file=""'])
154
155 def cloud_to_cloud_args(self):
156 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
157 ['--use_tls=true'])
158
159 def cloud_to_prod_env(self):
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700160 return {}
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700161
162 def server_args(self):
163 return ['go', 'run', 'server.go', '--use_tls=true']
164
165 def __str__(self):
166 return 'go'
167
168
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700169class NodeLanguage:
170
171 def __init__(self):
172 self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
173 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700174 self.server_cwd = None
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700175
176 def cloud_to_prod_args(self):
177 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
178 ['--use_tls=true'])
179
Jan Tattermusch8266c672015-09-17 09:18:03 -0700180 def cloud_to_cloud_args(self):
181 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
182 ['--use_tls=true', '--use_test_ca=true'])
183
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700184 def cloud_to_prod_env(self):
185 return _SSL_CERT_ENV
186
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700187 def server_args(self):
188 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
189
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700190 def __str__(self):
191 return 'node'
192
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700193
194class PHPLanguage:
195
196 def __init__(self):
197 self.client_cmdline_base = ['src/php/bin/interop_client.sh']
198 self.client_cwd = None
199
200 def cloud_to_prod_args(self):
201 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
202 ['--use_tls'])
203
Jan Tattermusch8266c672015-09-17 09:18:03 -0700204 def cloud_to_cloud_args(self):
205 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
206 ['--use_tls', '--use_test_ca'])
207
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700208 def cloud_to_prod_env(self):
209 return _SSL_CERT_ENV
210
211 def __str__(self):
212 return 'php'
213
214
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700215class RubyLanguage:
216
217 def __init__(self):
218 self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
219 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700220 self.server_cwd = None
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700221
222 def cloud_to_prod_args(self):
223 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
224 ['--use_tls'])
225
Jan Tattermusch8266c672015-09-17 09:18:03 -0700226 def cloud_to_cloud_args(self):
227 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
228 ['--use_tls', '--use_test_ca'])
229
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700230 def cloud_to_prod_env(self):
231 return _SSL_CERT_ENV
232
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700233 def server_args(self):
234 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls']
235
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700236 def __str__(self):
237 return 'ruby'
238
239
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700240# TODO(jtattermusch): python once we get it working
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700241_LANGUAGES = {
242 'c++' : CXXLanguage(),
243 'csharp' : CSharpLanguage(),
Jan Tattermuschcc1bde72015-10-05 12:47:45 -0700244 'go' : GoLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700245 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700246 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700247 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700248 'ruby' : RubyLanguage(),
249}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700250
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700251# languages supported as cloud_to_cloud servers
Jan Tattermuschb1dec722015-10-06 11:46:01 -0700252_SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700253
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700254# TODO(jtattermusch): add empty_stream once PHP starts supporting it.
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700255# TODO(jtattermusch): add timeout_on_sleeping_server once java starts supporting it.
Jan Tattermusch320bd612015-09-15 12:44:35 -0700256# TODO(jtattermusch): add support for auth tests.
257_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700258 'client_streaming', 'server_streaming',
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700259 'cancel_after_begin', 'cancel_after_first_response']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700260
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700261_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
262 'oauth2_auth_token', 'per_rpc_creds']
263
Jan Tattermusch8266c672015-09-17 09:18:03 -0700264
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700265def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
266 """Wraps given cmdline array to create 'docker run' cmdline from it."""
267 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
268
269 # turn environ into -e docker args
270 if environ:
271 for k,v in environ.iteritems():
272 docker_cmdline += ['-e', '%s=%s' % (k,v)]
273
274 # set working directory
275 workdir = '/var/local/git/grpc'
276 if cwd:
277 workdir = os.path.join(workdir, cwd)
278 docker_cmdline += ['-w', workdir]
279
280 docker_cmdline += docker_args + [image] + cmdline
281 return docker_cmdline
282
283
284def bash_login_cmdline(cmdline):
285 """Creates bash -l -c cmdline from args list."""
286 # Use login shell:
287 # * rvm and nvm require it
288 # * makes error messages clearer if executables are missing
289 return ['bash', '-l', '-c', ' '.join(cmdline)]
290
291
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700292def add_auth_options(language, test_case, cmdline, env):
293 """Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
294
295 language = str(language)
296 cmdline = list(cmdline)
297 env = env.copy()
298
299 # TODO(jtattermusch): this file path only works inside docker
300 key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
301 oauth_scope_arg = '--oauth_scope=https://www.googleapis.com/auth/xapi.zoo'
302 key_file_arg = '--service_account_key_file=%s' % key_filepath
303 default_account_arg = '--default_service_account=830293263384-compute@developer.gserviceaccount.com'
304
305 if test_case in ['jwt_token_creds', 'per_rpc_creds', 'oauth2_auth_token']:
306 if language in ['csharp', 'node', 'php', 'ruby']:
307 env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
308 else:
309 cmdline += [key_file_arg]
310
311 if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
312 cmdline += [oauth_scope_arg]
313
Jan Tattermusch64d7c242015-10-08 08:02:27 -0700314 if test_case == 'oauth2_auth_token' and language == 'c++':
315 # C++ oauth2 test uses GCE creds and thus needs to know the default account
316 cmdline += [default_account_arg]
317
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700318 if test_case == 'compute_engine_creds':
319 cmdline += [oauth_scope_arg, default_account_arg]
320
321 return (cmdline, env)
322
323
Jan Tattermusche2686282015-10-08 16:27:07 -0700324def _job_kill_handler(job):
325 if job._spec.container_name:
326 dockerjob.docker_kill(job._spec.container_name)
327
328
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700329def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700330 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700331 cmdline = language.cloud_to_prod_args() + ['--test_case=%s' % test_case]
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700332 cwd = language.client_cwd
333 environ = language.cloud_to_prod_env()
Jan Tattermusche2686282015-10-08 16:27:07 -0700334 container_name = None
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700335 if auth:
336 cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
337 cmdline = bash_login_cmdline(cmdline)
338
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700339 if docker_image:
Jan Tattermusche2686282015-10-08 16:27:07 -0700340 container_name = dockerjob.random_name('interop_client_%s' % language)
341 cmdline = docker_run_cmdline(cmdline,
342 image=docker_image,
343 cwd=cwd,
344 environ=environ,
345 docker_args=['--net=host',
346 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700347 cwd = None
348 environ = None
349
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700350 suite_name='cloud_to_prod_auth' if auth else 'cloud_to_prod'
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700351 test_job = jobset.JobSpec(
352 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700353 cwd=cwd,
354 environ=environ,
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700355 shortname="%s:%s:%s" % (suite_name, language, test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700356 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700357 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700358 timeout_retries=2 if args.allow_flakes else 0,
359 kill_handler=_job_kill_handler)
360 test_job.container_name = container_name
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700361 return test_job
362
Jan Tattermusch8266c672015-09-17 09:18:03 -0700363
364def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700365 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700366 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700367 cmdline = bash_login_cmdline(language.cloud_to_cloud_args() +
368 ['--test_case=%s' % test_case,
369 '--server_host=%s' % server_host,
370 '--server_port=%s' % server_port ])
371 cwd = language.client_cwd
372 if docker_image:
Jan Tattermusche2686282015-10-08 16:27:07 -0700373 container_name = dockerjob.random_name('interop_client_%s' % language)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700374 cmdline = docker_run_cmdline(cmdline,
375 image=docker_image,
376 cwd=cwd,
Jan Tattermusche2686282015-10-08 16:27:07 -0700377 docker_args=['--net=host',
378 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700379 cwd = None
Jan Tattermusche2686282015-10-08 16:27:07 -0700380
Jan Tattermusch8266c672015-09-17 09:18:03 -0700381 test_job = jobset.JobSpec(
382 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700383 cwd=cwd,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700384 shortname="cloud_to_cloud:%s:%s_server:%s" % (language, server_name,
385 test_case),
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700386 timeout_seconds=2*60,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700387 flake_retries=5 if args.allow_flakes else 0,
Jan Tattermusche2686282015-10-08 16:27:07 -0700388 timeout_retries=2 if args.allow_flakes else 0,
389 kill_handler=_job_kill_handler)
390 test_job.container_name = container_name
Jan Tattermusch8266c672015-09-17 09:18:03 -0700391 return test_job
392
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700393
394def server_jobspec(language, docker_image):
395 """Create jobspec for running a server"""
Jan Tattermusche2686282015-10-08 16:27:07 -0700396 container_name = dockerjob.random_name('interop_server_%s' % language)
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700397 cmdline = bash_login_cmdline(language.server_args() +
398 ['--port=%s' % _DEFAULT_SERVER_PORT])
399 docker_cmdline = docker_run_cmdline(cmdline,
400 image=docker_image,
401 cwd=language.server_cwd,
402 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
Jan Tattermusche2686282015-10-08 16:27:07 -0700403 '--name', container_name])
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700404 server_job = jobset.JobSpec(
405 cmdline=docker_cmdline,
Jan Tattermusche2686282015-10-08 16:27:07 -0700406 shortname="interop_server_%s" % language,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700407 timeout_seconds=30*60)
Jan Tattermusche2686282015-10-08 16:27:07 -0700408 server_job.container_name = container_name
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700409 return server_job
410
411
412def build_interop_image_jobspec(language, tag=None):
413 """Creates jobspec for building interop docker image for a language"""
414 safelang = str(language).replace("+", "x")
415 if not tag:
416 tag = 'grpc_interop_%s:%s' % (safelang, uuid.uuid4())
417 env = {'INTEROP_IMAGE': tag, 'BASE_NAME': 'grpc_interop_%s' % safelang}
418 if not args.travis:
419 env['TTY_FLAG'] = '-t'
420 build_job = jobset.JobSpec(
421 cmdline=['tools/jenkins/build_interop_image.sh'],
422 environ=env,
423 shortname="build_docker_%s" % (language),
424 timeout_seconds=30*60)
425 build_job.tag = tag
426 return build_job
427
428
Jan Tattermusch320bd612015-09-15 12:44:35 -0700429argp = argparse.ArgumentParser(description='Run interop tests.')
430argp.add_argument('-l', '--language',
431 choices=['all'] + sorted(_LANGUAGES),
432 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700433 default=['all'],
434 help='Clients to run.')
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700435argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700436argp.add_argument('--cloud_to_prod',
437 default=False,
438 action='store_const',
439 const=True,
440 help='Run cloud_to_prod tests.')
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700441argp.add_argument('--cloud_to_prod_auth',
442 default=False,
443 action='store_const',
444 const=True,
445 help='Run cloud_to_prod_auth tests.')
Jan Tattermusch8266c672015-09-17 09:18:03 -0700446argp.add_argument('-s', '--server',
447 choices=['all'] + sorted(_SERVERS),
448 action='append',
449 help='Run cloud_to_cloud servers in a separate docker ' +
450 'image. Servers can only be started automatically if ' +
451 '--use_docker option is enabled.',
452 default=[])
453argp.add_argument('--override_server',
454 action='append',
455 type=lambda kv: kv.split("="),
456 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
457 default=[])
458argp.add_argument('-t', '--travis',
459 default=False,
460 action='store_const',
461 const=True)
462argp.add_argument('--use_docker',
463 default=False,
464 action='store_const',
465 const=True,
466 help='Run all the interop tests under docker. That provides ' +
467 'additional isolation and prevents the need to install ' +
468 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700469argp.add_argument('--allow_flakes',
470 default=False,
471 action='store_const',
472 const=True,
473 help="Allow flaky tests to show as passing (re-runs failed tests up to five times)")
Jan Tattermusch320bd612015-09-15 12:44:35 -0700474args = argp.parse_args()
475
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700476servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700477 if x == 'all' else [x]
478 for x in args.server))
479
480if args.use_docker:
481 if not args.travis:
482 print 'Seen --use_docker flag, will run interop tests under docker.'
483 print
484 print 'IMPORTANT: The changes you are testing need to be locally committed'
485 print 'because only the committed changes in the current branch will be'
486 print 'copied to the docker environment.'
487 time.sleep(5)
488
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700489if not args.use_docker and servers:
490 print "Running interop servers is only supported with --use_docker option enabled."
491 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700492
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700493languages = set(_LANGUAGES[l]
494 for l in itertools.chain.from_iterable(
495 _LANGUAGES.iterkeys() if x == 'all' else [x]
496 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700497
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700498docker_images={}
499if args.use_docker:
500 # languages for which to build docker images
501 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
502 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700503
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700504 build_jobs = []
505 for l in languages_to_build:
506 job = build_interop_image_jobspec(l)
507 docker_images[str(l)] = job.tag
508 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700509
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700510 if build_jobs:
511 jobset.message('START', 'Building interop docker images.', do_newline=True)
512 if jobset.run(build_jobs, newline_on_success=True, maxjobs=args.jobs):
513 jobset.message('SUCCESS', 'All docker images built successfully.', do_newline=True)
514 else:
515 jobset.message('FAILED', 'Failed to build interop docker images.', do_newline=True)
516 for image in docker_images.itervalues():
517 dockerjob.remove_image(image, skip_nonexistent=True)
518 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700519
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700520# Start interop servers.
521server_jobs={}
522server_addresses={}
523try:
524 for s in servers:
525 lang = str(s)
526 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
527 job = dockerjob.DockerJob(spec)
528 server_jobs[lang] = job
529 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700530
Jan Tattermusch210a0ea2015-10-02 15:05:36 -0700531
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700532 jobs = []
533 if args.cloud_to_prod:
534 for language in languages:
535 for test_case in _TEST_CASES:
536 test_job = cloud_to_prod_jobspec(language, test_case,
537 docker_image=docker_images.get(str(language)))
538 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700539
Jan Tattermuschfb8c77d2015-10-06 09:33:37 -0700540 if args.cloud_to_prod_auth:
541 for language in languages:
542 for test_case in _AUTH_TEST_CASES:
543 test_job = cloud_to_prod_jobspec(language, test_case,
544 docker_image=docker_images.get(str(language)),
545 auth=True)
546 jobs.append(test_job)
547
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700548 for server in args.override_server:
549 server_name = server[0]
550 (server_host, server_port) = server[1].split(':')
551 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700552
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700553 for server_name, server_address in server_addresses.iteritems():
554 (server_host, server_port) = server_address
555 for language in languages:
556 for test_case in _TEST_CASES:
557 test_job = cloud_to_cloud_jobspec(language,
558 test_case,
559 server_name,
560 server_host,
561 server_port,
562 docker_image=docker_images.get(str(language)))
563 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700564
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700565 if not jobs:
566 print "No jobs to run."
567 for image in docker_images.itervalues():
568 dockerjob.remove_image(image, skip_nonexistent=True)
569 sys.exit(1)
570
571 root = ET.Element('testsuites')
572 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
573
574 if jobset.run(jobs, newline_on_success=True, maxjobs=args.jobs, xml_report=testsuite):
575 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
576 else:
577 jobset.message('FAILED', 'Some tests failed', do_newline=True)
578
579 tree = ET.ElementTree(root)
580 tree.write('report.xml', encoding='UTF-8')
581
582finally:
583 # Check if servers are still running.
584 for server, job in server_jobs.iteritems():
585 if not job.is_running():
586 print 'Server "%s" has exited prematurely.' % server
587
588 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
589
590 for image in docker_images.itervalues():
591 print 'Removing docker image %s' % image
592 dockerjob.remove_image(image)