Merge pull request #6561 from sfc-gh-ajbeamon/fdb-tenant-fdbcli

Add fdbcli support for tenants
This commit is contained in:
A.J. Beamon 2022-03-21 11:26:10 -07:00 committed by GitHub
commit fd81ef99c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 582 additions and 32 deletions

View File

@ -542,6 +542,103 @@ def triggerddteaminfolog(logger):
output = run_fdbcli_command('triggerddteaminfolog')
assert output == 'Triggered team info logging in data distribution.'
@enable_logging()
def tenants(logger):
output = run_fdbcli_command('listtenants')
assert output == 'The cluster has no tenants'
output = run_fdbcli_command('createtenant tenant')
assert output == 'The tenant `tenant\' has been created'
output = run_fdbcli_command('createtenant tenant2')
assert output == 'The tenant `tenant2\' has been created'
output = run_fdbcli_command('listtenants')
assert output == '1. tenant\n 2. tenant2'
output = run_fdbcli_command('listtenants a z 1')
assert output == '1. tenant'
output = run_fdbcli_command('listtenants a tenant2')
assert output == '1. tenant'
output = run_fdbcli_command('listtenants tenant2 z')
assert output == '1. tenant2'
output = run_fdbcli_command('gettenant tenant')
lines = output.split('\n')
assert len(lines) == 2
assert lines[0].strip().startswith('id: ')
assert lines[1].strip().startswith('prefix: ')
output = run_fdbcli_command('usetenant')
assert output == 'Using the default tenant'
output = run_fdbcli_command_and_get_error('usetenant tenant3')
assert output == 'ERROR: Tenant `tenant3\' does not exist'
# Test writing keys to different tenants and make sure they all work correctly
run_fdbcli_command('writemode on; set tenant_test default_tenant')
output = run_fdbcli_command('get tenant_test')
assert output == '`tenant_test\' is `default_tenant\''
process = subprocess.Popen(command_template[:-1], stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=fdbcli_env)
cmd_sequence = ['writemode on', 'usetenant tenant', 'get tenant_test', 'set tenant_test tenant']
output, _ = process.communicate(input='\n'.join(cmd_sequence).encode())
lines = output.decode().strip().split('\n')[-3:]
assert lines[0] == 'Using tenant `tenant\''
assert lines[1] == '`tenant_test\': not found'
assert lines[2].startswith('Committed')
process = subprocess.Popen(command_template[:-1], stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=fdbcli_env)
cmd_sequence = ['writemode on', 'usetenant tenant2', 'get tenant_test', 'set tenant_test tenant2', 'get tenant_test']
output, _ = process.communicate(input='\n'.join(cmd_sequence).encode())
lines = output.decode().strip().split('\n')[-4:]
assert lines[0] == 'Using tenant `tenant2\''
assert lines[1] == '`tenant_test\': not found'
assert lines[2].startswith('Committed')
assert lines[3] == '`tenant_test\' is `tenant2\''
process = subprocess.Popen(command_template[:-1], stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=fdbcli_env)
cmd_sequence = ['usetenant tenant', 'get tenant_test', 'defaulttenant', 'get tenant_test']
output, _ = process.communicate(input='\n'.join(cmd_sequence).encode())
lines = output.decode().strip().split('\n')[-4:]
assert lines[0] == 'Using tenant `tenant\''
assert lines[1] == '`tenant_test\' is `tenant\''
assert lines[2] == 'Using the default tenant'
assert lines[3] == '`tenant_test\' is `default_tenant\''
process = subprocess.Popen(command_template[:-1], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=fdbcli_env)
cmd_sequence = ['writemode on', 'usetenant tenant', 'clear tenant_test', 'deletetenant tenant', 'get tenant_test', 'defaulttenant', 'usetenant tenant']
output, error_output = process.communicate(input='\n'.join(cmd_sequence).encode())
lines = output.decode().strip().split('\n')[-7:]
error_lines = error_output.decode().strip().split('\n')[-2:]
assert lines[0] == 'Using tenant `tenant\''
assert lines[1].startswith('Committed')
assert lines[2] == 'The tenant `tenant\' has been deleted'
assert lines[3] == 'WARNING: the active tenant was deleted. Use the `usetenant\' or `defaulttenant\''
assert lines[4] == 'command to choose a new tenant.'
assert error_lines[0] == 'ERROR: Tenant does not exist (2131)'
assert lines[6] == 'Using the default tenant'
assert error_lines[1] == 'ERROR: Tenant `tenant\' does not exist'
process = subprocess.Popen(command_template[:-1], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=fdbcli_env)
cmd_sequence = ['writemode on', 'deletetenant tenant2', 'usetenant tenant2', 'clear tenant_test', 'defaulttenant', 'deletetenant tenant2']
output, error_output = process.communicate(input='\n'.join(cmd_sequence).encode())
lines = output.decode().strip().split('\n')[-4:]
error_lines = error_output.decode().strip().split('\n')[-1:]
assert error_lines[0] == 'ERROR: Cannot delete a non-empty tenant (2133)'
assert lines[0] == 'Using tenant `tenant2\''
assert lines[1].startswith('Committed')
assert lines[2] == 'Using the default tenant'
assert lines[3] == 'The tenant `tenant2\' has been deleted'
run_fdbcli_command('writemode on; clear tenant_test')
if __name__ == '__main__':
parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
@ -586,6 +683,7 @@ if __name__ == '__main__':
transaction()
throttle()
triggerddteaminfolog()
tenants()
else:
assert args.process_number > 1, "Process number should be positive"
coordinators()

