foundationdb/fdbcli/tests/metacluster_fdbcli_tests.py

218 lines
8.4 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import functools
import logging
import os
import random
import subprocess
from argparse import RawDescriptionHelpFormatter
# TODO: deduplicate with fdbcli_tests.py
def enable_logging(level=logging.DEBUG):
"""Enable logging in the function with the specified logging level
Args:
level (logging.<level>, optional): logging level for the decorated function. Defaults to logging.ERROR.
"""
def func_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# initialize logger
logger = logging.getLogger(func.__name__)
logger.setLevel(level)
# set logging format
handler = logging.StreamHandler()
handler_format = logging.Formatter(
'[%(asctime)s] - %(filename)s:%(lineno)d - %(levelname)s - %(name)s - %(message)s')
handler.setFormatter(handler_format)
handler.setLevel(level)
logger.addHandler(handler)
# pass the logger to the decorated function
result = func(logger, *args, **kwargs)
return result
return wrapper
return func_decorator
def run_fdbcli_command(cluster_file, *args):
"""run the fdbcli statement: fdbcli --exec '<arg1> <arg2> ... <argN>'.
Returns:
string: Stderr output from fdbcli
"""
command_template = [fdbcli_bin, '-C', "{}".format(cluster_file), '--exec']
commands = command_template + ["{}".format(' '.join(args))]
try:
process = subprocess.run(commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=fdbcli_env, timeout=20)
rc = process.returncode
out = process.stdout.decode('utf-8').strip()
err = process.stderr.decode('utf-8').strip()
return rc, out, err
except subprocess.TimeoutExpired:
raise Exception('The fdbcli command is stuck, database is unavailable')
def get_cluster_connection_str(cluster_file_path):
with open(cluster_file_path, 'r') as f:
conn_str = f.readline().strip()
return conn_str
@enable_logging()
def metacluster_create(logger, cluster_file, name, tenant_id_prefix):
# creating a metacluster with optional tenant mode should fail
rc, out, err = run_fdbcli_command(cluster_file, "configure tenant_mode=optional_experimental")
if rc != 0:
raise Exception(err)
rc, out, err = run_fdbcli_command(cluster_file, "metacluster create_experimental", name, str(tenant_id_prefix))
if "ERROR" not in out:
raise Exception("Metacluster creation should have failed")
# set the tenant mode to disabled for the metacluster otherwise creation will fail
rc, out, err = run_fdbcli_command(cluster_file, "configure tenant_mode=disabled")
logger.debug('Metacluster tenant mode set to disabled')
if rc != 0:
raise Exception(err)
rc, out, err = run_fdbcli_command(cluster_file, "metacluster create_experimental", name, str(tenant_id_prefix))
if rc != 0:
raise Exception(err)
logger.debug(out)
logger.debug('Metacluster {} created'.format(name))
def metacluster_register(management_cluster_file, data_cluster_file, name, max_tenant_groups):
conn_str = get_cluster_connection_str(data_cluster_file)
rc, out, err = run_fdbcli_command(management_cluster_file,
"metacluster register",
name,
"connection_string={}".format(conn_str),
'max_tenant_groups={}'.format(max_tenant_groups))
if rc != 0:
raise Exception(err)
@enable_logging()
def setup_metacluster(logger, management_cluster, data_clusters, max_tenant_groups_per_cluster):
management_cluster_file = management_cluster[0]
management_cluster_name = management_cluster[1]
tenant_id_prefix = random.randint(0, 32767)
logger.debug('management cluster: {}'.format(management_cluster_name))
logger.debug('data clusters: {}'.format([name for (cf, name) in data_clusters]))
metacluster_create(management_cluster_file, management_cluster_name, tenant_id_prefix)
for (cf, name) in data_clusters:
metacluster_register(management_cluster_file, cf, name, max_tenant_groups=max_tenant_groups_per_cluster)
def metacluster_status(cluster_file):
_, out, _ = run_fdbcli_command(cluster_file, "metacluster status")
return out
def setup_tenants(management_cluster_file, data_cluster_files, tenants):
for tenant in tenants:
_, output, _ = run_fdbcli_command(management_cluster_file, 'tenant create', tenant)
expected_output = 'The tenant `{}\' has been created'.format(tenant)
assert output == expected_output
def configure_tenant(management_cluster_file, data_cluster_files, tenant, tenant_group=None, assigned_cluster=None):
command = 'tenant configure {}'.format(tenant)
if tenant_group:
command = command + ' tenant_group={}'.format(tenant_group)
if assigned_cluster:
command = command + ' assigned_cluster={}'.format(assigned_cluster)
_, output, err = run_fdbcli_command(management_cluster_file, command)
return output, err
def clear_database_and_tenants(management_cluster_file, data_cluster_files):
subcmd1 = 'writemode on'
subcmd2 = 'option on SPECIAL_KEY_SPACE_ENABLE_WRITES'
subcmd3 = 'clearrange ' '"" \\xff'
subcmd4 = 'clearrange \\xff\\xff/management/tenant/map/ \\xff\\xff/management/tenant/map0'
run_fdbcli_command(management_cluster_file, subcmd1, subcmd2, subcmd3, subcmd4)
@enable_logging()
def clusters_status_test(logger, cluster_files, max_tenant_groups_per_cluster):
logger.debug('Verifying no cluster is part of a metacluster')
for cf in cluster_files:
output = metacluster_status(cf)
assert output == "This cluster is not part of a metacluster"
logger.debug('Verified')
num_clusters = len(cluster_files)
names = ['meta_mgmt']
names.extend(['data{}'.format(i) for i in range(1, num_clusters)])
logger.debug('Setting up a metacluster')
setup_metacluster([cluster_files[0], names[0]], list(zip(cluster_files[1:], names[1:])),
max_tenant_groups_per_cluster=max_tenant_groups_per_cluster)
expected = """
number of data clusters: {}
tenant group capacity: {}
allocated tenant groups: 0
"""
expected = expected.format(num_clusters - 1, (num_clusters - 1) * max_tenant_groups_per_cluster).strip()
output = metacluster_status(cluster_files[0])
assert expected == output
logger.debug('Metacluster setup correctly')
for (cf, name) in zip(cluster_files[1:], names[1:]):
output = metacluster_status(cf)
expected = "This cluster \"{}\" is a data cluster within the metacluster named \"{}\"".format(name, names[0])
assert expected == output
@enable_logging()
def configure_tenants_test_disableClusterAssignment(logger, cluster_files):
tenants = ['tenant1', 'tenant2']
logger.debug('Tenants to create: {}'.format(tenants))
setup_tenants(cluster_files[0], cluster_files[1:], tenants)
# Once we reach here, the tenants have been created successfully
logger.debug('Tenants created: {}'.format(tenants))
for tenant in tenants:
out, err = configure_tenant(cluster_files[0], cluster_files[1:], tenant, assigned_cluster='cluster')
assert err == 'ERROR: Tenant configuration is invalid (2140)'
logger.debug('Tenants configured')
clear_database_and_tenants(cluster_files[0], cluster_files[1:])
logger.debug('Tenants cleared')
@enable_logging()
def test_main(logger):
logger.debug('Tests start')
# This must be the first test to run, since it sets up the metacluster that
# will be used throughout the test
clusters_status_test(cluster_files, max_tenant_groups_per_cluster=5)
configure_tenants_test_disableClusterAssignment(cluster_files)
logger.debug('Tests complete')
if __name__ == "__main__":
print("metacluster_fdbcli_tests")
script_desc = """
This script executes a series of commands on multiple clusters within an FDB metacluster.
"""
parser = argparse.ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
description=script_desc)
parser.add_argument('build_dir', metavar='BUILD_DIRECTORY', help='FDB build directory')
args = parser.parse_args()
# keep current environment variables
fdbcli_env = os.environ.copy()
cluster_files = fdbcli_env.get("FDB_CLUSTERS").split(';')
assert len(cluster_files) > 1
fdbcli_bin = args.build_dir + '/bin/fdbcli'
test_main()