blob: a689876972a5e857fe8ba63270f67c923f785560 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2016 The Fuchsia 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.
"""The Cobalt build system command-line interface."""
from __future__ import print_function
import argparse
import filecmp
import fileinput
import json
import logging
import os
import shutil
import string
import subprocess
import sys
import tempfile
import tools.container_util as container_util
import tools.cpplint as cpplint
import tools.golint as golint
import tools.process_starter as process_starter
import tools.test_runner as test_runner
import tools.production_util as production_util
import tools.gitfmt as gitfmt
from tools.test_runner import E2E_TEST_ANALYZER_PUBLIC_KEY_PEM
from tools.test_runner import E2E_TEST_SHUFFLER_PUBLIC_KEY_PEM
from tools.process_starter import DEFAULT_ANALYZER_PRIVATE_KEY_PEM
from tools.process_starter import DEFAULT_ANALYZER_PUBLIC_KEY_PEM
from tools.process_starter import DEFAULT_SHUFFLER_PRIVATE_KEY_PEM
from tools.process_starter import DEFAULT_SHUFFLER_PUBLIC_KEY_PEM
from tools.process_starter import DEFAULT_ANALYZER_SERVICE_PORT
from tools.process_starter import DEFAULT_SHUFFLER_PORT
from tools.process_starter import DEFAULT_REPORT_MASTER_PORT
from tools.process_starter import DEMO_CONFIG_DIR
from tools.process_starter import LOCALHOST_TLS_CERT_FILE
from tools.process_starter import LOCALHOST_TLS_KEY_FILE
from tools.process_starter import PRODUCTION_CONFIG_DIR
from tools.process_starter import SHUFFLER_DEMO_CONFIG_FILE
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
OUT_DIR = os.path.abspath(os.path.join(THIS_DIR, 'out'))
SYSROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, 'sysroot'))
PERSONAL_BT_ADMIN_SERVICE_ACCOUNT_CREDENTIALS_FILE = os.path.join(
THIS_DIR, 'personal_bt_admin_service_account.json')
PERSONAL_CLUSTER_JSON_FILE = os.path.join(THIS_DIR, 'personal_cluster.json')
BIGTABLE_TOOL_PATH = \
os.path.join(OUT_DIR, 'tools', 'bigtable_tool', 'bigtable_tool')
OPEN_SSL_CONFIG_FILE = os.path.join(THIS_DIR, 'self-signed-with-ip.cnf')
GRPC_PEM_ROOTS = os.path.join(THIS_DIR, 'third_party', 'grpc', 'etc',
'roots.pem')
CONFIG_SUBMODULE_PATH = os.path.join(THIS_DIR, 'third_party', 'config')
CONFIG_BINARY_PROTO = os.path.join(OUT_DIR, 'third_party', 'config',
'cobalt_config.binproto')
_logger = logging.getLogger()
_verbose_count = 0
# In Python3, the `raw_input` Built-in function was renamed to `input`.
try:
input = raw_input
except NameError:
pass
def _initLogging(verbose_count):
"""Ensures that the logger (obtained via logging.getLogger(), as usual) is
initialized, with the log level set as appropriate for |verbose_count|
instances of --verbose on the command line.
"""
assert (verbose_count >= 0)
if verbose_count == 0:
level = logging.WARNING
elif verbose_count == 1:
level = logging.INFO
else: # verbose_count >= 2
level = logging.DEBUG
logging.basicConfig(format='%(relativeCreated).3f:%(levelname)s:%(message)s')
logger = logging.getLogger()
logger.setLevel(level)
logger.debug('Initialized logging: verbose_count=%d, level=%d' %
(verbose_count, level))
def ensureDir(dir_path):
"""Ensures that the directory at |dir_path| exists.
If not it is created.
Args:
dir_path{string}: The path to a directory. If it does not exist it will be
created.
"""
if not os.path.exists(dir_path):
os.makedirs(dir_path)
def _compound_project_name(args):
""" Builds a compound project name such as google.com:my-project
Args:
args: A namespace object as returned from the parse_args() function. It must
have one argument named "cloud_project_prefix" and one named
"cloud_project_name."
"""
return container_util.compound_project_name(args.cloud_project_prefix,
args.cloud_project_name)
def _setup(args):
subprocess.check_call(['git', 'submodule', 'init'])
subprocess.check_call(['git', 'submodule', 'update'])
subprocess.check_call(['./setup.sh'])
def _update_config(args):
savedDir = os.getcwd()
try:
os.chdir(CONFIG_SUBMODULE_PATH)
subprocess.check_call(['git', 'pull', 'origin', 'master'])
finally:
os.chdir(savedDir)
def _build(args):
ensureDir(OUT_DIR)
savedir = os.getcwd()
os.chdir(OUT_DIR)
subprocess.check_call([args.cmake_path, '-G', 'Ninja', '..'])
subprocess.check_call([args.ninja_path])
os.chdir(savedir)
def _check_config(args):
config_parser_bin = os.path.join(OUT_DIR, 'config', 'config_parser',
'config_parser')
if not os.path.isfile(config_parser_bin):
print(
'%s could not be found. Run \n\n%s setup\n%s build\n\nand try again.' %
(config_parser_bin, sys.argv[0], sys.argv[0]))
return
if not _check_test_configs(args):
return
subprocess.check_call(
[config_parser_bin, '-config_dir', args.config_dir, '-check_only'])
def _check_test_configs(args):
testapp_config_path = os.path.join(args.config_dir, 'fuchsia', 'test_app2',
'config.yaml')
if not _check_config_exists(testapp_config_path):
return False
prober_config_path = os.path.join(args.config_dir, 'fuchsia', 'prober',
'config.yaml')
if not _check_config_exists(prober_config_path):
print('Run this command and try again: ./cobaltb.py write_prober_config')
return False
_, tmp_path = tempfile.mkstemp()
_make_prober_config(testapp_config_path, tmp_path)
is_same_file = filecmp.cmp(tmp_path, prober_config_path)
os.remove(tmp_path)
if not is_same_file:
print('Testapp config and prober config should be identical except for '
'names of custom metrics output log types.\n'
'Run this command and try again: ./cobaltb.py write_prober_config')
return is_same_file
def _write_prober_config(args):
testapp_config_path = os.path.join(args.config_dir, 'fuchsia', 'test_app2',
'config.yaml')
if not _check_config_exists(testapp_config_path):
return False
prober_config_path = os.path.join(args.config_dir, 'fuchsia', 'prober',
'config.yaml')
if os.path.isfile(prober_config_path):
print('This action will overwrite the file %s.' % prober_config_path)
answer = input('Continue anyway? (y/N) ')
if not _parse_bool(answer):
return
prober_dir = os.path.dirname(prober_config_path)
if not os.path.exists(prober_dir):
os.makedirs(prober_dir)
_make_prober_config(testapp_config_path, prober_config_path)
def _check_config_exists(config_path):
if not os.path.isfile(config_path):
print('Expected config at path %s' % config_path)
return False
return True
def _make_prober_config(testapp_config_path, output_path):
testapp_custom_log_source = 'processed/<team_name>-cobalt-dev:custom-proto-test'
prober_custom_log_source = 'processed/<team_name>-cobalt-dev:custom-proto-prober-test'
with open(testapp_config_path, 'r') as f:
testapp_config = f.read()
with open(output_path, 'w') as f:
f.write(
testapp_config.replace(testapp_custom_log_source,
prober_custom_log_source))
def _fmt(args):
gitfmt.fmt(args.committed)
def _lint(args):
status = 0
status += cpplint.main()
status += golint.main()
exit(status)
# Specifiers of subsets of tests to run
TEST_FILTERS = [
'all', 'gtests', 'nogtests', 'gotests', 'nogotests', 'btemulator',
'nobtemulator', 'e2e', 'noe2e', 'cloud_bt', 'perf', 'config_parser'
]
# Returns 0 if all tests pass, otherwise returns 1. Prints a failure or success
# message.
def _test(args):
# A map from positive filter specifiers to the list of test directories
# it represents. Note that 'cloud_bt' and 'perf' tests are special. They are
# not included in 'all'. They are only run if asked for explicitly.
FILTER_MAP = {
'all': [
'gtests', 'go_tests', 'gtests_btemulator', 'e2e_tests',
'config_parser_tests'
],
'gtests': ['gtests'],
'gotests': ['go_tests'],
'btemulator': ['gtests_btemulator'],
'e2e': ['e2e_tests'],
'cloud_bt': ['gtests_cloud_bt'],
'perf': ['perf_tests'],
'config_parser': ['config_parser_tests']
}
# A list of test directories for which the contained tests assume the
# existence of a running instance of the Bigtable Emulator.
NEEDS_BT_EMULATOR = ['gtests_btemulator', 'e2e_tests']
# A list of test directories for which the contained tests assume the
# existence of a running instance of the Cobalt processes (Shuffler,
# Analyzer Service, Report Master.)
NEEDS_COBALT_PROCESSES = ['e2e_tests']
# By default try each test just once.
num_times_to_try = 1
# Get the list of test directories we should run.
if args.tests.startswith('no'):
test_dirs = [
test_dir for test_dir in FILTER_MAP['all']
if test_dir not in FILTER_MAP[args.tests[2:]]
]
else:
test_dirs = FILTER_MAP[args.tests]
failure_list = []
print('Will run tests in the following directories: %s.' %
', '.join(test_dirs))
bigtable_project_name = ''
bigtable_instance_id = ''
for test_dir in test_dirs:
start_bt_emulator = ((test_dir in NEEDS_BT_EMULATOR) and
not args.use_cloud_bt and
not args.cobalt_on_personal_cluster and
not args.production_dir)
start_cobalt_processes = ((test_dir in NEEDS_COBALT_PROCESSES) and
not args.cobalt_on_personal_cluster and
not args.production_dir)
test_args = None
if (test_dir == 'gtests_cloud_bt'):
if not os.path.exists(bt_admin_service_account_credentials_file):
print('You must first create the file %s.' %
bt_admin_service_account_credentials_file)
print('See the instructions in README.md.')
return
bigtable_project_name_from_args = _compound_project_name(args)
if bigtable_project_name_from_args == '':
print('--cloud_project_name must be specified')
failure_list.append('gtests_cloud_bt')
break
if args.bigtable_instance_id == '':
print('--bigtable_instance_id must be specified')
failure_list.append('gtests_cloud_bt')
break
test_args = [
'--bigtable_project_name=%s' % bigtable_project_name_from_args,
'--bigtable_instance_id=%s' % args.bigtable_instance_id
]
bigtable_project_name = bigtable_project_name_from_args
bigtable_instance_id = args.bigtable_instance_id
if (test_dir == 'gtests_btemulator' and args.tests != 'btemulator'):
# TODO(rudominer) At the moment the EnableReportScheduling test case is
# flaky so I am disabling it. If my analysis is correct the flakiness is
# due to a bug in the bigtable emulator.
# https://github.com/GoogleCloudPlatform/google-cloud-go/issues/826
# We disalbe the flaky test in all cases except when explicitly running
# only the bt emulator tests as requested via the flag --tests=btemulator.
test_args = ['--gtest_filter=-*EnableReportScheduling*']
if (test_dir == 'e2e_tests'):
# The end-to-end test is naturally flaky. Our strategy is to attempt
# it multiple times.
num_times_to_try = 3
analyzer_pk_pem_file = E2E_TEST_ANALYZER_PUBLIC_KEY_PEM
report_master_uri = 'localhost:%d' % DEFAULT_REPORT_MASTER_PORT
shuffler_pk_pem_file = E2E_TEST_SHUFFLER_PUBLIC_KEY_PEM
shuffler_uri = 'localhost:%d' % DEFAULT_SHUFFLER_PORT
if args.cobalt_on_personal_cluster or args.production_dir:
if args.cobalt_on_personal_cluster and args.production_dir:
print('Do not specify both --production_dir and '
'-cobalt_on_personal_cluster.')
failure_list.append('e2e_tests')
break
public_uris = container_util.get_public_uris(args.cluster_name,
args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone)
report_master_uri = public_uris['report_master']
shuffler_uri = public_uris['shuffler']
if args.use_cloud_bt:
# use_cloud_bt means to use local instances of the Cobalt processes
# connected to a Cloud Bigtable. cobalt_on_personal_cluster means to
# use Cloud instances of the Cobalt processes. These two options
# are inconsistent.
print('Do not specify both --use_cloud_bt and '
'-cobalt_on_personal_cluster or --production_dir.')
failure_list.append('e2e_tests')
break
if args.cobalt_on_personal_cluster:
analyzer_pk_pem_file = DEFAULT_ANALYZER_PUBLIC_KEY_PEM
shuffler_pk_pem_file = DEFAULT_SHUFFLER_PUBLIC_KEY_PEM
elif args.production_dir:
pem_directory = os.path.abspath(args.production_dir)
analyzer_pk_pem_file = os.path.join(pem_directory,
'analyzer_public.pem')
shuffler_pk_pem_file = os.path.join(pem_directory,
'shuffler_public.pem')
report_master_uri = (
args.report_master_preferred_address or report_master_uri)
shuffler_uri = (args.shuffler_preferred_address or shuffler_uri)
test_args = [
'-analyzer_pk_pem_file=%s' % analyzer_pk_pem_file,
'-shuffler_uri=%s' % shuffler_uri,
'-shuffler_pk_pem_file=%s' % shuffler_pk_pem_file,
'-report_master_uri=%s' % report_master_uri,
('-observation_querier_path=%s' %
process_starter.OBSERVATION_QUERIER_PATH),
'-test_app_path=%s' % process_starter.TEST_APP_PATH,
'-config_bin_proto_path=%s' % CONFIG_BINARY_PROTO,
'-sub_process_v=%d' % _verbose_count
]
use_tls = _parse_bool(args.use_tls)
if use_tls:
test_args += [
'-use_tls',
'-report_master_root_certs=%s' % args.report_master_root_certs,
'-shuffler_root_certs=%s' % args.shuffler_root_certs
]
if (not (args.cobalt_on_personal_cluster or args.production_dir)):
test_args += ['-skip_oauth']
if (args.use_cloud_bt or args.cobalt_on_personal_cluster or
args.production_dir):
bigtable_project_name_from_args = _compound_project_name(args)
test_args = test_args + [
'-bigtable_tool_path=%s' % BIGTABLE_TOOL_PATH,
'-bigtable_project_name=%s' % bigtable_project_name_from_args,
'-bigtable_instance_id=%s' % args.bigtable_instance_id,
]
bigtable_project_name = bigtable_project_name_from_args
bigtable_instance_id = args.bigtable_instance_id
if args.production_dir or args.cobalt_on_personal_cluster:
# Currently thresholding in the Shuffler is disabled when we run the Shuffler in either
# production or devel because in both cases we deploy two Shuffler instances but the
# Shuffler thresholding logic was not written to support that. When we run the Shuffler
# locally on the test machine then we still use and test thresholding.
test_args = test_args + [
'-do_shuffler_threshold_test=false',
]
print('********************************************************')
this_failure_list = []
for attempt in range(num_times_to_try):
this_failure_list = test_runner.run_all_tests(
test_dir,
start_bt_emulator=start_bt_emulator,
start_cobalt_processes=start_cobalt_processes,
bigtable_project_name=bigtable_project_name,
bigtable_instance_id=bigtable_instance_id,
verbose_count=_verbose_count,
vmodule=_vmodule,
use_tls=_parse_bool(args.use_tls),
tls_cert_file=args.tls_cert_file,
tls_key_file=args.tls_key_file,
test_args=test_args)
if this_failure_list and attempt < num_times_to_try - 1:
print('')
print('***** Attempt %i of %s failed. Retrying...' %
(attempt, this_failure_list))
print('')
else:
break
if this_failure_list:
failure_list.append('%s (%s)' % (test_dir, this_failure_list))
print('')
if failure_list:
print('******************* SOME TESTS FAILED *******************')
print('failures = %s' % failure_list)
return 1
else:
print('******************* ALL TESTS PASSED *******************')
return 0
# Files and directories in the out directory to NOT delete when doing
# a partial clean.
TO_SKIP_ON_PARTIAL_CLEAN = [
'CMakeFiles', 'third_party', '.ninja_deps', '.ninja_log', 'CMakeCache.txt',
'build.ninja', 'cmake_install.cmake', 'rules.ninja'
]
def _clean(args):
if args.full:
print('Deleting the out directory...')
shutil.rmtree(OUT_DIR, ignore_errors=True)
else:
print('Doing a partial clean. Pass --full for a full clean.')
if not os.path.exists(OUT_DIR):
return
for f in os.listdir(OUT_DIR):
if not f in TO_SKIP_ON_PARTIAL_CLEAN:
full_path = os.path.join(OUT_DIR, f)
if os.path.isfile(full_path):
os.remove(full_path)
else:
shutil.rmtree(full_path, ignore_errors=True)
def _start_bigtable_emulator(args):
process_starter.start_bigtable_emulator()
def _start_shuffler(args):
process_starter.start_shuffler(
port=args.port,
analyzer_uri=args.analyzer_uri,
use_memstore=args.use_memstore,
erase_db=(not args.keep_existing_db),
config_file=args.config_file,
use_tls=_parse_bool(args.use_tls),
tls_cert_file=args.tls_cert_file,
tls_key_file=args.tls_key_file,
# Because it makes the demo more interesting
# we use verbose_count at least 3.
verbose_count=max(3, _verbose_count))
def _start_analyzer_service(args):
bigtable_project_name = ''
bigtable_instance_id = ''
if args.use_cloud_bt:
bigtable_project_name = _compound_project_name(args)
bigtable_instance_id = args.bigtable_instance_id
process_starter.start_analyzer_service(
port=args.port,
bigtable_project_name=bigtable_project_name,
bigtable_instance_id=bigtable_instance_id,
# Because it makes the demo more interesting
# we use verbose_count at least 3.
verbose_count=max(3, _verbose_count))
def _start_report_master(args):
bigtable_project_name = ''
bigtable_instance_id = ''
if args.use_cloud_bt:
bigtable_project_name = _compound_project_name(args)
bigtable_instance_id = args.bigtable_instance_id
process_starter.start_report_master(
port=args.port,
bigtable_project_name=bigtable_project_name,
bigtable_instance_id=bigtable_instance_id,
use_tls=_parse_bool(args.use_tls),
tls_cert_file=args.tls_cert_file,
tls_key_file=args.tls_key_file,
verbose_count=_verbose_count)
def _start_test_app(args):
analyzer_uri = 'localhost:%d' % DEFAULT_ANALYZER_SERVICE_PORT
shuffler_uri = (
args.shuffler_preferred_address or 'localhost:%d' % DEFAULT_SHUFFLER_PORT)
analyzer_public_key_pem = \
DEFAULT_ANALYZER_PUBLIC_KEY_PEM
shuffler_public_key_pem = \
DEFAULT_SHUFFLER_PUBLIC_KEY_PEM
use_tls = _parse_bool(args.use_tls)
if args.production_dir:
pem_directory = os.path.abspath(args.production_dir)
analyzer_public_key_pem = os.path.join(pem_directory, 'analyzer_public.pem')
shuffler_public_key_pem = os.path.join(pem_directory, 'shuffler_public.pem')
if args.cobalt_on_personal_cluster or args.production_dir:
public_uris = container_util.get_public_uris(args.cluster_name,
args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone)
shuffler_uri = args.shuffler_preferred_address or public_uris['shuffler']
process_starter.start_test_app(
shuffler_uri=shuffler_uri,
analyzer_uri=analyzer_uri,
analyzer_pk_pem_file=analyzer_public_key_pem,
use_tls=use_tls,
root_certs_pem_file=args.shuffler_root_certs,
shuffler_pk_pem_file=shuffler_public_key_pem,
project_id=args.project_id,
automatic=args.automatic,
# Because it makes the demo more interesting
# we use verbose_count at least 3.
verbose_count=max(3, _verbose_count))
def _start_report_client(args):
report_master_uri = (
args.report_master_preferred_address or
'localhost:%d' % DEFAULT_REPORT_MASTER_PORT)
if args.cobalt_on_personal_cluster or args.production_dir:
public_uris = container_util.get_public_uris(args.cluster_name,
args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone)
report_master_uri = (
args.report_master_preferred_address or public_uris['report_master'])
process_starter.start_report_client(
report_master_uri=report_master_uri,
use_tls=_parse_bool(args.use_tls),
root_certs_pem_file=args.report_master_root_certs,
project_id=args.project_id,
verbose_count=_verbose_count)
def _start_observation_querier(args):
bigtable_project_name = ''
bigtable_instance_id = ''
if args.use_cloud_bt or args.production_dir:
bigtable_project_name = _compound_project_name(args)
bigtable_instance_id = args.bigtable_instance_id
process_starter.start_observation_querier(
bigtable_project_name=bigtable_project_name,
bigtable_instance_id=bigtable_instance_id,
verbose_count=_verbose_count)
def _generate_keys(args):
path = os.path.join(OUT_DIR, 'tools', 'key_generator', 'key_generator')
subprocess.check_call([path])
def _generate_self_signed_certificate(key_file,
cert_file,
ip_address='127.0.0.1',
hostname='localhost'):
# First we build an openssl config file by replacing two tokens in our
# template file with the IP address.
ip_address_token = '@@@IP_ADDRESS@@@'
hostname_token = '@@@HOSTNAME@@@'
openssl_config_file = os.path.join(OUT_DIR, 'self-signed-with-ip.cnf')
with open(openssl_config_file, 'w+b') as f:
for line in fileinput.input(OPEN_SSL_CONFIG_FILE):
if string.find(line, ip_address_token) != -1:
line = line.replace(ip_address_token, ip_address)
if string.find(line, hostname_token) != -1:
line = line.replace(hostname_token, hostname)
f.write(line)
# then we invoke openssl and point it at the config file we just generated.
subprocess.check_call([
'openssl', 'req', '-x509', '-nodes', '-days', '365', '-config',
openssl_config_file, '-newkey', 'rsa:2048', '-keyout', key_file, '-out',
cert_file
])
def _generate_cert(args):
if not (args.path_to_key and args.path_to_cert):
print('--path-to-key and --path-to-cert are both required.')
return
_generate_self_signed_certificate(args.path_to_key, args.path_to_cert,
args.ip_address, args.hostname)
def _invoke_bigtable_tool(args, command):
if not os.path.exists(bt_admin_service_account_credentials_file):
print('You must first create the file %s.' %
bt_admin_service_account_credentials_file)
print('See the instructions in README.md.')
return
bigtable_project_name_from_args = _compound_project_name(args)
if bigtable_project_name_from_args == '':
print('--cloud_project_name must be specified')
return
if args.bigtable_instance_id == '':
print('--bigtable_instance_id must be specified')
return
cmd = [
BIGTABLE_TOOL_PATH, '-command', command, '-bigtable_project_name',
bigtable_project_name_from_args, '-bigtable_instance_id',
args.bigtable_instance_id
]
if command == 'delete_observations':
if args.customer_id == 0:
print('--customer_id must be specified')
return
if args.project_id == 0:
print('--project_id must be specified')
return
if args.metric_id == 0:
print('--metric_id must be specified')
return
cmd = cmd + [
'-customer',
str(args.customer_id), '-project',
str(args.project_id), '-metric',
str(args.metric_id)
]
elif command == 'delete_reports':
if args.customer_id == 0:
print('--customer_id must be specified')
return
if args.project_id == 0:
print('--project_id must be specified')
return
if args.report_config_id == 0:
print('--report_config_id must be specified')
return
cmd = cmd + [
'-customer',
str(args.customer_id), '-project',
str(args.project_id), '-report_config',
str(args.report_config_id)
]
if args.danger_danger_delete_production_reports:
cmd = cmd + ['-danger_danger_delete_production_reports']
subprocess.check_call(cmd)
def _provision_bigtable(args):
_invoke_bigtable_tool(args, 'create_tables')
def _delete_observations(args):
_invoke_bigtable_tool(args, 'delete_observations')
def _delete_reports(args):
_invoke_bigtable_tool(args, 'delete_reports')
def _deploy_show(args):
container_util.display(args.cloud_project_prefix, args.cloud_project_name,
args.cluster_zone, args.cluster_name)
def _deploy_login(args):
container_util.login(args.cloud_project_prefix, args.cloud_project_name)
def _deploy_authenticate(args):
container_util.authenticate(args.cluster_name, args.cloud_project_prefix,
args.cloud_project_name, args.cluster_zone)
def _deploy_build(args):
if args.production_dir:
print(
'Production configs should be built using `./cobaltb.py deploy '
'production_build` which will build a clean version of the binaries, then '
'build the docker images in one step.')
answer = input('Continue anyway? (y/N) ')
if not _parse_bool(answer):
return
container_util.build_all_docker_images(
shuffler_config_file=args.shuffler_config_file,
cobalt_config_dir=args.cobalt_config_dir)
if not _is_config_up_to_date():
print('Docker image was built using an older config. You can update the '
'config using the `./cobaltb.py update_config` command.')
def _deploy_production_build(args):
if not args.production_dir:
print('Notice that you have not passed the flag --production_dir and so '
'you will be pushing the built containers to your personal devel '
'cluster, not a production cluster.')
answer = input('Continue? (y/N) ')
if not _parse_bool(answer):
return
full_ref = production_util.build_and_push_production_docker_images(
args.cloud_project_name, args.production_dir, args.git_revision)
if full_ref:
print('')
print('')
print('Done pushing the new build. To set this as the default build, copy '
'the following json blob into the versions.json.')
print('')
print('{')
print(' "shuffler": "%s",' % full_ref)
print(' "report-master": "%s",' % full_ref)
print(' "analyzer-service": "%s"' % full_ref)
print('}')
def _deploy_push(args):
if args.job == 'shuffler':
container_util.push_shuffler_to_container_registry(
args.cloud_project_prefix, args.cloud_project_name)
elif args.job == 'analyzer-service':
container_util.push_analyzer_service_to_container_registry(
args.cloud_project_prefix, args.cloud_project_name)
elif args.job == 'report-master':
container_util.push_report_master_to_container_registry(
args.cloud_project_prefix, args.cloud_project_name)
else:
print('Unknown job "%s". I only know how to push "shuffler", '
'"analyzer-service" and "report-master".' % args.job)
def _parse_bool(bool_string):
return bool_string.lower() in ['true', 't', 'y', 'yes', '1']
def _load_versions_file(args):
versions = {
'shuffler': 'latest',
'analyzer-service': 'latest',
'report-master': 'latest',
}
try:
with open(args.deployed_versions_file) as f:
read_versions_file = {}
try:
read_versions_file = json.load(f)
except ValueError:
print('%s could not be parsed.' % args.deployed_versions_file)
for key in read_versions_file:
if key in versions:
versions[key] = read_versions_file[key]
except IOError:
print('%s could not be found.' % args.deployed_versions_file)
return versions
def _deploy_start(args):
version = _load_versions_file(args).get(args.job, 'latest')
components = [c.strip() for c in args.components.split(',')]
if args.job == 'shuffler':
container_util.start_shuffler(
args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name,
args.shuffler_static_ip,
components,
version,
use_memstore=_parse_bool(args.shuffler_use_memstore),
danger_danger_delete_all_data_at_startup=args
.danger_danger_delete_all_data_at_startup)
elif args.job == 'analyzer-service':
if args.bigtable_instance_id == '':
print('--bigtable_instance_id must be specified')
return
container_util.start_analyzer_service(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone, args.cluster_name,
args.bigtable_instance_id,
args.analyzer_service_static_ip,
components, version)
elif args.job == 'report-master':
if args.bigtable_instance_id == '':
print('--bigtable_instance_id must be specified')
return
container_util.start_report_master(
args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name,
args.bigtable_instance_id,
args.report_master_static_ip,
components,
version,
args.report_master_update_repo_url,
enable_report_scheduling=_parse_bool(
args.report_master_enable_scheduling))
else:
print('Unknown job "%s". I only know how to start "shuffler", '
'"analyzer-service" and "report-master".' % args.job)
def _deploy_stop(args):
version = _load_versions_file(args).get(args.job, 'latest')
components = [c.strip() for c in args.components.split(',')]
if args.job == 'shuffler':
container_util.stop_shuffler(args.cloud_project_prefix,
args.cloud_project_name, args.cluster_zone,
args.cluster_name, components, version)
elif args.job == 'analyzer-service':
container_util.stop_analyzer_service(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone, args.cluster_name,
components, version)
elif args.job == 'report-master':
container_util.stop_report_master(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone, args.cluster_name,
components, version)
else:
print('Unknown job "%s". I only know how to stop "shuffler", '
'"analyzer-service" and "report-master".' % args.job)
def _deploy_stopstart(args):
_deploy_stop(args)
_deploy_start(args)
def _deploy_upload_secret_keys(args):
container_util.create_analyzer_private_key_secret(
args.cloud_project_prefix, args.cloud_project_name, args.cluster_zone,
args.cluster_name, args.analyzer_private_key_pem)
container_util.create_shuffler_private_key_secret(
args.cloud_project_prefix, args.cloud_project_name, args.cluster_zone,
args.cluster_name, args.shuffler_private_key_pem)
def _deploy_delete_secret_keys(args):
container_util.delete_analyzer_private_key_secret(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name)
container_util.delete_shuffler_private_key_secret(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name)
def _deploy_endpoint(args):
if args.cloud_project_prefix and args.cloud_project_prefix != 'google.com':
print('Endpoints cannot be configured by this script for projects with '
'the prefix: ' % args.cloud_project_prefix)
return
if args.job == 'report-master':
container_util.configure_report_master_endpoint(
args.cloud_project_prefix, args.cloud_project_name,
args.report_master_static_ip)
elif args.job == 'shuffler':
container_util.configure_shuffler_endpoint(args.cloud_project_prefix,
args.cloud_project_name,
args.shuffler_static_ip)
else:
print('Unknown job "%s". I only know how to configure endpoints for the '
'"report-master" or the "shuffler".' % args.job)
def _deploy_addresses(args):
container_util.reserve_static_ip_addresses(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone)
def _deploy_upload_certificate(args):
path_to_cert = args.path_to_cert
path_to_key = args.path_to_key
if not path_to_cert or not path_to_key:
print('Both --path-to-cert and --path-to-key must be set!')
return
path_to_cert = os.path.abspath(path_to_cert)
path_to_key = os.path.abspath(path_to_key)
if args.job == 'report-master':
container_util.create_cert_secret_for_report_master(
args.cloud_project_prefix, args.cloud_project_name, args.cluster_zone,
args.cluster_name, path_to_cert, path_to_key)
elif args.job == 'shuffler':
container_util.create_cert_secret_for_shuffler(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name,
path_to_cert, path_to_key)
else:
print('Unknown job "%s". I only know how to deploy certificates for the '
'"report-master" or the "shuffler".' % args.job)
def _deploy_delete_certificate(args):
if args.job == 'report-master':
container_util.delete_cert_secret_for_report_master(
args.cloud_project_prefix, args.cloud_project_name, args.cluster_zone,
args.cluster_name)
elif args.job == 'shuffler':
container_util.delete_cert_secret_for_shuffler(args.cloud_project_prefix,
args.cloud_project_name,
args.cluster_zone,
args.cluster_name)
else:
print('Unknown job "%s". I only know how to delete certificates for the '
'"report-master" or the "shuffler".' % args.job)
def _deploy_upload_service_account_key(args):
container_util.create_report_master_gcs_service_account_secret(
args.cloud_project_prefix, args.cloud_project_name, args.cluster_zone,
args.cluster_name, args.service_account_key_json)
def _deploy_kube_ui(args):
container_util.kube_ui(args.cloud_project_name, args.cluster_zone,
args.cluster_name)
def _default_shuffler_config_file(cluster_settings):
if cluster_settings['shuffler_config_file']:
return os.path.join(THIS_DIR, cluster_settings['shuffler_config_file'])
return SHUFFLER_DEMO_CONFIG_FILE
def _default_cobalt_config_dir(cluster_settings):
if cluster_settings['cobalt_config_dir']:
return os.path.join(THIS_DIR, cluster_settings['cobalt_config_dir'])
return DEMO_CONFIG_DIR
def _default_report_master_enable_scheduling(cluster_settings):
if cluster_settings['report_master_enable_scheduling']:
return cluster_settings['report_master_enable_scheduling']
return 'false'
def _default_shuffler_use_memstore(cluster_settings):
if cluster_settings['shuffler_use_memstore']:
return cluster_settings['shuffler_use_memstore']
return 'false'
def _default_use_tls(cobalt_is_running_on_gke, cluster_settings):
if cobalt_is_running_on_gke:
return cluster_settings['use_tls'] or 'false'
return 'false'
def _default_shuffler_root_certs(cobalt_is_running_on_gke, cluster_settings):
if cobalt_is_running_on_gke:
return cluster_settings['shuffler_root_certs']
return LOCALHOST_TLS_CERT_FILE
def _default_report_master_root_certs(cobalt_is_running_on_gke,
cluster_settings):
if cobalt_is_running_on_gke:
return cluster_settings['report_master_root_certs']
return LOCALHOST_TLS_CERT_FILE
def _default_shuffler_preferred_address(cobalt_is_running_on_gke,
cluster_settings):
if cobalt_is_running_on_gke:
return cluster_settings['shuffler_preferred_address']
return ''
def _default_report_master_preferred_address(cobalt_is_running_on_gke,
cluster_settings):
if cobalt_is_running_on_gke:
return cluster_settings['report_master_preferred_address']
return ''
def _cluster_settings_from_json(cluster_settings, json_file_path):
""" Reads cluster settings from a json file and adds them to a dictionary.
Args:
cluster_settings: A dictionary of cluster settings whose values will be
overwritten by any corresponding values in the json file. Any values in
the json file that do not correspond to a key in this dictionary will be
ignored.
json_file_path: The full path to a json file that must exist.
"""
print('The GKE cluster settings file being used is: %s.' % json_file_path)
with open(json_file_path) as f:
try:
read_cluster_settings = json.load(f)
except ValueError:
print('%s could not be parsed.' % json_file_path)
for key in read_cluster_settings:
if key in cluster_settings:
cluster_settings[key] = read_cluster_settings[key]
cluster_settings['deployed_versions_file'] = os.path.join(
os.path.dirname(json_file_path),
cluster_settings['deployed_versions_file'])
def _add_cloud_project_args(parser, cluster_settings):
parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of name of the Cloud project with which you wish '
'to work. This is usually an organization domain name if your '
'Cloud project is associated with one. Pass the empty string for no '
'prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project with which you wish '
'to work. This is the full project name if --cloud_project_prefix '
'is empty. Otherwise the full project name is '
'<cloud_project_prefix>:<cloud_project_name>. '
'Default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
def _add_cloud_access_args(parser, cluster_settings):
_add_cloud_project_args(parser, cluster_settings)
parser.add_argument(
'--cluster_name',
help='The GKE "container cluster" within your Cloud project with which '
'you wish to work. '
'Default=%s' % cluster_settings['cluster_name'],
default=cluster_settings['cluster_name'])
parser.add_argument(
'--cluster_zone',
help='The zone in which your GKE "container cluster" is located. '
'Default=%s' % cluster_settings['cluster_zone'],
default=cluster_settings['cluster_zone'])
def _add_static_ip_args(parser, cluster_settings):
parser.add_argument(
'--shuffler_static_ip',
help='A static IP address that has been previously reserved on the GKE '
'cluster for the Shuffler. '
'Default=%s' % cluster_settings['shuffler_static_ip'],
default=cluster_settings['shuffler_static_ip'])
parser.add_argument(
'--report_master_static_ip',
help='A static IP address that has been previously reserved on the GKE '
'cluster for the Report Master. '
'Default=%s' % cluster_settings['report_master_static_ip'],
default=cluster_settings['report_master_static_ip'])
parser.add_argument(
'--analyzer_service_static_ip',
help='A static IP address that has been previously reserved on the GKE '
'cluster for the Analyzer Service. '
'Default=%s' % cluster_settings['analyzer_service_static_ip'],
default=cluster_settings['analyzer_service_static_ip'])
def _add_gke_deployment_args(parser, cluster_settings):
_add_cloud_access_args(parser, cluster_settings)
_add_static_ip_args(parser, cluster_settings)
def _add_deploy_start_args(parser, cluster_settings):
parser.add_argument(
'--bigtable_instance_id',
help='Specify a Cloud Bigtable instance within the specified Cloud '
'project that the Analyzer should connect to. This is required '
'if and only if you are starting one of the two Analyzer jobs. '
'Default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
default_report_master_enable_scheduling = \
_default_report_master_enable_scheduling(cluster_settings)
parser.add_argument(
'--report_master_enable_scheduling',
default=default_report_master_enable_scheduling,
help=('When starting the ReportMaster, should the ReportMaster run all '
'reports automatically on a schedule? Default=%s.' %
default_report_master_enable_scheduling))
parser.add_argument(
'--report_master_update_repo_url',
help='URL to a git repository containing a cobalt configuration in '
'its master branch. If this flag is set, the configuration of '
'report master will be updated by pulling from the specified '
'repository before scheduled reports are run. '
'(e.g. "https://cobalt-analytics.googlesource.com/config/")',
default=cluster_settings['report_master_update_repo_url'])
default_shuffler_use_memstore = _default_shuffler_use_memstore(
cluster_settings)
parser.add_argument(
'--shuffler-use-memstore',
default=default_shuffler_use_memstore,
help=('When starting the Shuffler, should the Suffler use its in-memory '
'data store rather than a persistent datastore? Default=%s.' %
default_shuffler_use_memstore))
parser.add_argument(
'-danger_danger_delete_all_data_at_startup',
help='When starting the Shuffler, should all of the Observations '
'collected during previous runs of the Shuffler be permanently and '
'irrecoverably deleted from the Shuffler\'s store upon startup?',
action='store_true')
def _add_deploy_start_stop_args(parser, cluster_settings, verb):
parser.add_argument(
'--deployed_versions_file',
help='A file with version numbers to use',
default=cluster_settings['deployed_versions_file'])
parser.add_argument(
'--job',
help='The job you wish to ' + verb + '. Valid choices are "shuffler", '
'"analyzer-service", "report-master". Required.')
parser.add_argument(
'--components',
help='The components you wish to ' + verb + '. Valid choices are '
'"service" and "statefulset". Default: "service,statefulset".',
default='service,statefulset')
def _is_config_up_to_date():
savedDir = os.getcwd()
try:
os.chdir(CONFIG_SUBMODULE_PATH)
# Get the hash for the latest local revision.
local_hash = subprocess.check_output(['git', 'rev-parse', '@'])
# Get the hash for the latest remote revision.
remote_hash = subprocess.check_output(['git', 'rev-parse', 'origin/master'])
return (local_hash == remote_hash)
finally:
os.chdir(savedDir)
def main():
if not sys.platform.startswith('linux'):
print('Only linux is supported!')
return 1
# We parse the command line flags twice. The first time we are looking
# only for two particular flags, namely --production_dir and
# --cobalt_on_personal_cluster. This first pass
# will not print any help and will ignore all other flags.
parser0 = argparse.ArgumentParser(add_help=False)
parser0.add_argument('--production_dir', default='')
parser0.add_argument('-cobalt_on_personal_cluster', action='store_true')
args0, ignore = parser0.parse_known_args()
# If the flag --production_dir is passed then it must be the path to
# a directory containing a file named cluster.json. The contents of this
# json file is used to set default values for many of the other flags. That
# explains why we want to parse the flags twice.
production_cluster_json_file = ''
if args0.production_dir:
production_cluster_json_file = os.path.join(args0.production_dir,
'cluster.json')
if not os.path.exists(production_cluster_json_file):
print('File not found: %s.' % production_cluster_json_file)
return
# cluster_settings contains the default values for many of the flags.
cluster_settings = {
'analyzer_service_static_ip': '',
'bigtable_instance_id': '',
'cloud_project_name': '',
'cloud_project_prefix': '',
'cluster_name': '',
'cluster_zone': '',
'cobalt_config_dir': '',
'deployed_versions_file': 'versions.json',
'report_master_enable_scheduling': '',
'report_master_preferred_address': '',
'report_master_root_certs': '',
'report_master_static_ip': '',
'report_master_update_repo_url': '',
'shuffler_config_file': '',
'shuffler_preferred_address': '',
'shuffler_root_certs': '',
'shuffler_static_ip': '',
'shuffler_use_memstore': '',
'use_tls': '',
}
if production_cluster_json_file:
_cluster_settings_from_json(cluster_settings, production_cluster_json_file)
elif os.path.exists(PERSONAL_CLUSTER_JSON_FILE):
_cluster_settings_from_json(cluster_settings, PERSONAL_CLUSTER_JSON_FILE)
# We also use the flag --production_dir to find the PEM files.
analyzer_private_key_pem = DEFAULT_ANALYZER_PRIVATE_KEY_PEM
shuffler_private_key_pem = DEFAULT_SHUFFLER_PRIVATE_KEY_PEM
if args0.production_dir:
pem_directory = os.path.abspath(args0.production_dir)
analyzer_private_key_pem = os.path.join(pem_directory,
'analyzer_private.pem')
shuffler_private_key_pem = os.path.join(pem_directory,
'shuffler_private.pem')
cobalt_is_running_on_gke = (
args0.cobalt_on_personal_cluster or args0.production_dir)
default_use_tls = _default_use_tls(cobalt_is_running_on_gke, cluster_settings)
default_shuffler_preferred_address = _default_shuffler_preferred_address(
cobalt_is_running_on_gke, cluster_settings)
default_report_master_preferred_address = \
_default_report_master_preferred_address(cobalt_is_running_on_gke,
cluster_settings)
default_shuffler_root_certs = _default_shuffler_root_certs(
cobalt_is_running_on_gke, cluster_settings)
default_report_master_root_certs = _default_report_master_root_certs(
cobalt_is_running_on_gke, cluster_settings)
parser = argparse.ArgumentParser(description='The Cobalt command-line '
'interface.')
# Note(rudominer) A note about the handling of optional arguments here.
# We create |parent_parser| and make it a parent of all of our sub parsers.
# When we want to add a global optional argument (i.e. one that applies
# to all sub-commands such as --verbose) we add the optional argument
# to both |parent_parser| and |parser|. The reason for this is that
# that appears to be the only way to get the help string to show up both
# when the top-level command is invoked and when
# a sub-command is invoked.
#
# In other words when the user types:
#
# python cobaltb.py -h
#
# and also when the user types
#
# python cobaltb.py test -h
#
# we want to show the help for the --verbose option.
parent_parser = argparse.ArgumentParser(add_help=False)
# Even though the flag --production_dir was already parsed by parser0
# above, we add the flag here too so that we can print help for it
# and also so that the call to parser.parse_args() below will not complain
# about --produciton_dir being unrecognized.
production_dir_help = (
'The path to a Cobalt production directory containing a "cluster.json" '
'file. The use of this flag in various commands implies that the command'
' should be applied against the production Cobalt cluster associated '
'with the specified directory. The contents of cluster.json will be used'
' to set the default values for many of the other flags pertaining to '
'production deployment. Additionally if there is a file named '
'"bt_admin_service_account.json" in that directory then the environment '
'variable GOOGLE_APPLICATION_CREDENTIALS will be set to the path to this'
' file. Also the public and private key PEM files will be looked for in '
'this directory. See README.md for details.')
parser.add_argument('--production_dir', default='', help=production_dir_help)
parent_parser.add_argument(
'--production_dir', default='', help=production_dir_help)
parser.add_argument(
'--verbose',
help='Be verbose (multiple times for more)',
default=0,
dest='verbose_count',
action='count')
parent_parser.add_argument(
'--verbose',
help='Be verbose (multiple times for more)',
default=0,
dest='verbose_count',
action='count')
parser.add_argument(
'--vmodule',
help='A string to use for the GLog -vmodule flag when running the Cobalt '
'processes locally. Currently only used for the end-to-end test. '
'Optional.)',
default='')
parent_parser.add_argument(
'--vmodule',
help='A string to use for the GLog -vmodule flag when running the Cobalt'
'processes locally. Currently only used for the end-to-end test. '
'Optional.)',
default='')
subparsers = parser.add_subparsers()
########################################################
# setup command
########################################################
sub_parser = subparsers.add_parser(
'setup', parents=[parent_parser], help='Sets up the build environment.')
sub_parser.set_defaults(func=_setup)
########################################################
# update_config command
########################################################
sub_parser = subparsers.add_parser(
'update_config',
parents=[parent_parser],
help="Pulls the current version Cobalt's config "
'from its remote repo.')
sub_parser.set_defaults(func=_update_config)
########################################################
# write_prober_config command
########################################################
sub_parser = subparsers.add_parser(
'write_prober_config',
parents=[parent_parser],
help='Copies the test_app2 config to the '
'prober config, replacing the name of the '
'custom metrics output log source.')
sub_parser.add_argument(
'--config_dir',
help='Path to the configuration '
'directory which should contain the prober config. '
'Default: %s' % CONFIG_SUBMODULE_PATH,
default=CONFIG_SUBMODULE_PATH)
sub_parser.set_defaults(func=_write_prober_config)
########################################################
# check_config command
########################################################
sub_parser = subparsers.add_parser(
'check_config',
parents=[parent_parser],
help='Check the validity of the cobalt '
'configuration.')
sub_parser.add_argument(
'--config_dir',
help='Path to the configuration directory to be checked. Default: %s' %
CONFIG_SUBMODULE_PATH,
default=CONFIG_SUBMODULE_PATH)
sub_parser.set_defaults(func=_check_config)
########################################################
# build command
########################################################
sub_parser = subparsers.add_parser(
'build', parents=[parent_parser], help='Builds Cobalt.')
sub_parser.add_argument(
'--cmake_path', default='cmake', help='Path to CMake binary')
sub_parser.add_argument(
'--ninja_path', default='ninja', help='Path to Ninja binary')
sub_parser.set_defaults(func=_build)
########################################################
# lint command
########################################################
sub_parser = subparsers.add_parser(
'lint',
parents=[parent_parser],
help='Run language linters on all source files.')
sub_parser.set_defaults(func=_lint)
########################################################
# fmt command
########################################################
sub_parser = subparsers.add_parser(
'fmt',
parents=[parent_parser],
help='Run language formatter on modified files.')
sub_parser.add_argument(
'-committed',
action='store_true',
default=False,
help='Also run on files modified in the latest commit.')
sub_parser.set_defaults(func=_fmt)
########################################################
# test command
########################################################
sub_parser = subparsers.add_parser(
'test',
parents=[parent_parser],
help='Runs Cobalt tests. You must build first.')
sub_parser.set_defaults(func=_test)
sub_parser.add_argument(
'--tests',
choices=TEST_FILTERS,
help='Specify a subset of tests to run. Default=all',
default='all')
sub_parser.add_argument(
'-use_cloud_bt',
help='Causes the end-to-end tests to run using local instances of the '
'Cobalt processes connected to an instance of Cloud Bigtable. Otherwise '
'a local instance of the Bigtable Emulator will be used.',
action='store_true')
sub_parser.add_argument(
'-cobalt_on_personal_cluster',
help='Causes the end-to-end tests to run using the instance of Cobalt '
'deployed on your personal GKE cluster. Otherwise local instances of the '
'Cobalt processes are used. This option and -use_cloud_bt are mutually '
'inconsistent. Do not use both at the same time. Also this option and '
'--production_dir or mutually inconsistent because --production_dir '
'implies that the end-to-end tests should be run against the specified '
'production instance of Cobalt.',
action='store_true')
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify a Cloud Bigtable instance within the specified Cloud'
' project against which to run some of the tests.'
' Only used for the cloud_bt tests and e2e tests when either '
'-use_cloud_bt or -cobalt_on_personal_cluster are specified.'
' default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser.add_argument(
'--shuffler_preferred_address',
default=default_shuffler_preferred_address,
help='Address of the form <host>:<port> to be used by the '
'end-to-end test for connecting to the Shuffler. Optional. '
'If not specified then the default port will be used and, when '
'running locally, "localhost" will be used and when running on GKE '
'the IP address of the Shuffler will be automatically discovered.'
'default=%s' % default_shuffler_preferred_address)
sub_parser.add_argument(
'--report_master_preferred_address',
default=default_report_master_preferred_address,
help='Address of the form <host>:<port> to be used by the '
'end-to-end for connecting to the ReportMaster. Optional. '
'If not specified then the default port will be used and, when '
'running locally, "localhost" will be used and when running on GKE '
'the IP address of the ReportMaster will be automatically '
'discovered. '
'default=%s' % default_report_master_preferred_address)
sub_parser.add_argument(
'--use_tls',
default=default_use_tls,
help='Run end to end tests with tls enabled. '
'default=%s' % default_use_tls)
sub_parser.add_argument(
'--shuffler_root_certs',
default=default_shuffler_root_certs,
help='When --use_tls=true then use this as the root certs file '
'(a.k.a the CA file) for the tls connection to the Shuffler. If not '
'specified then gRPC defaults will be used. '
'default=%s' % default_shuffler_root_certs)
sub_parser.add_argument(
'--report_master_root_certs',
default=default_report_master_root_certs,
help='When --use_tls=true then use this as the root certs file '
'(a.k.a the CA file) for the tls connection to the ReportMaster. If not '
'specified then gRPC defaults will be used. '
'default=%s' % default_report_master_root_certs)
sub_parser.add_argument(
'--tls_cert_file',
default=LOCALHOST_TLS_CERT_FILE,
help='When --use_tls=true and the servers are being run locally, '
'then use this as the server cert file. '
'default=%s' % LOCALHOST_TLS_CERT_FILE)
sub_parser.add_argument(
'--tls_key_file',
default=LOCALHOST_TLS_KEY_FILE,
help='When --use_tls=true and the servers are being run locally, '
'then use this as the server private key file. '
'default=%s' % LOCALHOST_TLS_KEY_FILE)
########################################################
# clean command
########################################################
sub_parser = subparsers.add_parser(
'clean',
parents=[parent_parser],
help='Deletes some or all of the build products.')
sub_parser.set_defaults(func=_clean)
sub_parser.add_argument(
'--full', help='Delete the entire "out" directory.', action='store_true')
########################################################
# start command
########################################################
start_parser = subparsers.add_parser(
'start', help='Start one of the Cobalt processes running locally.')
start_subparsers = start_parser.add_subparsers()
sub_parser = start_subparsers.add_parser(
'shuffler',
parents=[parent_parser],
help='Start the Shuffler running locally.')
sub_parser.set_defaults(func=_start_shuffler)
sub_parser.add_argument(
'--port',
help='The port on which the Shuffler should listen. '
'Default=%s.' % DEFAULT_SHUFFLER_PORT,
default=DEFAULT_SHUFFLER_PORT)
sub_parser.add_argument(
'--analyzer_uri',
help='Default=localhost:%s' % DEFAULT_ANALYZER_SERVICE_PORT,
default='localhost:%s' % DEFAULT_ANALYZER_SERVICE_PORT)
sub_parser.add_argument(
'-use_memstore',
help='Default: False, use persistent LevelDB Store.',
action='store_true')
sub_parser.add_argument(
'-keep_existing_db',
help='When using LevelDB should any previously persisted data be kept? '
'Default=False, erase the DB before starting the Shuffler.',
action='store_true')
sub_parser.add_argument(
'--config_file',
help='Path to the Shuffler configuration file. '
'Default=%s' % SHUFFLER_DEMO_CONFIG_FILE,
default=SHUFFLER_DEMO_CONFIG_FILE)
sub_parser.add_argument(
'--use_tls',
default='false',
help='Start the Shuffler with tls enabled. '
'default=false')
sub_parser.add_argument(
'--tls_cert_file',
default=LOCALHOST_TLS_CERT_FILE,
help='When --use_tls=true then use this as the cert file. '
'default=%s' % LOCALHOST_TLS_CERT_FILE)
sub_parser.add_argument(
'--tls_key_file',
default=LOCALHOST_TLS_KEY_FILE,
help='When --use_tls=true then use this as the server private key file. '
'default=%s' % LOCALHOST_TLS_KEY_FILE)
sub_parser = start_subparsers.add_parser(
'analyzer_service',
parents=[parent_parser],
help='Start the Analyzer Service running locally'
' and connected to a local instance of the Bigtable Emulator.')
sub_parser.set_defaults(func=_start_analyzer_service)
sub_parser.add_argument(
'-use_cloud_bt',
help='Causes the Analyzer service to connect to an instance of Cloud '
'Bigtable. Otherwise a local instance of the Bigtable Emulator will '
'be used.',
action='store_true')
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance to which the Analyzer service will connect. Only '
'used if -use_cloud_bt is set. '
'This is usually an organization domain name if your Cloud project '
'is associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance to which the Analyzer service will connect. '
'Only used if -use_cloud_bt is set. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify a Cloud Bigtable instance within the specified Cloud '
'project against which to query. Only used if -use_cloud_bt is set. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser.add_argument(
'--port',
help='The port on which the Analyzer service should listen. '
'Default=%s.' % DEFAULT_ANALYZER_SERVICE_PORT,
default=DEFAULT_ANALYZER_SERVICE_PORT,
)
sub_parser = start_subparsers.add_parser(
'report_master',
parents=[parent_parser],
help='Start the Analyzer ReportMaster Service '
'running locally and connected to a local instance of the Bigtable'
'Emulator.')
sub_parser.set_defaults(func=_start_report_master)
sub_parser.add_argument(
'-use_cloud_bt',
help='Causes the Report Master to connect to an instance of Cloud '
'Bigtable. Otherwise a local instance of the Bigtable Emulator will '
'be used.',
action='store_true')
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance to which the Report Master will connect. Only '
'used if -use_cloud_bt is set. '
'This is usually an organization domain name if your Cloud project '
'is associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance to which the Report Master will connect. '
'Only used if -use_cloud_bt is set. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify a Cloud Bigtable instance within the specified Cloud '
'project against which to query. Only used if -use_cloud_bt is set. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser.add_argument(
'--port',
help='The port on which the ReportMaster should listen. '
'Default=%s.' % DEFAULT_REPORT_MASTER_PORT,
default=DEFAULT_REPORT_MASTER_PORT)
sub_parser.add_argument(
'--cobalt_config_dir',
help='Path of directory containing Cobalt configuration files. '
'Default=%s' % DEMO_CONFIG_DIR,
default=DEMO_CONFIG_DIR)
sub_parser.add_argument(
'--use_tls',
default='false',
help='Start the ReportMaster with tls enabled. '
'default=False')
sub_parser.add_argument(
'--tls_cert_file',
default=LOCALHOST_TLS_CERT_FILE,
help='When --use_tls=true then use this as the cert file. '
'default=%s' % LOCALHOST_TLS_CERT_FILE)
sub_parser.add_argument(
'--tls_key_file',
default=LOCALHOST_TLS_KEY_FILE,
help='When --use_tls=true then use this as the server private key file. '
'default=%s' % LOCALHOST_TLS_KEY_FILE)
sub_parser = start_subparsers.add_parser(
'test_app',
parents=[parent_parser],
help='Start the Cobalt test client app.')
sub_parser.set_defaults(func=_start_test_app)
sub_parser.add_argument(
'-automatic',
help='Causes the test_app to run in automatic mode rather than '
'interactive mode. It runs forever until killed sending Observations '
'to the Shuffler.',
action='store_true')
sub_parser.add_argument(
'-cobalt_on_personal_cluster',
help='Causes the test_app to run using an instance of Cobalt '
'deployed in Google Container Engine. Otherwise local instances of the '
'Cobalt processes are used.',
action='store_true')
default_cobalt_config_dir = _default_cobalt_config_dir(cluster_settings)
sub_parser.add_argument(
'--cobalt_config_dir',
help='Path of directory containing Cobalt configuration files. '
'Default=%s' % default_cobalt_config_dir,
default=default_cobalt_config_dir)
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--project_id',
help='Specify the Cobalt project ID with which you wish to work. '
'Must be in the range [1, 99]. Default = 1.',
default=1)
sub_parser.add_argument(
'--shuffler_preferred_address',
default=default_shuffler_preferred_address,
help='Address of the form <host>:<port> to be used by the '
'test_app for connecting to the Shuffler. Optional. '
'If not specified then the default port will be used and, when '
'running locally, "localhost" will be used and when running on GKE '
'the IP address of the Shuffler will be automatically discovered.'
'default=%s' % default_shuffler_preferred_address)
sub_parser.add_argument(
'--use_tls',
default=default_use_tls,
help='The test_app will use TLS to communicate with the Shuffler. '
'default=%s' % default_use_tls)
sub_parser.add_argument(
'--shuffler_root_certs',
default=default_shuffler_root_certs,
help='When --use_tls=true then use this as the root certs file '
'(a.k.a the CA file) for the tls connection to the Shuffler. If not '
'specified then gRPC defaults will be used. '
'default=%s' % default_shuffler_root_certs)
sub_parser = start_subparsers.add_parser(
'report_client',
parents=[parent_parser],
help='Start the Cobalt report client.')
sub_parser.set_defaults(func=_start_report_client)
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'-cobalt_on_personal_cluster',
help='Causes the report_client to query the instance of ReportMaster '
'in your personal cluster rather than one running locally. If '
'--production_dir is also specified it takes precedence and causes '
'the report_client to query the instance of ReportMaster in the '
'specified production cluster.',
action='store_true')
sub_parser.add_argument(
'--project_id',
help='Specify the Cobalt project ID from which you wish to query. '
'Default = 1.',
default=1)
sub_parser.add_argument(
'--report_master_preferred_address',
default=default_report_master_preferred_address,
help='Address of the form <host>:<port> to be used by the '
'report_client for connecting to the ReportMaster. Optional. '
'If not specified then the default port will be used and, when '
'running locally, "localhost" will be used and when running on GKE '
'the IP address of the ReportMaster will be automatically '
'discovered. '
'default=%s' % default_report_master_preferred_address)
sub_parser.add_argument(
'--use_tls',
default=default_use_tls,
help='The test_app will use TLS to communicate with the Shuffler. '
'default=%s' % default_use_tls)
sub_parser.add_argument(
'--report_master_root_certs',
default=default_report_master_root_certs,
help='When --use_tls=true then use this as the root certs file '
'(a.k.a the CA file) for the tls connection to the ReportMaster. If not '
'specified then gRPC defaults will be used. '
'default=%s' % default_report_master_root_certs)
sub_parser = start_subparsers.add_parser(
'observation_querier',
parents=[parent_parser],
help='Start the Cobalt ObservationStore '
'querying tool.')
sub_parser.set_defaults(func=_start_observation_querier)
sub_parser.add_argument(
'-use_cloud_bt',
help='Causes the query to be performed against an instance of Cloud '
'Bigtable. Otherwise a local instance of the Bigtable Emulator will be '
'used.',
action='store_true')
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance against which to '
'query. Only used if -use_cloud_bt is set. This is '
'usually an organization domain name if your Cloud project is '
'associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance against which to '
'query. Only used if -use_cloud_bt is set. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify a Cloud Bigtable instance within the specified Cloud '
'project against which to query. Only used if -use_cloud_bt is set. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser = start_subparsers.add_parser(
'bigtable_emulator',
parents=[parent_parser],
help='Start the Bigtable Emulator running locally.')
sub_parser.set_defaults(func=_start_bigtable_emulator)
########################################################
# generate_keys command
########################################################
sub_parser = subparsers.add_parser(
'generate_keys',
parents=[parent_parser],
help='Generate new public/private key pairs.')
sub_parser.set_defaults(func=_generate_keys)
########################################################
# generate_cert command
########################################################
sub_parser = subparsers.add_parser(
'generate_cert',
parents=[parent_parser],
help='Generate a self-signed TLS key/cert pair. You must have '
'openssl in your path.')
sub_parser.set_defaults(func=_generate_cert)
sub_parser.add_argument(
'--path-to-key',
default='',
help='Absolute or relative path to a private key file that should be '
'generated. Required.')
sub_parser.add_argument(
'--path-to-cert',
default='',
help='Absolute or relative path to a cert file that should be '
'generted. Required.')
sub_parser.add_argument(
'--ip-address',
default='127.0.0.1',
help='Optional IP address. If specified the self-signed certificate will '
'include a Subject Alternative Name section containing the given IP '
'address. This is necessary in order to use the IP address in gRPC '
'requests.')
sub_parser.add_argument(
'--hostname',
default='localhost',
help='Optional HostName. If specified the self-signed certificate will '
'include a Subject Alternative Name section containing the hostname. '
'This is necessary in order to use the hostname in gRPC '
'requests.')
########################################################
# bigtable command
########################################################
bigtable_parser = subparsers.add_parser(
'bigtable',
help='Perform an operation on your personal Cloud Bigtable cluster.')
bigtable_subparsers = bigtable_parser.add_subparsers()
sub_parser = bigtable_subparsers.add_parser(
'provision',
parents=[parent_parser],
help="Create Cobalt's Cloud BigTables if they don't exit.")
sub_parser.set_defaults(func=_provision_bigtable)
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance to be provisioned. This is '
'usually an organization domain name if your Cloud project is '
'associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance to be provisioned. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify the Cloud Bigtable instance within the specified Cloud'
' project that is to be provisioned. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser = bigtable_subparsers.add_parser(
'delete_observations',
parents=[parent_parser],
help='**WARNING: Permanently delete data from Cobalt\'s Observation '
'store. **')
sub_parser.set_defaults(func=_delete_observations)
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance from which all Observation data will be '
'permanently deleted. This is '
'usually an organization domain name if your Cloud project is '
'associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance from which all Observation data will be '
'permanently deleted. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify the Cloud Bigtable instance within the specified Cloud'
' project from which all Observation data will be permanently deleted. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser.add_argument(
'--customer_id',
help='Specify the Cobalt customer ID from which you wish to delete '
'observations. Required.',
default=0)
sub_parser.add_argument(
'--project_id',
help='Specify the Cobalt project ID from which you wish to delete '
'observations. Required.',
default=0)
sub_parser.add_argument(
'--metric_id',
help='Specify the Cobalt metric ID from which you wish to delete '
'observations. Required.',
default=0)
sub_parser = bigtable_subparsers.add_parser(
'delete_reports',
parents=[parent_parser],
help='**WARNING: Permanently delete data from Cobalt\'s Report '
'store. **')
sub_parser.set_defaults(func=_delete_reports)
sub_parser.add_argument(
'--cloud_project_prefix',
help='The prefix part of the name of the Cloud project containing the '
'Bigtable instance from which all Report data will be '
'permanently deleted. This is '
'usually an organization domain name if your Cloud project is '
'associated with one. Pass the empty string for no prefix. '
'Default=%s.' % cluster_settings['cloud_project_prefix'],
default=cluster_settings['cloud_project_prefix'])
sub_parser.add_argument(
'--cloud_project_name',
help='The main part of the name of the Cloud project containing the '
'Bigtable instance from which all Report data will be '
'permanently deleted. This is the full project '
'name if --cloud_project_prefix is empty. Otherwise the full '
'project name is <cloud_project_prefix>:<cloud_project_name>. '
'default=%s' % cluster_settings['cloud_project_name'],
default=cluster_settings['cloud_project_name'])
sub_parser.add_argument(
'--bigtable_instance_id',
help='Specify the Cloud Bigtable instance within the specified Cloud'
' project from which all Report data will be permanently deleted. '
'default=%s' % cluster_settings['bigtable_instance_id'],
default=cluster_settings['bigtable_instance_id'])
sub_parser.add_argument(
'--customer_id',
help='Specify the Cobalt customer ID from which you wish to delete '
'reports. Required.',
default=0)
sub_parser.add_argument(
'--project_id',
help='Specify the Cobalt project ID from which you wish to delete '
'reports. Required.',
default=0)
sub_parser.add_argument(
'--report_config_id',
help='Specify the Cobalt report config ID for which you wish to delete '
'all report data. Required.',
default=0)
sub_parser.add_argument(
'-danger_danger_delete_production_reports',
help='Overrides the default protection that prevents you from deleting '
'reports from production projects.',
action='store_true')
########################################################
# deploy command
########################################################
deploy_parser = subparsers.add_parser(
'deploy',
parents=[parent_parser],
help='Build Docker containers. Push to Container Regitry. Deploy to GKE.')
deploy_subparsers = deploy_parser.add_subparsers()
sub_parser = deploy_subparsers.add_parser(
'show',
parents=[parent_parser],
help='Display information about currently '
'deployed jobs on GKE, including their public URIs.')
sub_parser.set_defaults(func=_deploy_show)
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'login',
parents=[parent_parser],
help='Login to gcloud. Invoke if any command fails and asks you to login '
'to gcloud.')
sub_parser.set_defaults(func=_deploy_login)
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'authenticate',
parents=[parent_parser],
help='Refresh your authentication token if '
'necessary. Also associates your local computer with a particular '
'GKE cluster to which you will be deploying.')
sub_parser.set_defaults(func=_deploy_authenticate)
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'build',
parents=[parent_parser],
help='Rebuild all Docker images. '
'You must have the Docker daemon running.')
sub_parser.set_defaults(func=_deploy_build)
_add_cloud_access_args(sub_parser, cluster_settings)
default_shuffler_config_file = _default_shuffler_config_file(cluster_settings)
sub_parser.add_argument(
'--shuffler_config_file',
help='Path to the Shuffler configuration file. '
'Default=%s' % default_shuffler_config_file,
default=default_shuffler_config_file)
default_cobalt_config_dir = _default_cobalt_config_dir(cluster_settings)
sub_parser.add_argument(
'--cobalt_config_dir',
help='Path of directory containing Cobalt configuration files. '
'Default=%s' % default_cobalt_config_dir,
default=default_cobalt_config_dir)
sub_parser = deploy_subparsers.add_parser(
'production_build',
parents=[parent_parser],
help='Clean rebuild of binaries followed by '
'a build and push of all Docker images.')
sub_parser.set_defaults(func=_deploy_production_build)
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--shuffler_config_file',
help='Path to the Shuffler configuration file. '
'Default=%s' % default_shuffler_config_file,
default=default_shuffler_config_file)
sub_parser.add_argument(
'--cobalt_config_dir',
help='Path of directory containing Cobalt configuration files. '
'Default=%s' % default_cobalt_config_dir,
default=default_cobalt_config_dir)
sub_parser.add_argument(
'--git_revision',
help='A git revision to build the binaries off of. If this is not '
'provided, you will be prompted to select one from a list',
default='')
sub_parser = deploy_subparsers.add_parser(
'push',
parents=[parent_parser],
help='Push a Docker image to the Google'
'Container Registry.')
sub_parser.set_defaults(func=_deploy_push)
sub_parser.add_argument(
'--job',
help='The job you wish to push. Valid choices are "shuffler", '
'"analyzer-service", "report-master". Required.')
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'start', parents=[parent_parser], help='Start one of the jobs on GKE.')
sub_parser.set_defaults(func=_deploy_start)
_add_gke_deployment_args(sub_parser, cluster_settings)
_add_deploy_start_stop_args(sub_parser, cluster_settings, 'start')
_add_deploy_start_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'stop', parents=[parent_parser], help='Stop one of the jobs on GKE.')
sub_parser.set_defaults(func=_deploy_stop)
_add_gke_deployment_args(sub_parser, cluster_settings)
_add_deploy_start_stop_args(sub_parser, cluster_settings, 'stop')
sub_parser = deploy_subparsers.add_parser(
'stopstart', parents=[parent_parser], help='Stop and start a job on GKE.')
sub_parser.set_defaults(func=_deploy_stopstart)
_add_gke_deployment_args(sub_parser, cluster_settings)
_add_deploy_start_stop_args(sub_parser, cluster_settings, 'stopstart')
_add_deploy_start_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'upload_secret_keys',
parents=[parent_parser],
help='Creates |secret| objects in the '
'cluster to store the private keys for the Analyzer and the Shuffler. '
'The private keys must first be generated using the "generate_keys" '
'command (once for the Analyzer and once for the Shuffler). This must be '
'done at least once before starting the Analyzer Service or the '
'Shuffler. To replace the keys first delete the old ones using the '
'"deploy delete_secret_keys" command.')
sub_parser.set_defaults(func=_deploy_upload_secret_keys)
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--analyzer_private_key_pem', default=analyzer_private_key_pem)
sub_parser.add_argument(
'--shuffler_private_key_pem', default=shuffler_private_key_pem)
sub_parser = deploy_subparsers.add_parser(
'delete_secret_keys',
parents=[parent_parser],
help='Deletes the |secret| objects in the '
'cluster that were created using the "deploy upload_secret_keys" '
'command.')
sub_parser.set_defaults(func=_deploy_delete_secret_keys)
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser = deploy_subparsers.add_parser(
'endpoint',
parents=[parent_parser],
help='Create or configure the Cloud Endpoint for one of Cobalt\'s job.')
sub_parser.set_defaults(func=_deploy_endpoint)
_add_cloud_project_args(sub_parser, cluster_settings)
_add_static_ip_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--job',
help='The job whose Cloud Endpoint you wish to configure. Valid choices: '
'"report-master", "shuffler". Required.')
sub_parser = deploy_subparsers.add_parser(
'addresses',
parents=[parent_parser],
help='Reserves static ip addresses for all the Cobalt jobs.')
_add_cloud_access_args(sub_parser, cluster_settings)
sub_parser.set_defaults(func=_deploy_addresses)
sub_parser = deploy_subparsers.add_parser(
'upload_certificate',
parents=[parent_parser],
help='Deploy certificates and associated private keys')
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--job',
help='The job whose certificate you wish to deploy. Valid choices: '
'"report-master", "shuffler". Required.')
sub_parser.add_argument(
'--path-to-cert', help='Path to the certificate to be deployed.')
sub_parser.add_argument(
'--path-to-key', help='Path to the private key to be deployed.')
sub_parser.set_defaults(func=_deploy_upload_certificate)
sub_parser = deploy_subparsers.add_parser(
'delete_certificate',
parents=[parent_parser],
help='Delete certificates and associated private keys')
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--job',
help='The job whose certificate you wish to delete. Valid choices: '
'"report-master", "shuffler". Required.')
sub_parser.set_defaults(func=_deploy_delete_certificate)
sub_parser = deploy_subparsers.add_parser(
'upload_service_account_key',
parents=[parent_parser],
help='Creates a |secret| object in the '
'cluster to store the private key for the ReportMaster\'s '
'service account. This is the credential that allows the ReportMaster '
'to export reports to Google Cloud Storage. The service account and '
'private key must first be created manually through the Google Cloud '
'console for the relevant project. The service account must separately '
'be granted write permission to any Google Cloud Storage bucket to '
'which Cobalt will export reports. Then the private key json file '
'should be temporarily downloaded to your machine. This command is used '
'to upload it as a Kubernetes secret. After it is uploaded the json '
'file should be deleted from your local machine.')
sub_parser.set_defaults(func=_deploy_upload_service_account_key)
_add_gke_deployment_args(sub_parser, cluster_settings)
sub_parser.add_argument(
'--service_account_key_json',
help='Path to the private key json file to upload. Required.')
sub_parser = deploy_subparsers.add_parser(
'kube_ui', parents=[parent_parser], help='Launches the kubernetes ui.')
sub_parser.set_defaults(func=_deploy_kube_ui)
_add_gke_deployment_args(sub_parser, cluster_settings)
args = parser.parse_args()
global _verbose_count
_verbose_count = args.verbose_count
_initLogging(_verbose_count)
global _vmodule
_vmodule = args.vmodule
# Add bin dirs from sysroot to the front of the path.
os.environ['PATH'] = \
'%s/bin' % SYSROOT_DIR \
+ os.pathsep + '%s/golang/bin' % SYSROOT_DIR \
+ os.pathsep + '%s/gcloud/google-cloud-sdk/bin' % SYSROOT_DIR \
+ os.pathsep + os.environ['PATH']
os.environ['LD_LIBRARY_PATH'] = '%s/lib' % SYSROOT_DIR
global bt_admin_service_account_credentials_file
bt_admin_service_account_credentials_file = \
PERSONAL_BT_ADMIN_SERVICE_ACCOUNT_CREDENTIALS_FILE
if args0.production_dir:
bt_admin_service_account_credentials_file = os.path.abspath(
os.path.join(args0.production_dir, 'bt_admin_service_account.json'))
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = \
bt_admin_service_account_credentials_file
os.environ['GRPC_DEFAULT_SSL_ROOTS_FILE_PATH'] = GRPC_PEM_ROOTS
os.environ['GOROOT'] = '%s/golang' % SYSROOT_DIR
# Until Python3.7 adds the 'required' flag for subparsers, an error occurs
# when running without specifying a subparser on the command line:
# https://bugs.python.org/issue16308
# Work around the issue by checking whether the 'func' attribute has been
# set.
try:
a = getattr(args, 'func')
except AttributeError:
parser.print_usage()
sys.exit(0)
return args.func(args)
if __name__ == '__main__':
sys.exit(main())