View File

@ -153,6 +153,27 @@ If ``description=<DESC>`` is specified, the description field in the cluster fil
For more information on setting the cluster description, see :ref:`configuration-setting-cluster-description`.
createtenant
------------
The ``createtenant`` command is used to create new tenants in the cluster. Its syntax is ``createtenant <TENANT_NAME>``.
The tenant name can be any byte string that does not begin with the ``\xff`` byte. If the tenant already exists, ``fdbcli`` will report an error.
defaulttenant
-------------
The ``defaulttenant`` command configures ``fdbcli`` to run its commands without a tenant. This is the default behavior.
The active tenant cannot be changed while a transaction (using ``begin``) is open.
deletetenant
------------
The ``deletetenant`` command is used to delete tenants from the cluster. Its syntax is ``deletetenant <TENANT_NAME>``.
In order to delete a tenant, it must be empty. To delete a tenant with data, first clear that data using the ``clear`` command. If the tenant does not exist, ``fdbcli`` will report an error.
exclude
-------
@ -210,6 +231,13 @@ The ``getrangekeys`` command fetches keys in a range. Its syntax is ``getrangeke
Note that :ref:`characters can be escaped <cli-escaping>` when specifying keys (or values) in ``fdbcli``.
gettenant
---------
The ``gettenant`` command fetches metadata for a given tenant and displays it. Its syntax is ``gettenant <TENANT_NAME>``.
Included in the output of this command are the ``id`` and ``prefix`` assigned to the tenant. If the tenant does not exist, ``fdbcli`` will report an error.
getversion
----------
@ -300,6 +328,13 @@ Attempts to kill all specified processes. Each address should include the IP and
Attempts to kill all known processes in the cluster.
listtenants
-----------
The ``listtenants`` command prints the names of tenants in the cluster. Its syntax is ``listtenants [BEGIN] [END] [LIMIT]``.
By default, the ``listtenants`` command will print up to 100 entries from the entire range of tenants. A narrower sub-range can be printed using the optional ``[BEGIN]`` and ``[END]`` parameters, and the limit can be changed by specifying an integer ``[LIMIT]`` parameter.
lock
----
@ -512,6 +547,17 @@ unlock
The ``unlock`` command unlocks the database with the specified lock UID. Because this is a potentially dangerous operation, users must copy a passphrase before the unlock command is executed.
usetenant
---------
The ``usetenant`` command configures ``fdbcli`` to run transactions within the specified tenant. Its syntax is ``usetenant <TENANT_NAME>``.
When configured, transactions will read and write keys from the key-space associated with the specified tenant. By default, ``fdbcli`` runs without a tenant. Management operations that modify keys (e.g. ``exclude``) will not operate within the tenant.
If the tenant chosen does not exist, ``fdbcli`` will report an error.
The active tenant cannot be changed while a transaction (using ``begin``) is open.
writemode
---------

