Imported Upstream version 1.22.0
[platform/upstream/grpc.git] / tools / interop_matrix / run_interop_matrix_tests.py
1 #!/usr/bin/env python2.7
2 # Copyright 2017 gRPC authors.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Run tests using docker images in Google Container Registry per matrix."""
16
17 from __future__ import print_function
18
19 import argparse
20 import atexit
21 import json
22 import multiprocessing
23 import os
24 import re
25 import subprocess
26 import sys
27 import uuid
28
29 # Language Runtime Matrix
30 import client_matrix
31
32 python_util_dir = os.path.abspath(
33     os.path.join(os.path.dirname(__file__), '../run_tests/python_utils'))
34 sys.path.append(python_util_dir)
35 import dockerjob
36 import jobset
37 import report_utils
38 import upload_test_results
39
40 _TEST_TIMEOUT_SECONDS = 60
41 _PULL_IMAGE_TIMEOUT_SECONDS = 15 * 60
42 _MAX_PARALLEL_DOWNLOADS = 6
43 _LANGUAGES = client_matrix.LANG_RUNTIME_MATRIX.keys()
44 # All gRPC release tags, flattened, deduped and sorted.
45 _RELEASES = sorted(
46     list(
47         set(release
48             for release_dict in client_matrix.LANG_RELEASE_MATRIX.values()
49             for release in release_dict.keys())))
50
51 argp = argparse.ArgumentParser(description='Run interop tests.')
52 argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
53 argp.add_argument(
54     '--gcr_path',
55     default='gcr.io/grpc-testing',
56     help='Path of docker images in Google Container Registry')
57 argp.add_argument(
58     '--release',
59     default='all',
60     choices=['all'] + _RELEASES,
61     help='Release tags to test.  When testing all '
62     'releases defined in client_matrix.py, use "all".')
63 argp.add_argument(
64     '-l',
65     '--language',
66     choices=['all'] + sorted(_LANGUAGES),
67     nargs='+',
68     default=['all'],
69     help='Languages to test')
70 argp.add_argument(
71     '--keep',
72     action='store_true',
73     help='keep the created local images after finishing the tests.')
74 argp.add_argument(
75     '--report_file', default='report.xml', help='The result file to create.')
76 argp.add_argument(
77     '--allow_flakes',
78     default=False,
79     action='store_const',
80     const=True,
81     help=('Allow flaky tests to show as passing (re-runs failed '
82           'tests up to five times)'))
83 argp.add_argument(
84     '--bq_result_table',
85     default='',
86     type=str,
87     nargs='?',
88     help='Upload test results to a specified BQ table.')
89 # Requests will be routed through specified VIP by default.
90 # See go/grpc-interop-tests (internal-only) for details.
91 argp.add_argument(
92     '--server_host',
93     default='74.125.206.210',
94     type=str,
95     nargs='?',
96     help='The gateway to backend services.')
97
98
99 def _get_test_images_for_lang(lang, release_arg, image_path_prefix):
100     """Find docker images for a language across releases and runtimes.
101
102   Returns dictionary of list of (<tag>, <image-full-path>) keyed by runtime.
103   """
104     if release_arg == 'all':
105         # Use all defined releases for given language
106         releases = client_matrix.get_release_tags(lang)
107     else:
108         # Look for a particular release.
109         if release_arg not in client_matrix.get_release_tags(lang):
110             jobset.message(
111                 'SKIPPED',
112                 'release %s for %s is not defined' % (release_arg, lang),
113                 do_newline=True)
114             return {}
115         releases = [release_arg]
116
117     # Image tuples keyed by runtime.
118     images = {}
119     for tag in releases:
120         for runtime in client_matrix.get_runtimes_for_lang_release(lang, tag):
121             image_name = '%s/grpc_interop_%s:%s' % (image_path_prefix, runtime,
122                                                     tag)
123             image_tuple = (tag, image_name)
124
125             if not images.has_key(runtime):
126                 images[runtime] = []
127             images[runtime].append(image_tuple)
128     return images
129
130
131 def _read_test_cases_file(lang, runtime, release):
132     """Read test cases from a bash-like file and return a list of commands"""
133     # Check to see if we need to use a particular version of test cases.
134     release_info = client_matrix.LANG_RELEASE_MATRIX[lang].get(release)
135     if release_info:
136         testcases_file = release_info.testcases_file
137     if not testcases_file:
138         # TODO(jtattermusch): remove the double-underscore, it is pointless
139         testcases_file = '%s__master' % lang
140
141     # For csharp, the testcases file used depends on the runtime
142     # TODO(jtattermusch): remove this odd specialcase
143     if lang == 'csharp' and runtime == 'csharpcoreclr':
144         testcases_file = testcases_file.replace('csharp_', 'csharpcoreclr_')
145
146     testcases_filepath = os.path.join(
147         os.path.dirname(__file__), 'testcases', testcases_file)
148     lines = []
149     with open(testcases_filepath) as f:
150         for line in f.readlines():
151             line = re.sub('\\#.*$', '', line)  # remove hash comments
152             line = line.strip()
153             if line and not line.startswith('echo'):
154                 # Each non-empty line is a treated as a test case command
155                 lines.append(line)
156     return lines
157
158
159 def _cleanup_docker_image(image):
160     jobset.message('START', 'Cleanup docker image %s' % image, do_newline=True)
161     dockerjob.remove_image(image, skip_nonexistent=True)
162
163
164 args = argp.parse_args()
165
166
167 # caches test cases (list of JobSpec) loaded from file.  Keyed by lang and runtime.
168 def _generate_test_case_jobspecs(lang, runtime, release, suite_name):
169     """Returns the list of test cases from testcase files per lang/release."""
170     testcase_lines = _read_test_cases_file(lang, runtime, release)
171
172     job_spec_list = []
173     for line in testcase_lines:
174         # TODO(jtattermusch): revisit the logic for updating test case commands
175         # what it currently being done seems fragile.
176
177         # Extract test case name from the command line
178         m = re.search(r'--test_case=(\w+)', line)
179         testcase_name = m.group(1) if m else 'unknown_test'
180
181         # Extract the server name from the command line
182         if '--server_host_override=' in line:
183             m = re.search(
184                 r'--server_host_override=((.*).sandbox.googleapis.com)', line)
185         else:
186             m = re.search(r'--server_host=((.*).sandbox.googleapis.com)', line)
187         server = m.group(1) if m else 'unknown_server'
188         server_short = m.group(2) if m else 'unknown_server'
189
190         # replace original server_host argument
191         assert '--server_host=' in line
192         line = re.sub(r'--server_host=[^ ]*',
193                       r'--server_host=%s' % args.server_host, line)
194
195         # some interop tests don't set server_host_override (see #17407),
196         # but we need to use it if different host is set via cmdline args.
197         if args.server_host != server and not '--server_host_override=' in line:
198             line = re.sub(r'(--server_host=[^ ]*)',
199                           r'\1 --server_host_override=%s' % server, line)
200
201         spec = jobset.JobSpec(
202             cmdline=line,
203             shortname='%s:%s:%s:%s' % (suite_name, lang, server_short,
204                                        testcase_name),
205             timeout_seconds=_TEST_TIMEOUT_SECONDS,
206             shell=True,
207             flake_retries=5 if args.allow_flakes else 0)
208         job_spec_list.append(spec)
209     return job_spec_list
210
211
212 def _pull_images_for_lang(lang, images):
213     """Pull all images for given lang from container registry."""
214     jobset.message(
215         'START', 'Downloading images for language "%s"' % lang, do_newline=True)
216     download_specs = []
217     for release, image in images:
218         # Pull the image and warm it up.
219         # First time we use an image with "docker run", it takes time to unpack
220         # the image and later this delay would fail our test cases.
221         cmdline = [
222             'time gcloud docker -- pull %s && time docker run --rm=true %s /bin/true'
223             % (image, image)
224         ]
225         spec = jobset.JobSpec(
226             cmdline=cmdline,
227             shortname='pull_image_%s' % (image),
228             timeout_seconds=_PULL_IMAGE_TIMEOUT_SECONDS,
229             shell=True,
230             flake_retries=2)
231         download_specs.append(spec)
232     # too many image downloads at once tend to get stuck
233     max_pull_jobs = min(args.jobs, _MAX_PARALLEL_DOWNLOADS)
234     num_failures, resultset = jobset.run(
235         download_specs, newline_on_success=True, maxjobs=max_pull_jobs)
236     if num_failures:
237         jobset.message(
238             'FAILED', 'Failed to download some images', do_newline=True)
239         return False
240     else:
241         jobset.message(
242             'SUCCESS', 'All images downloaded successfully.', do_newline=True)
243         return True
244
245
246 def _run_tests_for_lang(lang, runtime, images, xml_report_tree):
247     """Find and run all test cases for a language.
248
249   images is a list of (<release-tag>, <image-full-path>) tuple.
250   """
251     skip_tests = False
252     if not _pull_images_for_lang(lang, images):
253         jobset.message(
254             'FAILED',
255             'Image download failed. Skipping tests for language "%s"' % lang,
256             do_newline=True)
257         skip_tests = True
258
259     total_num_failures = 0
260     for release, image in images:
261         suite_name = '%s__%s_%s' % (lang, runtime, release)
262         job_spec_list = _generate_test_case_jobspecs(lang, runtime, release,
263                                                      suite_name)
264
265         if not job_spec_list:
266             jobset.message(
267                 'FAILED', 'No test cases were found.', do_newline=True)
268             total_num_failures += 1
269             continue
270
271         num_failures, resultset = jobset.run(
272             job_spec_list,
273             newline_on_success=True,
274             add_env={'docker_image': image},
275             maxjobs=args.jobs,
276             skip_jobs=skip_tests)
277         if args.bq_result_table and resultset:
278             upload_test_results.upload_interop_results_to_bq(
279                 resultset, args.bq_result_table)
280         if skip_tests:
281             jobset.message('FAILED', 'Tests were skipped', do_newline=True)
282             total_num_failures += 1
283         elif num_failures:
284             jobset.message('FAILED', 'Some tests failed', do_newline=True)
285             total_num_failures += num_failures
286         else:
287             jobset.message('SUCCESS', 'All tests passed', do_newline=True)
288
289         report_utils.append_junit_xml_results(xml_report_tree, resultset,
290                                               'grpc_interop_matrix', suite_name,
291                                               str(uuid.uuid4()))
292
293     # cleanup all downloaded docker images
294     for _, image in images:
295         if not args.keep:
296             _cleanup_docker_image(image)
297
298     return total_num_failures
299
300
301 languages = args.language if args.language != ['all'] else _LANGUAGES
302 total_num_failures = 0
303 _xml_report_tree = report_utils.new_junit_xml_tree()
304 for lang in languages:
305     docker_images = _get_test_images_for_lang(lang, args.release, args.gcr_path)
306     for runtime in sorted(docker_images.keys()):
307         total_num_failures += _run_tests_for_lang(
308             lang, runtime, docker_images[runtime], _xml_report_tree)
309
310 report_utils.create_xml_report_file(_xml_report_tree, args.report_file)
311
312 if total_num_failures:
313     sys.exit(1)
314 sys.exit(0)