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') output = run_fdbcli_command('triggerddteaminfolog')
assert output == 'Triggered team info logging in data distribution.' 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__': if __name__ == '__main__':
parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
@ -586,6 +683,7 @@ if __name__ == '__main__':
transaction() transaction()
throttle() throttle()
triggerddteaminfolog() triggerddteaminfolog()
tenants()
else: else:
assert args.process_number > 1, "Process number should be positive" assert args.process_number > 1, "Process number should be positive"
coordinators() 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`. 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 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``. 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 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. 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 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. 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 writemode
--------- ---------

View File

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

View File

@ -24,6 +24,7 @@ set(FDBCLI_SRCS
SnapshotCommand.actor.cpp SnapshotCommand.actor.cpp
StatusCommand.actor.cpp StatusCommand.actor.cpp
SuspendCommand.actor.cpp SuspendCommand.actor.cpp
TenantCommands.actor.cpp
ThrottleCommand.actor.cpp ThrottleCommand.actor.cpp
TriggerDDTeamInfoLogCommand.actor.cpp TriggerDDTeamInfoLogCommand.actor.cpp
TssqCommand.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) { if (tokens.size() == 1) {
printUsage(tokens[0]); printUsage(tokens[0]);
return false; return false;
@ -92,8 +95,15 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
printUsage(tokens[0]); printUsage(tokens[0]);
return false; 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")) { } else if (tokencmp(tokens[1], "stop")) {
if (tokens.size() != 3) { if (tokens.size() != 3) {
printUsage(tokens[0]); 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>|" "<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>>*|" "commit_proxies=<COMMIT_PROXIES>|grv_proxies=<GRV_PROXIES>|logs=<LOGS>|resolvers=<RESOLVERS>>*|"
"count=<TSS_COUNT>|perpetual_storage_wiggle=<WIGGLE_SPEED>|perpetual_storage_wiggle_locality=" "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", "change the database configuration",
"The `new' option, if present, initializes a new database with the given configuration rather than changing " "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 " "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. " "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 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" "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.")); "See the FoundationDB Administration Guide for more information."));
} // namespace fdb_cli } // 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 "fdbcli/fdbcli.actor.h"
#include <cinttypes> #include <cinttypes>
#include <cstdio>
#include <type_traits> #include <type_traits>
#include <signal.h> #include <signal.h>
@ -530,6 +531,19 @@ void initHelp() {
helpMap["writemode"] = CommandHelp("writemode <on|off>", helpMap["writemode"] = CommandHelp("writemode <on|off>",
"enables or disables sets and clears", "enables or disables sets and clears",
"Setting or clearing keys from the CLI is not recommended."); "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() { 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 // TODO: Update the function to get rid of the Database after refactoring
Reference<ITransaction> getTransaction(Reference<IDatabase> db, Reference<ITransaction> getTransaction(Reference<IDatabase> db,
Reference<ITenant> tenant,
Reference<ITransaction>& tr, Reference<ITransaction>& tr,
FdbOptions* options, FdbOptions* options,
bool intrans) { bool intrans) {
// Update "tr" to point to a brand new transaction object when it's not initialized or "intrans" flag is "false", // 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 // which indicates we need a new transaction object
if (!tr || !intrans) { if (!tr || !intrans) {
tr = db->createTransaction(); if (tenant) {
tr = tenant->createTransaction();
} else {
tr = db->createTransaction();
}
options->apply(tr); options->apply(tr);
} }
@ -769,7 +788,8 @@ void configureGenerator(const char* text, const char* line, std::vector<std::str
"resolvers=", "resolvers=",
"perpetual_storage_wiggle=", "perpetual_storage_wiggle=",
"perpetual_storage_wiggle_locality=", "perpetual_storage_wiggle_locality=",
"storage_migration_type=", "storage_migration_type="
"tenant_mode=",
nullptr }; nullptr };
arrayGenerator(text, line, opts, lc); arrayGenerator(text, line, opts, lc);
} }
@ -1152,6 +1172,13 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
state Database localDb; state Database localDb;
state Reference<IDatabase> db; 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 Reference<ITransaction> tr;
state Transaction trx; 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 // The 3.0 timeout is a guard to avoid waiting forever when the cli cannot talk to any coordinators
loop { loop {
try { try {
getTransaction(db, tr, options, intrans); getTransaction(db, managementTenant, tr, options, intrans);
tr->setOption(FDBTransactionOptions::LOCK_AWARE); tr->setOption(FDBTransactionOptions::LOCK_AWARE);
wait(delay(3.0) || success(safeThreadFutureToFuture(tr->getReadVersion()))); wait(delay(3.0) || success(safeThreadFutureToFuture(tr->getReadVersion())));
break; break;
@ -1373,8 +1400,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "waitopen")) { if (tokencmp(tokens[0], "waitopen")) {
wait(makeInterruptable( wait(makeInterruptable(success(safeThreadFutureToFuture(
success(safeThreadFutureToFuture(getTransaction(db, tr, options, intrans)->getReadVersion())))); getTransaction(db, managementTenant, tr, options, intrans)->getReadVersion()))));
continue; continue;
} }
@ -1478,14 +1505,14 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "changefeed")) { if (tokencmp(tokens[0], "changefeed")) {
bool _result = wait(makeInterruptable(changeFeedCommandActor(localDb, tokens, warn))); bool _result = wait(makeInterruptable(changeFeedCommandActor(localDb, tenantEntry, tokens, warn)));
if (!_result) if (!_result)
is_error = true; is_error = true;
continue; continue;
} }
if (tokencmp(tokens[0], "blobrange")) { if (tokencmp(tokens[0], "blobrange")) {
bool _result = wait(makeInterruptable(blobRangeCommandActor(localDb, tokens))); bool _result = wait(makeInterruptable(blobRangeCommandActor(localDb, tenantEntry, tokens)));
if (!_result) if (!_result)
is_error = true; is_error = true;
continue; continue;
@ -1536,7 +1563,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} else { } else {
activeOptions = FdbOptions(globalOptions); activeOptions = FdbOptions(globalOptions);
options = &activeOptions; options = &activeOptions;
getTransaction(db, tr, options, false); getTransaction(db, tenant, tr, options, false);
intrans = true; intrans = true;
printf("Transaction started\n"); printf("Transaction started\n");
} }
@ -1597,7 +1624,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
is_error = true; is_error = true;
} else { } else {
state ThreadFuture<Optional<Value>> valueF = 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))); Optional<Standalone<StringRef>> v = wait(makeInterruptable(safeThreadFutureToFuture(valueF)));
if (v.present()) if (v.present())
@ -1613,8 +1640,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]); printUsage(tokens[0]);
is_error = true; is_error = true;
} else { } else {
Version v = wait(makeInterruptable( Version v = wait(makeInterruptable(safeThreadFutureToFuture(
safeThreadFutureToFuture(getTransaction(db, tr, options, intrans)->getReadVersion()))); getTransaction(db, tenant, tr, options, intrans)->getReadVersion())));
fmt::print("{}\n", v); fmt::print("{}\n", v);
} }
continue; continue;
@ -1628,7 +1655,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "kill")) { 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))); bool _result = wait(makeInterruptable(killCommandActor(db, tr, tokens, &address_interface)));
if (!_result) if (!_result)
is_error = true; is_error = true;
@ -1636,7 +1663,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "suspend")) { 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))); bool _result = wait(makeInterruptable(suspendCommandActor(db, tr, tokens, &address_interface)));
if (!_result) if (!_result)
is_error = true; is_error = true;
@ -1658,7 +1685,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "consistencycheck")) { if (tokencmp(tokens[0], "consistencycheck")) {
getTransaction(db, tr, options, intrans); getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(consistencyCheckCommandActor(tr, tokens, intrans))); bool _result = wait(makeInterruptable(consistencyCheckCommandActor(tr, tokens, intrans)));
if (!_result) if (!_result)
is_error = true; is_error = true;
@ -1666,7 +1693,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "profile")) { if (tokencmp(tokens[0], "profile")) {
getTransaction(db, tr, options, intrans); getTransaction(db, managementTenant, tr, options, intrans);
bool _result = wait(makeInterruptable(profileCommandActor(tr, tokens, intrans))); bool _result = wait(makeInterruptable(profileCommandActor(tr, tokens, intrans)));
if (!_result) if (!_result)
is_error = true; is_error = true;
@ -1674,7 +1701,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
} }
if (tokencmp(tokens[0], "expensive_data_check")) { if (tokencmp(tokens[0], "expensive_data_check")) {
getTransaction(db, tr, options, intrans); getTransaction(db, managementTenant, tr, options, intrans);
bool _result = bool _result =
wait(makeInterruptable(expensiveDataCheckCommandActor(db, tr, tokens, &address_interface))); wait(makeInterruptable(expensiveDataCheckCommandActor(db, tr, tokens, &address_interface)));
if (!_result) if (!_result)
@ -1734,8 +1761,8 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
endKey = strinc(tokens[1]); endKey = strinc(tokens[1]);
} }
state ThreadFuture<RangeResult> kvsF = getTransaction(db, tenant, tr, options, intrans);
getTransaction(db, tr, options, intrans)->getRange(KeyRangeRef(tokens[1], endKey), limit); state ThreadFuture<RangeResult> kvsF = tr->getRange(KeyRangeRef(tokens[1], endKey), limit);
RangeResult kvs = wait(makeInterruptable(safeThreadFutureToFuture(kvsF))); RangeResult kvs = wait(makeInterruptable(safeThreadFutureToFuture(kvsF)));
printf("\nRange limited to %d keys\n", limit); printf("\nRange limited to %d keys\n", limit);
@ -1779,7 +1806,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]); printUsage(tokens[0]);
is_error = true; is_error = true;
} else { } else {
getTransaction(db, tr, options, intrans); getTransaction(db, tenant, tr, options, intrans);
tr->set(tokens[1], tokens[2]); tr->set(tokens[1], tokens[2]);
if (!intrans) { if (!intrans) {
@ -1800,7 +1827,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]); printUsage(tokens[0]);
is_error = true; is_error = true;
} else { } else {
getTransaction(db, tr, options, intrans); getTransaction(db, tenant, tr, options, intrans);
tr->clear(tokens[1]); tr->clear(tokens[1]);
if (!intrans) { if (!intrans) {
@ -1821,7 +1848,7 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
printUsage(tokens[0]); printUsage(tokens[0]);
is_error = true; is_error = true;
} else { } else {
getTransaction(db, tr, options, intrans); getTransaction(db, tenant, tr, options, intrans);
tr->clear(KeyRangeRef(tokens[1], tokens[2])); tr->clear(KeyRangeRef(tokens[1], tokens[2]));
if (!intrans) { if (!intrans) {
@ -1910,6 +1937,86 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
continue; 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()); fprintf(stderr, "ERROR: Unknown command `%s'. Try `help'?\n", formatStringRef(tokens[0]).c_str());
is_error = true; 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); TraceEvent(SevInfo, "CLICommandLog", randomID).detail("Command", line).detail("IsError", is_error);
} catch (Error& e) { } 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()); fprintf(stderr, "ERROR: %s (%d)\n", e.what(), e.code());
}
is_error = true; is_error = true;
if (intrans) { if (intrans) {
printf("Rolling back current transaction\n"); printf("Rolling back current transaction\n");

View File

@ -138,8 +138,12 @@ ACTOR Future<bool> consistencyCheckCommandActor(Reference<ITransaction> tr,
bool intrans); bool intrans);
// coordinators command // coordinators command
ACTOR Future<bool> coordinatorsCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens); 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 // datadistribution command
ACTOR Future<bool> dataDistributionCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens); 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 // exclude command
ACTOR Future<bool> excludeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens, Future<Void> warn); ACTOR Future<bool> excludeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens, Future<Void> warn);
// expensive_data_check command // expensive_data_check command
@ -155,6 +159,8 @@ ACTOR Future<bool> fileConfigureCommandActor(Reference<IDatabase> db,
bool force); bool force);
// force_recovery_with_data_loss command // force_recovery_with_data_loss command
ACTOR Future<bool> forceRecoveryWithDataLossCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens); 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 // include command
ACTOR Future<bool> includeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens); ACTOR Future<bool> includeCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// kill command // kill command
@ -162,13 +168,20 @@ ACTOR Future<bool> killCommandActor(Reference<IDatabase> db,
Reference<ITransaction> tr, Reference<ITransaction> tr,
std::vector<StringRef> tokens, std::vector<StringRef> tokens,
std::map<Key, std::pair<Value, ClientLeaderRegInterface>>* address_interface); 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 // lock/unlock command
ACTOR Future<bool> lockCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens); ACTOR Future<bool> lockCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
ACTOR Future<bool> unlockDatabaseActor(Reference<IDatabase> db, UID uid); ACTOR Future<bool> unlockDatabaseActor(Reference<IDatabase> db, UID uid);
// changefeed command // 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 // 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 // maintenance command
ACTOR Future<bool> setHealthyZone(Reference<IDatabase> db, StringRef zoneId, double seconds, bool printWarning = false); ACTOR Future<bool> setHealthyZone(Reference<IDatabase> db, StringRef zoneId, double seconds, bool printWarning = false);
ACTOR Future<bool> clearHealthyZone(Reference<IDatabase> db, ACTOR Future<bool> clearHealthyZone(Reference<IDatabase> db,