View File

@ -62,12 +62,27 @@ ACTOR Future<Void> setBlobRange(Database db, Key startKey, Key endKey, Value val
namespace fdb_cli {
ACTOR Future<bool> blobRangeCommandActor(Database localDb, std::vector<StringRef> tokens) {
ACTOR Future<bool> blobRangeCommandActor(Database localDb,
Optional<TenantMapEntry> tenantEntry,
std::vector<StringRef> tokens) {
// enables blob writing for the given range
if (tokens.size() != 4) {
printUsage(tokens[0]);
return false;
} else if (tokens[3] > LiteralStringRef("\xff")) {
}
Key begin;
Key end;
if (tenantEntry.present()) {
begin = tokens[2].withPrefix(tenantEntry.get().prefix);
end = tokens[3].withPrefix(tenantEntry.get().prefix);
} else {
begin = tokens[2];
end = tokens[3];
}
if (end > LiteralStringRef("\xff")) {
// TODO is this something we want?
printf("Cannot blobbify system keyspace! Problematic End Key: %s\n", tokens[3].printable().c_str());
return false;
@ -78,12 +93,12 @@ ACTOR Future<bool> blobRangeCommandActor(Database localDb, std::vector<StringRef
printf("Starting blobbify range for [%s - %s)\n",
tokens[2].printable().c_str(),
tokens[3].printable().c_str());
wait(setBlobRange(localDb, tokens[2], tokens[3], LiteralStringRef("1")));
wait(setBlobRange(localDb, begin, end, LiteralStringRef("1")));
} else if (tokencmp(tokens[1], "stop")) {
printf("Stopping blobbify range for [%s - %s)\n",
tokens[2].printable().c_str(),
tokens[3].printable().c_str());
wait(setBlobRange(localDb, tokens[2], tokens[3], StringRef()));
wait(setBlobRange(localDb, begin, end, StringRef()));
} else {
printUsage(tokens[0]);
printf("Usage: blobrange <start|stop> <startkey> <endkey>");

View File

@ -24,6 +24,7 @@ set(FDBCLI_SRCS
SnapshotCommand.actor.cpp
StatusCommand.actor.cpp
SuspendCommand.actor.cpp
TenantCommands.actor.cpp
ThrottleCommand.actor.cpp
TriggerDDTeamInfoLogCommand.actor.cpp
TssqCommand.actor.cpp

View File

@ -75,7 +75,10 @@ ACTOR Future<Void> requestVersionUpdate(Database localDb, Reference<ChangeFeedDa
}
}
ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRef> tokens, Future<Void> warn) {
ACTOR Future<bool> changeFeedCommandActor(Database localDb,
Optional<TenantMapEntry> tenantEntry,
std::vector<StringRef> tokens,
Future<Void> warn) {
if (tokens.size() == 1) {
printUsage(tokens[0]);
return false;
@ -92,8 +95,15 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
printUsage(tokens[0]);
return false;
}
wait(updateChangeFeed(
localDb, tokens[2], ChangeFeedStatus::CHANGE_FEED_CREATE, KeyRangeRef(tokens[3], tokens[4])));
KeyRange range;
if (tenantEntry.present()) {
range = KeyRangeRef(tokens[3], tokens[4]).withPrefix(tenantEntry.get().prefix);
} else {
range = KeyRangeRef(tokens[3], tokens[4]);
}
wait(updateChangeFeed(localDb, tokens[2], ChangeFeedStatus::CHANGE_FEED_CREATE, range));
} else if (tokencmp(tokens[1], "stop")) {
if (tokens.size() != 3) {
printUsage(tokens[0]);

View File

@ -264,7 +264,8 @@ CommandFactory configureFactory(
"<single|double|triple|three_data_hall|three_datacenter|ssd|memory|memory-radixtree-beta|proxies=<PROXIES>|"
"commit_proxies=<COMMIT_PROXIES>|grv_proxies=<GRV_PROXIES>|logs=<LOGS>|resolvers=<RESOLVERS>>*|"
"count=<TSS_COUNT>|perpetual_storage_wiggle=<WIGGLE_SPEED>|perpetual_storage_wiggle_locality="
"<<LOCALITY_KEY>:<LOCALITY_VALUE>|0>|storage_migration_type={disabled|gradual|aggressive}",
"<<LOCALITY_KEY>:<LOCALITY_VALUE>|0>|storage_migration_type={disabled|gradual|aggressive}"
"|tenant_mode={disabled|optional_experimental|required_experimental}",
"change the database configuration",
"The `new' option, if present, initializes a new database with the given configuration rather than changing "
"the configuration of an existing one. When used, both a redundancy mode and a storage engine must be "
@ -295,6 +296,10 @@ CommandFactory configureFactory(
"perpetual_storage_wiggle_locality=<<LOCALITY_KEY>:<LOCALITY_VALUE>|0>: Set the process filter for wiggling. "
"The processes that match the given locality key and locality value are only wiggled. The value 0 will disable "
"the locality filter and matches all the processes for wiggling.\n\n"
"tenant_mode=<disabled|optional_experimental|required_experimental>: Sets the tenant mode for the cluster. If "
"optional, then transactions can be run with or without specifying tenants. If required, all data must be "
"accessed using tenants.\n\n"
"See the FoundationDB Administration Guide for more information."));
} // namespace fdb_cli

View File

@ -0,0 +1,249 @@
/*
* TenantCommands.actor.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2022 Apple Inc. and the FoundationDB project 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.
*/
#include "fdbcli/fdbcli.actor.h"
#include "fdbclient/FDBOptions.g.h"
#include "fdbclient/IClientApi.h"
#include "fdbclient/Knobs.h"
#include "fdbclient/ManagementAPI.actor.h"
#include "fdbclient/Schemas.h"
#include "flow/Arena.h"
#include "flow/FastRef.h"
#include "flow/ThreadHelper.actor.h"
#include "flow/actorcompiler.h" // This must be the last #include.
namespace fdb_cli {
const KeyRangeRef tenantSpecialKeyRange(LiteralStringRef("\xff\xff/management/tenant_map/"),
LiteralStringRef("\xff\xff/management/tenant_map0"));
// createtenant command
ACTOR Future<bool> createTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() != 2) {
printUsage(tokens[0]);
return false;
}
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
state bool doneExistenceCheck = false;
loop {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
try {
if (!doneExistenceCheck) {
Optional<Value> existingTenant = wait(safeThreadFutureToFuture(tr->get(tenantNameKey)));
if (existingTenant.present()) {
throw tenant_already_exists();
}
doneExistenceCheck = true;
}
tr->set(tenantNameKey, ValueRef());
wait(safeThreadFutureToFuture(tr->commit()));
break;
} catch (Error& e) {
state Error err(e);
if (e.code() == error_code_special_keys_api_failure) {
std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr));
fprintf(stderr, "ERROR: %s\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
printf("The tenant `%s' has been created\n", printable(tokens[1]).c_str());
return true;
}
CommandFactory createTenantFactory("createtenant",
CommandHelp("createtenant <TENANT_NAME>",
"creates a new tenant in the cluster",
"Creates a new tenant in the cluster with the specified name."));
// deletetenant command
ACTOR Future<bool> deleteTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() != 2) {
printUsage(tokens[0]);
return false;
}
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
state bool doneExistenceCheck = false;
loop {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
try {
if (!doneExistenceCheck) {
Optional<Value> existingTenant = wait(safeThreadFutureToFuture(tr->get(tenantNameKey)));
if (!existingTenant.present()) {
throw tenant_not_found();
}
doneExistenceCheck = true;
}
tr->clear(tenantNameKey);
wait(safeThreadFutureToFuture(tr->commit()));
break;
} catch (Error& e) {
state Error err(e);
if (e.code() == error_code_special_keys_api_failure) {
std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr));
fprintf(stderr, "ERROR: %s\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
printf("The tenant `%s' has been deleted\n", printable(tokens[1]).c_str());
return true;
}
CommandFactory deleteTenantFactory(
"deletetenant",
CommandHelp(
"deletetenant <TENANT_NAME>",
"deletes a tenant from the cluster",
"Deletes a tenant from the cluster. Deletion will be allowed only if the specified tenant contains no data."));
// listtenants command
ACTOR Future<bool> listTenantsCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() > 4) {
printUsage(tokens[0]);
return false;
}
StringRef beginTenant = ""_sr;
StringRef endTenant = "\xff\xff"_sr;
state int limit = 100;
if (tokens.size() >= 2) {
beginTenant = tokens[1];
}
if (tokens.size() >= 3) {
endTenant = tokens[2];
if (endTenant <= beginTenant) {
fprintf(stderr, "ERROR: end must be larger than begin");
return false;
}
}
if (tokens.size() == 4) {
int n = 0;
if (sscanf(tokens[3].toString().c_str(), "%d%n", &limit, &n) != 1 || n != tokens[3].size()) {
fprintf(stderr, "ERROR: invalid limit %s\n", tokens[3].toString().c_str());
return false;
}
}
state Key beginTenantKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(beginTenant);
state Key endTenantKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(endTenant);
state Reference<ITransaction> tr = db->createTransaction();
loop {
try {
RangeResult tenants = wait(safeThreadFutureToFuture(
tr->getRange(firstGreaterOrEqual(beginTenantKey), firstGreaterOrEqual(endTenantKey), limit)));
if (tenants.empty()) {
if (tokens.size() == 1) {
printf("The cluster has no tenants\n");
} else {
printf("The cluster has no tenants in the specified range\n");
}
}
int index = 0;
for (auto tenant : tenants) {
printf(" %d. %s\n",
++index,
printable(tenant.key.removePrefix(fdb_cli::tenantSpecialKeyRange.begin)).c_str());
}
return true;
} catch (Error& e) {
state Error err(e);
if (e.code() == error_code_special_keys_api_failure) {
std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr));
fprintf(stderr, "ERROR: %s\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
}
CommandFactory listTenantsFactory(
"listtenants",
CommandHelp("listtenants [BEGIN] [END] [LIMIT]",
"print a list of tenants in the cluster",
"Print a list of tenants in the cluster. Only tenants in the range [BEGIN] - [END] will be printed. "
"The number of tenants to print can be specified using the [LIMIT] parameter, which defaults to 100."));
// gettenant command
ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() != 2) {
printUsage(tokens[0]);
return false;
}
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
loop {
try {
Optional<Value> tenant = wait(safeThreadFutureToFuture(tr->get(tenantNameKey)));
if (!tenant.present()) {
throw tenant_not_found();
}
json_spirit::mValue jsonObject;
json_spirit::read_string(tenant.get().toString(), jsonObject);
JSONDoc doc(jsonObject);
int64_t id;
std::string prefix;
doc.get("id", id);
doc.get("prefix", prefix);
printf(" id: %" PRId64 "\n", id);
printf(" prefix: %s\n", printable(prefix).c_str());
return true;
} catch (Error& e) {
state Error err(e);
if (e.code() == error_code_special_keys_api_failure) {
std::string errorMsgStr = wait(fdb_cli::getSpecialKeysFailureErrorMessage(tr));
fprintf(stderr, "ERROR: %s\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
}
CommandFactory getTenantFactory("gettenant",
CommandHelp("gettenant <TENANT_NAME>",
"prints the metadata for a tenant",
"Prints the metadata for a tenant."));
} // namespace fdb_cli

View File

@ -55,6 +55,7 @@
#include "fdbcli/fdbcli.actor.h"
#include <cinttypes>
#include <cstdio>
#include <type_traits>
#include <signal.h>
@ -530,6 +531,19 @@ void initHelp() {
helpMap["writemode"] = CommandHelp("writemode <on|off>",
"enables or disables sets and clears",
"Setting or clearing keys from the CLI is not recommended.");
helpMap["usetenant"] =
CommandHelp("usetenant [NAME]",
"prints or configures the tenant used for transactions",
"If no name is given, prints the name of the current active tenant. Otherwise, all commands that "
"are used to read or write keys are done inside the tenant with the specified NAME. By default, "
"no tenant is configured and operations are performed on the raw key-space. The tenant cannot be "
"configured while a transaction started with `begin' is open.");
helpMap["defaulttenant"] =
CommandHelp("defaulttenant",
"configures transactions to not use a named tenant",
"All commands that are used to read or write keys will be done without a tenant and will operate "
"on the raw key-space. This is the default behavior. The tenant cannot be configured while a "
"transaction started with `begin' is open.");
}
void printVersion() {
@ -670,13 +684,18 @@ ACTOR Future<bool> createSnapshot(Database db, std::vector<StringRef> tokens) {
// TODO: Update the function to get rid of the Database after refactoring
Reference<ITransaction> getTransaction(Reference<IDatabase> db,
Reference<ITenant> tenant,
Reference<ITransaction>& tr,
FdbOptions* options,
bool intrans) {
// Update "tr" to point to a brand new transaction object when it's not initialized or "intrans" flag is "false",
// which indicates we need a new transaction object
if (!tr || !intrans) {
tr = db->createTransaction();
if (tenant) {
tr = tenant->createTransaction();
} else {
tr = db->createTransaction();
}
options->apply(tr);
}
@ -769,7 +788,8 @@ void configureGenerator(const char* text, const char* line, std::vector<std::str
"resolvers=",
"perpetual_storage_wiggle=",
"perpetual_storage_wiggle_locality=",
"storage_migration_type=",
"storage_migration_type="
"tenant_mode=",
nullptr };
arrayGenerator(text, line, opts, lc);
}
@ -1152,6 +1172,13 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
state Database localDb;
state Reference<IDatabase> db;
state Reference<ITenant> tenant;
state Optional<Standalone<StringRef>> tenantName;
state Optional<TenantMapEntry> tenantEntry;
// This tenant is kept empty for operations that perform management tasks (e.g. killing a process)
state const Reference<ITenant> managementTenant;
state Reference<ITransaction> tr;
state Transaction trx;
@ -1212,7 +1239,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
// The 3.0 timeout is a guard to avoid waiting forever when the cli cannot talk to any coordinators
loop {
try {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
wait(delay(3.0) || success(safeThreadFutureToFuture(tr->getReadVersion())));
break;
@ -1373,8 +1400,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "waitopen")) {
wait(makeInterruptable(
success(safeThreadFutureToFuture(getTransaction(db, tr, options, intrans)->getReadVersion()))));
wait(makeInterruptable(success(safeThreadFutureToFuture(
getTransaction(db, managementTenant, tr, options, intrans)->getReadVersion()))));
continue;
}
@ -1478,14 +1505,14 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "changefeed")) {
bool _result = wait(makeInterruptable(changeFeedCommandActor(localDb, tokens, warn)));
bool _result = wait(makeInterruptable(changeFeedCommandActor(localDb, tenantEntry, tokens, warn)));
if (!_result)
is_error = true;
continue;
}
if (tokencmp(tokens[0], "blobrange")) {
bool _result = wait(makeInterruptable(blobRangeCommandActor(localDb, tokens)));
bool _result = wait(makeInterruptable(blobRangeCommandActor(localDb, tenantEntry, tokens)));
if (!_result)
is_error = true;
continue;
@ -1536,7 +1563,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} else {
activeOptions = FdbOptions(globalOptions);
options = &activeOptions;
getTransaction(db, tr, options, false);
getTransaction(db, tenant, tr, options, false);
intrans = true;
printf("Transaction started\n");
}
@ -1597,7 +1624,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
is_error = true;
} else {
state ThreadFuture<Optional<Value>> valueF =
getTransaction(db, tr, options, intrans)->get(tokens[1]);
getTransaction(db, tenant, tr, options, intrans)->get(tokens[1]);
Optional<Standalone<StringRef>> v = wait(makeInterruptable(safeThreadFutureToFuture(valueF)));
if (v.present())
@ -1613,8 +1640,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]);
is_error = true;
} else {
Version v = wait(makeInterruptable(
safeThreadFutureToFuture(getTransaction(db, tr, options, intrans)->getReadVersion())));
Version v = wait(makeInterruptable(safeThreadFutureToFuture(
getTransaction(db, tenant, tr, options, intrans)->getReadVersion())));
fmt::print("{}\n", v);
}
continue;
@ -1628,7 +1655,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "kill")) {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(killCommandActor(db, tr, tokens, &address_interface)));
if (!_result)
is_error = true;
@ -1636,7 +1663,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "suspend")) {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(suspendCommandActor(db, tr, tokens, &address_interface)));
if (!_result)
is_error = true;
@ -1658,7 +1685,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "consistencycheck")) {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(consistencyCheckCommandActor(tr, tokens, intrans)));
if (!_result)
is_error = true;
@ -1666,7 +1693,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "profile")) {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(profileCommandActor(tr, tokens, intrans)));
if (!_result)
is_error = true;
@ -1674,7 +1701,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
}
if (tokencmp(tokens[0], "expensive_data_check")) {
getTransaction(db, tr, options, intrans);
getTransaction(db, managementTenant, tr, options, intrans);
bool _result =
wait(makeInterruptable(expensiveDataCheckCommandActor(db, tr, tokens, &address_interface)));
if (!_result)
@ -1734,8 +1761,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
endKey = strinc(tokens[1]);
}
state ThreadFuture<RangeResult> kvsF =
getTransaction(db, tr, options, intrans)->getRange(KeyRangeRef(tokens[1], endKey), limit);
getTransaction(db, tenant, tr, options, intrans);
state ThreadFuture<RangeResult> kvsF = tr->getRange(KeyRangeRef(tokens[1], endKey), limit);
RangeResult kvs = wait(makeInterruptable(safeThreadFutureToFuture(kvsF)));
printf("\nRange limited to %d keys\n", limit);
@ -1779,7 +1806,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]);
is_error = true;
} else {
getTransaction(db, tr, options, intrans);
getTransaction(db, tenant, tr, options, intrans);
tr->set(tokens[1], tokens[2]);
if (!intrans) {
@ -1800,7 +1827,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]);
is_error = true;
} else {
getTransaction(db, tr, options, intrans);
getTransaction(db, tenant, tr, options, intrans);
tr->clear(tokens[1]);
if (!intrans) {
@ -1821,7 +1848,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]);
is_error = true;
} else {
getTransaction(db, tr, options, intrans);
getTransaction(db, tenant, tr, options, intrans);
tr->clear(KeyRangeRef(tokens[1], tokens[2]));
if (!intrans) {
@ -1910,6 +1937,86 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
continue;
}
if (tokencmp(tokens[0], "usetenant")) {
if (tokens.size() > 2) {
printUsage(tokens[0]);
is_error = true;
} else if (intrans && tokens.size() == 2) {
fprintf(stderr, "ERROR: Tenant cannot be changed while a transaction is open\n");
is_error = true;
} else if (tokens.size() == 1) {
if (!tenantName.present()) {
printf("Using the default tenant\n");
} else {
printf("Using tenant `%s'\n", printable(tenantName.get()).c_str());
}
} else {
Optional<TenantMapEntry> entry =
wait(makeInterruptable(ManagementAPI::tryGetTenant(db, tokens[1])));
if (!entry.present()) {
fprintf(stderr, "ERROR: Tenant `%s' does not exist\n", printable(tokens[1]).c_str());
is_error = true;
} else {
tenant = db->openTenant(tokens[1]);
tenantName = tokens[1];
tenantEntry = entry;
printf("Using tenant `%s'\n", printable(tokens[1]).c_str());
}
}
continue;
}
if (tokencmp(tokens[0], "defaulttenant")) {
if (tokens.size() != 1) {
printUsage(tokens[0]);
is_error = true;
} else if (intrans) {
fprintf(stderr, "ERROR: Tenant cannot be changed while a transaction is open\n");
is_error = true;
} else {
tenant = Reference<ITenant>();
tenantName = Optional<Standalone<StringRef>>();
tenantEntry = Optional<TenantMapEntry>();
printf("Using the default tenant\n");
}
continue;
}
if (tokencmp(tokens[0], "createtenant")) {
bool _result = wait(makeInterruptable(createTenantCommandActor(db, tokens)));
if (!_result)
is_error = true;
continue;
}
if (tokencmp(tokens[0], "deletetenant")) {
bool _result = wait(makeInterruptable(deleteTenantCommandActor(db, tokens)));
if (!_result)
is_error = true;
else if (tenantName.present() && tokens[1] == tenantName.get()) {
printAtCol("WARNING: the active tenant was deleted. Use the `usetenant' or `defaulttenant' "
"command to choose a new tenant.\n",
80);
}
continue;
}
if (tokencmp(tokens[0], "listtenants")) {
bool _result = wait(makeInterruptable(listTenantsCommandActor(db, tokens)));
if (!_result)
is_error = true;
continue;
}
if (tokencmp(tokens[0], "gettenant")) {
bool _result = wait(makeInterruptable(getTenantCommandActor(db, tokens)));
if (!_result)
is_error = true;
continue;
}
fprintf(stderr, "ERROR: Unknown command `%s'. Try `help'?\n", formatStringRef(tokens[0]).c_str());
is_error = true;
}
@ -1917,8 +2024,14 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
TraceEvent(SevInfo, "CLICommandLog", randomID).detail("Command", line).detail("IsError", is_error);
} catch (Error& e) {
if (e.code() != error_code_actor_cancelled)
if (e.code() == error_code_tenant_name_required) {
printAtCol("ERROR: tenant name required. Use the `usetenant' command to select a tenant or enable the "
"`RAW_ACCESS' option to read raw keys.",
80,
stderr);
} else if (e.code() != error_code_actor_cancelled) {
fprintf(stderr, "ERROR: %s (%d)\n", e.what(), e.code());
}
is_error = true;
if (intrans) {
printf("Rolling back current transaction\n");

View File

@ -138,8 +138,12 @@ ACTOR Future<bool> consistencyCheckCommandActor(Reference<ITransaction> tr,
bool intrans);
// coordinators command
ACTOR Future<bool> coordinatorsCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// createtenant command
ACTOR Future<bool> createTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// datadistribution command
ACTOR Future<bool> dataDistributionCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// deletetenant command
ACTOR Future<bool> deleteTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// exclude command
ACTOR Future<bool> excludeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens, Future<Void> warn);
// expensive_data_check command
@ -155,6 +159,8 @@ ACTOR Future<bool> fileConfigureCommandActor(Reference<IDatabase> db,
bool force);
// force_recovery_with_data_loss command
ACTOR Future<bool> forceRecoveryWithDataLossCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// gettenant command
ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// include command
ACTOR Future<bool> includeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// kill command
@ -162,13 +168,20 @@ ACTOR Future<bool> killCommandActor(Reference<IDatabase> db,
Reference<ITransaction> tr,
std::vector<StringRef> tokens,
std::map<Key, std::pair<Value, ClientLeaderRegInterface>>* address_interface);
// listtenants command
ACTOR Future<bool> listTenantsCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// lock/unlock command
ACTOR Future<bool> lockCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
ACTOR Future<bool> unlockDatabaseActor(Reference<IDatabase> db, UID uid);
// changefeed command
ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRef> tokens, Future<Void> warn);
ACTOR Future<bool> changeFeedCommandActor(Database localDb,
Optional<TenantMapEntry> tenantEntry,
std::vector<StringRef> tokens,
Future<Void> warn);
// blobrange command
ACTOR Future<bool> blobRangeCommandActor(Database localDb, std::vector<StringRef> tokens);
ACTOR Future<bool> blobRangeCommandActor(Database localDb,
Optional<TenantMapEntry> tenantEntry,
std::vector<StringRef> tokens);
// maintenance command
ACTOR Future<bool> setHealthyZone(Reference<IDatabase> db, StringRef zoneId, double seconds, bool printWarning = false);
ACTOR Future<bool> clearHealthyZone(Reference<IDatabase> db,