|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright 2021 The gRPC Authors
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
# This script generates a load test configuration template from a collection of
|
|
|
|
# load test configurations.
|
|
|
|
#
|
|
|
|
# Configuration templates contain client and server configurations for multiple
|
|
|
|
# languages, and may contain template substitution keys. These templates are
|
|
|
|
# used to generate load test configurations by selecting clients and servers for
|
|
|
|
# the required languages. The source files for template generation may be load
|
|
|
|
# test configurations or load test configuration templates. Load test
|
|
|
|
# configuration generation is performed by loadtest_config.py. See documentation
|
|
|
|
# below:
|
|
|
|
# https://github.com/grpc/grpc/blob/master/tools/run_tests/performance/README.md
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from typing import Any, Dict, Iterable, Mapping, Type
|
|
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
import loadtest_config
|
|
|
|
|
|
|
|
TEMPLATE_FILE_HEADER_COMMENT = """
|
|
|
|
# Template generated from load test configurations by loadtest_template.py.
|
|
|
|
#
|
|
|
|
# Configuration templates contain client and server configurations for multiple
|
|
|
|
# languages, and may contain template substitution keys. These templates are
|
|
|
|
# used to generate load test configurations by selecting clients and servers for
|
|
|
|
# the required languages. The source files for template generation may be load
|
|
|
|
# test configurations or load test configuration templates. Load test
|
|
|
|
# configuration generation is performed by loadtest_config.py. See documentation
|
|
|
|
# below:
|
|
|
|
# https://github.com/grpc/grpc/blob/master/tools/run_tests/performance/README.md
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def loadtest_template(
|
|
|
|
input_file_names: Iterable[str],
|
|
|
|
metadata: Mapping[str, Any],
|
|
|
|
inject_client_pool: bool,
|
|
|
|
inject_driver_image: bool,
|
|
|
|
inject_driver_pool: bool,
|
|
|
|
inject_server_pool: bool,
|
|
|
|
inject_big_query_table: bool,
|
|
|
|
inject_timeout_seconds: bool,
|
|
|
|
inject_ttl_seconds: bool) -> Dict[str, Any]: # yapf: disable
|
|
|
|
"""Generates the load test template."""
|
|
|
|
clients = list()
|
|
|
|
servers = list()
|
|
|
|
spec = dict()
|
|
|
|
client_languages = set()
|
|
|
|
server_languages = set()
|
|
|
|
template = {
|
|
|
|
'apiVersion': 'e2etest.grpc.io/v1',
|
|
|
|
'kind': 'LoadTest',
|
|
|
|
'metadata': metadata,
|
|
|
|
}
|
|
|
|
for input_file_name in input_file_names:
|
|
|
|
with open(input_file_name) as f:
|
|
|
|
input_config = yaml.safe_load(f.read())
|
|
|
|
|
|
|
|
if input_config.get('apiVersion') != template['apiVersion']:
|
|
|
|
raise ValueError('Unexpected api version in file {}: {}'.format(
|
|
|
|
input_file_name, input_config.get('apiVersion')))
|
|
|
|
if input_config.get('kind') != template['kind']:
|
|
|
|
raise ValueError('Unexpected kind in file {}: {}'.format(
|
|
|
|
input_file_name, input_config.get('kind')))
|
|
|
|
|
|
|
|
for client in input_config['spec']['clients']:
|
|
|
|
if client['language'] in client_languages:
|
|
|
|
continue
|
|
|
|
if inject_client_pool:
|
|
|
|
client['pool'] = '${client_pool}'
|
|
|
|
clients.append(client)
|
|
|
|
client_languages.add(client['language'])
|
|
|
|
|
|
|
|
for server in input_config['spec']['servers']:
|
|
|
|
if server['language'] in server_languages:
|
|
|
|
continue
|
|
|
|
if inject_server_pool:
|
|
|
|
server['pool'] = '${server_pool}'
|
|
|
|
servers.append(server)
|
|
|
|
server_languages.add(server['language'])
|
|
|
|
|
|
|
|
input_spec = input_config['spec']
|
|
|
|
del input_spec['clients']
|
|
|
|
del input_spec['servers']
|
|
|
|
del input_spec['scenariosJSON']
|
|
|
|
spec.update(input_config['spec'])
|
|
|
|
|
|
|
|
clients.sort(key=lambda x: x['language'])
|
|
|
|
servers.sort(key=lambda x: x['language'])
|
|
|
|
|
|
|
|
spec.update({
|
|
|
|
'clients': clients,
|
|
|
|
'servers': servers,
|
|
|
|
})
|
|
|
|
|
|
|
|
if inject_driver_image or inject_driver_pool:
|
|
|
|
driver = dict({"language": "cxx"})
|
|
|
|
if inject_driver_image:
|
|
|
|
driver['image'] = "${driver_image}"
|
|
|
|
if inject_driver_pool:
|
|
|
|
driver['pool'] = "${driver_pool}"
|
|
|
|
spec['drivers'] = [driver]
|
|
|
|
|
|
|
|
if inject_big_query_table:
|
|
|
|
if 'results' not in spec:
|
|
|
|
spec['results'] = dict()
|
|
|
|
spec['results']['bigQueryTable'] = '${big_query_table}'
|
|
|
|
if inject_timeout_seconds:
|
|
|
|
spec['timeoutSeconds'] = '${timeout_seconds}'
|
|
|
|
if inject_ttl_seconds:
|
|
|
|
spec['ttlSeconds'] = '${ttl_seconds}'
|
|
|
|
|
|
|
|
template['spec'] = spec
|
|
|
|
|
|
|
|
return template
|
|
|
|
|
|
|
|
|
|
|
|
def template_dumper(header_comment: str) -> Type[yaml.SafeDumper]:
|
|
|
|
"""Returns a custom dumper to dump templates in the expected format."""
|
|
|
|
|
|
|
|
class TemplateDumper(yaml.SafeDumper):
|
|
|
|
|
|
|
|
def expect_stream_start(self):
|
|
|
|
super().expect_stream_start()
|
|
|
|
if isinstance(self.event, yaml.StreamStartEvent):
|
|
|
|
self.write_indent()
|
|
|
|
self.write_indicator(header_comment, need_whitespace=False)
|
|
|
|
|
|
|
|
def expect_block_sequence(self):
|
|
|
|
super().expect_block_sequence()
|
|
|
|
self.increase_indent()
|
|
|
|
|
|
|
|
def expect_block_sequence_item(self, first=False):
|
|
|
|
if isinstance(self.event, yaml.SequenceEndEvent):
|
|
|
|
self.indent = self.indents.pop()
|
|
|
|
super().expect_block_sequence_item(first)
|
|
|
|
|
|
|
|
return TemplateDumper
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
argp = argparse.ArgumentParser(
|
|
|
|
description='Creates a load test config generator template.',
|
|
|
|
fromfile_prefix_chars='@')
|
|
|
|
argp.add_argument('-i',
|
|
|
|
'--inputs',
|
|
|
|
action='extend',
|
|
|
|
nargs='+',
|
|
|
|
type=str,
|
|
|
|
help='Input files.')
|
|
|
|
argp.add_argument('-o',
|
|
|
|
'--output',
|
|
|
|
type=str,
|
|
|
|
help='Output file. Outputs to stdout if not set.')
|
|
|
|
argp.add_argument(
|
|
|
|
'--inject_client_pool',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.client(s).pool values to \'${client_pool}\'.')
|
|
|
|
argp.add_argument(
|
|
|
|
'--inject_driver_image',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.driver(s).image values to \'${driver_image}\'.')
|
|
|
|
argp.add_argument(
|
|
|
|
'--inject_driver_pool',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.driver(s).pool values to \'${driver_pool}\'.')
|
|
|
|
argp.add_argument(
|
|
|
|
'--inject_server_pool',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.server(s).pool values to \'${server_pool}\'.')
|
|
|
|
argp.add_argument(
|
|
|
|
'--inject_big_query_table',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.results.bigQueryTable to \'${big_query_table}\'.')
|
|
|
|
argp.add_argument('--inject_timeout_seconds',
|
|
|
|
action='store_true',
|
|
|
|
help='Set spec.timeoutSeconds to \'${timeout_seconds}\'.')
|
|
|
|
argp.add_argument('--inject_ttl_seconds',
|
|
|
|
action='store_true',
|
|
|
|
help='Set timeout ')
|
|
|
|
argp.add_argument('-n',
|
|
|
|
'--name',
|
|
|
|
default='',
|
|
|
|
type=str,
|
|
|
|
help='metadata.name.')
|
|
|
|
argp.add_argument('-a',
|
|
|
|
'--annotation',
|
|
|
|
action='append',
|
|
|
|
type=str,
|
|
|
|
help='metadata.annotation(s), in the form key=value.',
|
|
|
|
dest='annotations')
|
|
|
|
args = argp.parse_args()
|
|
|
|
|
|
|
|
annotations = loadtest_config.parse_key_value_args(args.annotations)
|
|
|
|
|
|
|
|
metadata = {'name': args.name}
|
|
|
|
if annotations:
|
|
|
|
metadata['annotations'] = annotations
|
|
|
|
|
|
|
|
template = loadtest_template(
|
|
|
|
input_file_names=args.inputs,
|
|
|
|
metadata=metadata,
|
|
|
|
inject_client_pool=args.inject_client_pool,
|
|
|
|
inject_driver_image=args.inject_driver_image,
|
|
|
|
inject_driver_pool=args.inject_driver_pool,
|
|
|
|
inject_server_pool=args.inject_server_pool,
|
|
|
|
inject_big_query_table=args.inject_big_query_table,
|
|
|
|
inject_timeout_seconds=args.inject_timeout_seconds,
|
|
|
|
inject_ttl_seconds=args.inject_ttl_seconds)
|
|
|
|
|
|
|
|
with open(args.output, 'w') if args.output else sys.stdout as f:
|
|
|
|
yaml.dump(template,
|
|
|
|
stream=f,
|
|
|
|
Dumper=template_dumper(TEMPLATE_FILE_HEADER_COMMENT.strip()),
|
|
|
|
default_flow_style=False)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|