Add support for tenant groups

This commit is contained in:
A.J. Beamon 2022-07-08 15:56:22 -07:00
parent 02ab3b05ab
commit c08592368f
20 changed files with 674 additions and 128 deletions

View File

@ -599,7 +599,7 @@ def tenants(logger):
output = run_fdbcli_command('createtenant tenant')
assert output == 'The tenant `tenant\' has been created'
output = run_fdbcli_command('createtenant tenant2')
output = run_fdbcli_command('createtenant tenant2 tenant_group=tenant_group2')
assert output == 'The tenant `tenant2\' has been created'
output = run_fdbcli_command('listtenants')
@ -632,6 +632,61 @@ def tenants(logger):
assert('base64' in json_output['tenant']['prefix'])
assert('printable' in json_output['tenant']['prefix'])
output = run_fdbcli_command('gettenant tenant2')
lines = output.split('\n')
assert len(lines) == 3
assert lines[0].strip().startswith('id: ')
assert lines[1].strip().startswith('prefix: ')
assert lines[2].strip() == 'tenant group: tenant_group2'
output = run_fdbcli_command('gettenant tenant2 JSON')
json_output = json.loads(output, strict=False)
assert(len(json_output) == 2)
assert('tenant' in json_output)
assert(json_output['type'] == 'success')
assert(len(json_output['tenant']) == 3)
assert('id' in json_output['tenant'])
assert('prefix' in json_output['tenant'])
assert('tenant_group' in json_output['tenant'])
assert(len(json_output['tenant']['tenant_group']) == 2)
assert('base64' in json_output['tenant']['tenant_group'])
assert(json_output['tenant']['tenant_group']['printable'] == 'tenant_group2')
output = run_fdbcli_command('configuretenant tenant tenant_group=tenant_group1')
assert output == 'The configuration for tenant `tenant\' has been updated'
output = run_fdbcli_command('gettenant tenant')
lines = output.split('\n')
assert len(lines) == 3
assert lines[2].strip() == 'tenant group: tenant_group1'
output = run_fdbcli_command('configuretenant tenant tenant_group=tenant_group1 tenant_group=tenant_group2')
assert output == 'The configuration for tenant `tenant\' has been updated'
output = run_fdbcli_command('gettenant tenant')
lines = output.split('\n')
assert len(lines) == 3
assert lines[2].strip() == 'tenant group: tenant_group2'
output = run_fdbcli_command('configuretenant tenant unset tenant_group')
assert output == 'The configuration for tenant `tenant\' has been updated'
output = run_fdbcli_command('gettenant tenant')
lines = output.split('\n')
assert len(lines) == 2
output = run_fdbcli_command_and_get_error('configuretenant tenant unset')
assert output == 'ERROR: `unset\' specified without a configuration parameter.'
output = run_fdbcli_command_and_get_error('configuretenant tenant unset tenant_group=tenant_group1')
assert output == 'ERROR: unrecognized configuration parameter `tenant_group=tenant_group1\''
output = run_fdbcli_command_and_get_error('configuretenant tenant tenant_group')
assert output == 'ERROR: invalid configuration string `tenant_group\'. String must specify a value using `=\'.'
output = run_fdbcli_command_and_get_error('configuretenant tenant3 tenant_group=tenant_group1')
assert output == 'ERROR: Tenant does not exist (2131)'
output = run_fdbcli_command('usetenant')
assert output == 'Using the default tenant'

View File

@ -35,20 +35,82 @@
namespace fdb_cli {
const KeyRangeRef tenantSpecialKeyRange(LiteralStringRef("\xff\xff/management/tenant/map/"),
LiteralStringRef("\xff\xff/management/tenant/map0"));
const KeyRangeRef tenantMapSpecialKeyRange(LiteralStringRef("\xff\xff/management/tenant/map/"),
LiteralStringRef("\xff\xff/management/tenant/map0"));
const KeyRangeRef tenantConfigSpecialKeyRange(LiteralStringRef("\xff\xff/management/tenant/configure/"),
LiteralStringRef("\xff\xff/management/tenant/configure0"));
Optional<std::map<Standalone<StringRef>, Optional<Value>>>
parseTenantConfiguration(std::vector<StringRef> const& tokens, int startIndex, bool allowUnset) {
std::map<Standalone<StringRef>, Optional<Value>> configParams;
for (int tokenNum = startIndex; tokenNum < tokens.size(); ++tokenNum) {
Optional<Value> value;
StringRef token = tokens[tokenNum];
StringRef param;
if (allowUnset && token == "unset"_sr) {
if (++tokenNum == tokens.size()) {
fmt::print(stderr, "ERROR: `unset' specified without a configuration parameter.\n");
return {};
}
param = tokens[tokenNum];
} else {
bool foundEquals;
param = token.eat("=", &foundEquals);
if (!foundEquals) {
fmt::print(stderr,
"ERROR: invalid configuration string `{}'. String must specify a value using `='.\n",
param.toString().c_str());
return {};
}
value = token;
}
if (tokencmp(param, "tenant_group")) {
configParams[param] = value;
} else {
fmt::print(stderr, "ERROR: unrecognized configuration parameter `{}'\n", param.toString().c_str());
return {};
}
}
return configParams;
}
Key makeConfigKey(TenantNameRef tenantName, StringRef configName) {
return tenantConfigSpecialKeyRange.begin.withSuffix(Tuple().append(tenantName).append(configName).pack());
}
void applyConfiguration(Reference<ITransaction> tr,
TenantNameRef tenantName,
std::map<Standalone<StringRef>, Optional<Value>> configuration) {
for (auto [configName, value] : configuration) {
if (value.present()) {
tr->set(makeConfigKey(tenantName, configName), value.get());
} else {
tr->clear(makeConfigKey(tenantName, configName));
}
}
}
// createtenant command
ACTOR Future<bool> createTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() != 2) {
if (tokens.size() < 2 || tokens.size() > 3) {
printUsage(tokens[0]);
return false;
}
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Key tenantNameKey = tenantMapSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
state bool doneExistenceCheck = false;
state Optional<std::map<Standalone<StringRef>, Optional<Value>>> configuration =
parseTenantConfiguration(tokens, 2, false);
if (!configuration.present()) {
return false;
}
loop {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
try {
@ -63,25 +125,26 @@ ACTOR Future<bool> createTenantCommandActor(Reference<IDatabase> db, std::vector
}
tr->set(tenantNameKey, ValueRef());
applyConfiguration(tr, tokens[1], configuration.get());
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());
std::string errorMsgStr = wait(getSpecialKeysFailureErrorMessage(tr));
fmt::print(stderr, "ERROR: {}\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
printf("The tenant `%s' has been created\n", printable(tokens[1]).c_str());
fmt::print("The tenant `{}' has been created\n", printable(tokens[1]).c_str());
return true;
}
CommandFactory createTenantFactory("createtenant",
CommandHelp("createtenant <TENANT_NAME>",
CommandHelp("createtenant <TENANT_NAME> [tenant_group=<TENANT_GROUP>]",
"creates a new tenant in the cluster",
"Creates a new tenant in the cluster with the specified name."));
@ -92,7 +155,7 @@ ACTOR Future<bool> deleteTenantCommandActor(Reference<IDatabase> db, std::vector
return false;
}
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Key tenantNameKey = tenantMapSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
state bool doneExistenceCheck = false;
@ -115,15 +178,15 @@ ACTOR Future<bool> deleteTenantCommandActor(Reference<IDatabase> db, std::vector
} 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());
std::string errorMsgStr = wait(getSpecialKeysFailureErrorMessage(tr));
fmt::print(stderr, "ERROR: {}\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
printf("The tenant `%s' has been deleted\n", printable(tokens[1]).c_str());
fmt::print("The tenant `{}' has been deleted\n", printable(tokens[1]).c_str());
return true;
}
@ -151,20 +214,20 @@ ACTOR Future<bool> listTenantsCommandActor(Reference<IDatabase> db, std::vector<
if (tokens.size() >= 3) {
endTenant = tokens[2];
if (endTenant <= beginTenant) {
fprintf(stderr, "ERROR: end must be larger than begin");
fmt::print(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());
if (sscanf(tokens[3].toString().c_str(), "%d%n", &limit, &n) != 1 || n != tokens[3].size() || limit <= 0) {
fmt::print(stderr, "ERROR: invalid limit `{}'\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 Key beginTenantKey = tenantMapSpecialKeyRange.begin.withSuffix(beginTenant);
state Key endTenantKey = tenantMapSpecialKeyRange.begin.withSuffix(endTenant);
state Reference<ITransaction> tr = db->createTransaction();
loop {
@ -176,25 +239,24 @@ ACTOR Future<bool> listTenantsCommandActor(Reference<IDatabase> db, std::vector<
if (tenants.empty()) {
if (tokens.size() == 1) {
printf("The cluster has no tenants\n");
fmt::print("The cluster has no tenants\n");
} else {
printf("The cluster has no tenants in the specified range\n");
fmt::print("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());
fmt::print(
" {}. {}\n", ++index, printable(tenant.key.removePrefix(tenantMapSpecialKeyRange.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());
std::string errorMsgStr = wait(getSpecialKeysFailureErrorMessage(tr));
fmt::print(stderr, "ERROR: {}\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
@ -217,7 +279,7 @@ ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<St
}
state bool useJson = tokens.size() == 3;
state Key tenantNameKey = fdb_cli::tenantSpecialKeyRange.begin.withSuffix(tokens[1]);
state Key tenantNameKey = tenantMapSpecialKeyRange.begin.withSuffix(tokens[1]);
state Reference<ITransaction> tr = db->createTransaction();
loop {
@ -236,13 +298,15 @@ ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<St
json_spirit::mObject resultObj;
resultObj["tenant"] = jsonObject;
resultObj["type"] = "success";
printf("%s\n",
json_spirit::write_string(json_spirit::mValue(resultObj), json_spirit::pretty_print).c_str());
fmt::print(
"{}\n",
json_spirit::write_string(json_spirit::mValue(resultObj), json_spirit::pretty_print).c_str());
} else {
JSONDoc doc(jsonObject);
int64_t id;
std::string prefix;
std::string tenantGroup;
doc.get("id", id);
if (apiVersion >= 720) {
@ -251,8 +315,13 @@ ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<St
doc.get("prefix", prefix);
}
bool hasTenantGroup = doc.tryGet("tenant_group.printable", tenantGroup);
printf(" id: %" PRId64 "\n", id);
printf(" prefix: %s\n", prefix.c_str());
if (hasTenantGroup) {
fmt::print(" tenant group: {}\n", tenantGroup.c_str());
}
}
return true;
@ -274,11 +343,11 @@ ACTOR Future<bool> getTenantCommandActor(Reference<IDatabase> db, std::vector<St
json_spirit::mObject resultObj;
resultObj["type"] = "error";
resultObj["error"] = errorStr;
printf(
"%s\n",
fmt::print(
"{}\n",
json_spirit::write_string(json_spirit::mValue(resultObj), json_spirit::pretty_print).c_str());
} else {
fprintf(stderr, "ERROR: %s\n", errorStr.c_str());
fmt::print(stderr, "ERROR: {}\n", errorStr.c_str());
}
return false;
@ -293,6 +362,50 @@ CommandFactory getTenantFactory(
"prints the metadata for a tenant",
"Prints the metadata for a tenant. If JSON is specified, then the output will be in JSON format."));
// configuretenant command
ACTOR Future<bool> configureTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() < 3) {
printUsage(tokens[0]);
return false;
}
state Optional<std::map<Standalone<StringRef>, Optional<Value>>> configuration =
parseTenantConfiguration(tokens, 2, true);
if (!configuration.present()) {
return false;
}
state Reference<ITransaction> tr = db->createTransaction();
loop {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
try {
applyConfiguration(tr, tokens[1], configuration.get());
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(getSpecialKeysFailureErrorMessage(tr));
fmt::print(stderr, "ERROR: {}\n", errorMsgStr.c_str());
return false;
}
wait(safeThreadFutureToFuture(tr->onError(err)));
}
}
fmt::print("The configuration for tenant `{}' has been updated\n", printable(tokens[1]).c_str());
return true;
}
CommandFactory configureTenantFactory(
"configuretenant",
CommandHelp("configuretenant <TENANT_NAME> <[unset] tenant_group[=<GROUP_NAME>]> ...",
"updates the configuration for a tenant",
"Updates the configuration for a tenant. Use `tenant_group=<GROUP_NAME>' to change the tenant group "
"that a tenant is assigned to or `unset tenant_group' to remove a tenant from its tenant group."));
// renametenant command
ACTOR Future<bool> renameTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() != 3) {
@ -301,7 +414,8 @@ ACTOR Future<bool> renameTenantCommandActor(Reference<IDatabase> db, std::vector
}
wait(safeThreadFutureToFuture(TenantAPI::renameTenant(db, tokens[1], tokens[2])));
printf("The tenant `%s' has been renamed to `%s'\n", printable(tokens[1]).c_str(), printable(tokens[2]).c_str());
fmt::print(
"The tenant `{}' has been renamed to `{}'\n", printable(tokens[1]).c_str(), printable(tokens[2]).c_str());
return true;
}
@ -309,6 +423,6 @@ CommandFactory renameTenantFactory(
"renametenant",
CommandHelp(
"renametenant <OLD_NAME> <NEW_NAME>",
"renames a tenant in the cluster.",
"renames a tenant in the cluster",
"Renames a tenant in the cluster. The old name must exist and the new name must not exist in the cluster."));
} // namespace fdb_cli

View File

@ -1940,6 +1940,13 @@ ACTOR Future<int> cli(CLIOptions opt, LineNoise* plinenoise) {
continue;
}
if (tokencmp(tokens[0], "configuretenant")) {
bool _result = wait(makeInterruptable(configureTenantCommandActor(db, tokens)));
if (!_result)
is_error = true;
continue;
}
if (tokencmp(tokens[0], "renametenant")) {
bool _result = wait(makeInterruptable(renameTenantCommandActor(db, tokens)));
if (!_result)

View File

@ -157,6 +157,8 @@ ACTOR Future<bool> configureCommandActor(Reference<IDatabase> db,
std::vector<StringRef> tokens,
LineNoise* linenoise,
Future<Void> warn);
// configuretenant command
ACTOR Future<bool> configureTenantCommandActor(Reference<IDatabase> db, std::vector<StringRef> tokens);
// consistency command
ACTOR Future<bool> consistencyCheckCommandActor(Reference<ITransaction> tr,
std::vector<StringRef> tokens,

View File

@ -1625,6 +1625,8 @@ const KeyRef tenantMapPrefix = tenantMapKeys.begin;
const KeyRef tenantMapPrivatePrefix = "\xff\xff/tenantMap/"_sr;
const KeyRef tenantLastIdKey = "\xff/tenantLastId/"_sr;
const KeyRef tenantDataPrefixKey = "\xff/tenantDataPrefix"_sr;
const KeyRangeRef tenantGroupTenantIndexKeys("\xff/tenant/tenantGroup/tenantMap/"_sr,
"\xff/tenant/tenantGroup/tenantMap0"_sr);
// for tests
void testSSISerdes(StorageServerInterface const& ssi) {

View File

@ -35,7 +35,7 @@ int64_t TenantMapEntry::prefixToId(KeyRef prefix) {
return id;
}
void TenantMapEntry::initPrefix(KeyRef subspace) {
void TenantMapEntry::setSubspace(KeyRef subspace) {
ASSERT(id >= 0);
prefix = makeString(8 + subspace.size());
uint8_t* data = mutateString(prefix);
@ -48,7 +48,16 @@ void TenantMapEntry::initPrefix(KeyRef subspace) {
TenantMapEntry::TenantMapEntry() : id(-1) {}
TenantMapEntry::TenantMapEntry(int64_t id, KeyRef subspace) : id(id) {
initPrefix(subspace);
setSubspace(subspace);
}
TenantMapEntry::TenantMapEntry(int64_t id, KeyRef subspace, Optional<TenantGroupName> tenantGroup)
: id(id), tenantGroup(tenantGroup) {
setSubspace(subspace);
}
bool TenantMapEntry::matchesConfiguration(TenantMapEntry const& other) const {
return tenantGroup == other.tenantGroup;
}
TEST_CASE("/fdbclient/TenantMapEntry/Serialization") {

View File

@ -0,0 +1,39 @@
/*
* TenantManagement.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 <string>
#include <map>
#include "fdbclient/SystemData.h"
#include "fdbclient/TenantManagement.actor.h"
#include "fdbclient/Tuple.h"
#include "flow/actorcompiler.h" // has to be last include
namespace TenantAPI {
Key getTenantGroupIndexKey(TenantGroupNameRef tenantGroup, Optional<TenantNameRef> tenant) {
Tuple tuple;
tuple.append(tenantGroup);
if (tenant.present()) {
tuple.append(tenant.get());
}
return tenantGroupTenantIndexKeys.begin.withSuffix(tuple.pack());
}
} // namespace TenantAPI

View File

@ -31,3 +31,13 @@ const KeyRangeRef TenantRangeImpl<false>::submoduleRange = KeyRangeRef(""_sr, "\
template <>
const KeyRangeRef TenantRangeImpl<false>::mapSubRange = KeyRangeRef("tenant_map/"_sr, "tenant_map0"_sr);
template <>
bool TenantRangeImpl<true>::subRangeIntersects(KeyRangeRef subRange, KeyRangeRef range) {
return subRange.intersects(range);
}
template <>
bool TenantRangeImpl<false>::subRangeIntersects(KeyRangeRef subRange, KeyRangeRef range) {
return subRange == mapSubRange;
}

View File

@ -685,6 +685,7 @@ extern const KeyRef tenantMapPrefix;
extern const KeyRef tenantMapPrivatePrefix;
extern const KeyRef tenantLastIdKey;
extern const KeyRef tenantDataPrefixKey;
extern const KeyRangeRef tenantGroupTenantIndexKeys;
#pragma clang diagnostic pop

View File

@ -28,6 +28,8 @@
typedef StringRef TenantNameRef;
typedef Standalone<TenantNameRef> TenantName;
typedef StringRef TenantGroupNameRef;
typedef Standalone<TenantGroupNameRef> TenantGroupName;
struct TenantMapEntry {
constexpr static FileIdentifier file_identifier = 12247338;
@ -37,21 +39,23 @@ struct TenantMapEntry {
int64_t id;
Key prefix;
Optional<TenantGroupName> tenantGroup;
constexpr static int ROOT_PREFIX_SIZE = sizeof(id);
private:
void initPrefix(KeyRef subspace);
public:
TenantMapEntry();
TenantMapEntry(int64_t id, KeyRef subspace);
TenantMapEntry(int64_t id, KeyRef subspace, Optional<TenantGroupName> tenantGroup);
Value encode() const { return ObjectWriter::toValue(*this, IncludeVersion(ProtocolVersion::withTenants())); }
void setSubspace(KeyRef subspace);
bool matchesConfiguration(TenantMapEntry const& other) const;
Value encode() const { return ObjectWriter::toValue(*this, IncludeVersion(ProtocolVersion::withTenantGroups())); }
static TenantMapEntry decode(ValueRef const& value) {
TenantMapEntry entry;
ObjectReader reader(value.begin(), IncludeVersion(ProtocolVersion::withTenants()));
ObjectReader reader(value.begin(), IncludeVersion());
reader.deserialize(entry);
return entry;
}
@ -60,16 +64,21 @@ public:
void serialize(Ar& ar) {
KeyRef subspace;
if (ar.isDeserializing) {
serializer(ar, id, subspace);
if (ar.protocolVersion().hasTenantGroups()) {
serializer(ar, id, subspace, tenantGroup);
} else {
serializer(ar, id, subspace);
}
if (id >= 0) {
initPrefix(subspace);
setSubspace(subspace);
}
} else {
ASSERT(prefix.size() >= 8 || (prefix.empty() && id == -1));
if (!prefix.empty()) {
subspace = prefix.substr(0, prefix.size() - 8);
}
serializer(ar, id, subspace);
serializer(ar, id, subspace, tenantGroup);
}
}
};

View File

@ -33,6 +33,9 @@
#include "flow/actorcompiler.h" // has to be last include
namespace TenantAPI {
Key getTenantGroupIndexKey(TenantGroupNameRef tenantGroup, Optional<TenantNameRef> tenant);
ACTOR template <class Transaction>
Future<Optional<TenantMapEntry>> tryGetTenantTransaction(Transaction tr, TenantName name) {
state Key tenantMapKey = name.withPrefix(tenantMapPrefix);
@ -84,16 +87,20 @@ Future<TenantMapEntry> getTenant(Reference<DB> db, TenantName name) {
// The caller must enforce that the tenant ID be unique from all current and past tenants, and it must also be unique
// from all other tenants created in the same transaction.
ACTOR template <class Transaction>
Future<std::pair<TenantMapEntry, bool>> createTenantTransaction(Transaction tr, TenantNameRef name, int64_t tenantId) {
Future<std::pair<TenantMapEntry, bool>> createTenantTransaction(Transaction tr,
TenantNameRef name,
TenantMapEntry tenantEntry) {
state Key tenantMapKey = name.withPrefix(tenantMapPrefix);
ASSERT(tenantEntry.id >= 0);
if (name.startsWith("\xff"_sr)) {
throw invalid_tenant_name();
}
tr->setOption(FDBTransactionOptions::RAW_ACCESS);
state Future<Optional<TenantMapEntry>> tenantEntryFuture = tryGetTenantTransaction(tr, name);
state Future<Optional<TenantMapEntry>> existingEntryFuture = tryGetTenantTransaction(tr, name);
state typename transaction_future_type<Transaction, Optional<Value>>::type tenantDataPrefixFuture =
tr->get(tenantDataPrefixKey);
state typename transaction_future_type<Transaction, Optional<Value>>::type tenantModeFuture =
@ -105,9 +112,9 @@ Future<std::pair<TenantMapEntry, bool>> createTenantTransaction(Transaction tr,
throw tenants_disabled();
}
Optional<TenantMapEntry> tenantEntry = wait(tenantEntryFuture);
if (tenantEntry.present()) {
return std::make_pair(tenantEntry.get(), false);
Optional<TenantMapEntry> existingEntry = wait(existingEntryFuture);
if (existingEntry.present()) {
return std::make_pair(existingEntry.get(), false);
}
Optional<Value> tenantDataPrefix = wait(safeThreadFutureToFuture(tenantDataPrefixFuture));
@ -122,18 +129,21 @@ Future<std::pair<TenantMapEntry, bool>> createTenantTransaction(Transaction tr,
throw client_invalid_operation();
}
state TenantMapEntry newTenant(tenantId, tenantDataPrefix.present() ? (KeyRef)tenantDataPrefix.get() : ""_sr);
tenantEntry.setSubspace(tenantDataPrefix.orDefault(""_sr));
state typename transaction_future_type<Transaction, RangeResult>::type prefixRangeFuture =
tr->getRange(prefixRange(newTenant.prefix), 1);
tr->getRange(prefixRange(tenantEntry.prefix), 1);
RangeResult contents = wait(safeThreadFutureToFuture(prefixRangeFuture));
if (!contents.empty()) {
throw tenant_prefix_allocator_conflict();
}
tr->set(tenantMapKey, newTenant.encode());
tr->set(tenantMapKey, tenantEntry.encode());
if (tenantEntry.tenantGroup.present()) {
tr->set(getTenantGroupIndexKey(tenantEntry.tenantGroup.get(), name), ""_sr);
}
return std::make_pair(newTenant, true);
return std::make_pair(tenantEntry, true);
}
ACTOR template <class Transaction>
@ -148,16 +158,20 @@ Future<int64_t> getNextTenantId(Transaction tr) {
}
ACTOR template <class DB>
Future<TenantMapEntry> createTenant(Reference<DB> db, TenantName name) {
Future<TenantMapEntry> createTenant(Reference<DB> db, TenantName name, TenantMapEntry tenantEntry = TenantMapEntry()) {
state Reference<typename DB::TransactionT> tr = db->createTransaction();
state bool firstTry = true;
state bool generateTenantId = tenantEntry.id < 0;
loop {
try {
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
state Future<int64_t> tenantIdFuture = getNextTenantId(tr);
state Future<int64_t> tenantIdFuture;
if (generateTenantId) {
tenantIdFuture = getNextTenantId(tr);
}
if (firstTry) {
Optional<TenantMapEntry> entry = wait(tryGetTenantTransaction(tr, name));
@ -168,17 +182,24 @@ Future<TenantMapEntry> createTenant(Reference<DB> db, TenantName name) {
firstTry = false;
}
int64_t tenantId = wait(tenantIdFuture);
tr->set(tenantLastIdKey, TenantMapEntry::idToPrefix(tenantId));
state std::pair<TenantMapEntry, bool> newTenant = wait(createTenantTransaction(tr, name, tenantId));
if (generateTenantId) {
int64_t tenantId = wait(tenantIdFuture);
tenantEntry.id = tenantId;
tr->set(tenantLastIdKey, TenantMapEntry::idToPrefix(tenantId));
}
wait(buggifiedCommit(tr, BUGGIFY_WITH_PROB(0.1)));
state std::pair<TenantMapEntry, bool> newTenant = wait(createTenantTransaction(tr, name, tenantEntry));
TraceEvent("CreatedTenant")
.detail("Tenant", name)
.detail("TenantId", newTenant.first.id)
.detail("Prefix", newTenant.first.prefix)
.detail("Version", tr->getCommittedVersion());
if (newTenant.second) {
wait(buggifiedCommit(tr, BUGGIFY_WITH_PROB(0.1)));
TraceEvent("CreatedTenant")
.detail("Tenant", name)
.detail("TenantId", newTenant.first.id)
.detail("Prefix", newTenant.first.prefix)
.detail("TenantGroup", tenantEntry.tenantGroup)
.detail("Version", tr->getCommittedVersion());
}
return newTenant.first;
} catch (Error& e) {
@ -206,6 +227,9 @@ Future<Void> deleteTenantTransaction(Transaction tr, TenantNameRef name) {
}
tr->clear(tenantMapKey);
if (tenantEntry.get().tenantGroup.present()) {
tr->clear(getTenantGroupIndexKey(tenantEntry.get().tenantGroup.get(), name));
}
return Void();
}
@ -240,6 +264,15 @@ Future<Void> deleteTenant(Reference<DB> db, TenantName name) {
}
}
// This should only be called from a transaction that has already confirmed that the cluster entry
// is present. The updatedEntry should use the existing entry and modify only those fields that need
// to be changed.
template <class Transaction>
void configureTenantTransaction(Transaction tr, TenantNameRef tenantName, TenantMapEntry tenantEntry) {
tr->setOption(FDBTransactionOptions::RAW_ACCESS);
tr->set(tenantName.withPrefix(tenantMapPrefix), tenantEntry.encode());
}
ACTOR template <class Transaction>
Future<std::map<TenantName, TenantMapEntry>> listTenantsTransaction(Transaction tr,
TenantNameRef begin,

View File

@ -31,14 +31,17 @@
#include "fdbclient/DatabaseContext.h"
#include "fdbclient/SpecialKeySpace.actor.h"
#include "fdbclient/TenantManagement.actor.h"
#include "fdbclient/Tuple.h"
#include "fdbclient/libb64/encode.h"
#include "flow/Arena.h"
#include "flow/UnitTest.h"
#include "flow/actorcompiler.h" // This must be the last #include.
template <bool HasSubRanges = true>
template <bool HasSubRanges>
class TenantRangeImpl : public SpecialKeyRangeRWImpl {
private:
static bool subRangeIntersects(KeyRangeRef subRange, KeyRangeRef range);
static KeyRangeRef removePrefix(KeyRangeRef range, KeyRef prefix, KeyRef defaultEnd) {
KeyRef begin = range.begin.removePrefix(prefix);
KeyRef end;
@ -53,15 +56,14 @@ private:
static KeyRef withTenantMapPrefix(KeyRef key, Arena& ar) {
int keySize = SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin.size() +
TenantRangeImpl::submoduleRange.begin.size() + TenantRangeImpl::mapSubRange.begin.size() +
key.size();
submoduleRange.begin.size() + mapSubRange.begin.size() + key.size();
KeyRef prefixedKey = makeString(keySize, ar);
uint8_t* mutableKey = mutateString(prefixedKey);
mutableKey = SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin.copyTo(mutableKey);
mutableKey = TenantRangeImpl::submoduleRange.begin.copyTo(mutableKey);
mutableKey = TenantRangeImpl::mapSubRange.begin.copyTo(mutableKey);
mutableKey = submoduleRange.begin.copyTo(mutableKey);
mutableKey = mapSubRange.begin.copyTo(mutableKey);
key.copyTo(mutableKey);
return prefixedKey;
@ -90,6 +92,19 @@ private:
// This is not a standard encoding in JSON, and some libraries may not be able to easily decode it
tenantEntry["prefix"] = tenant.second.prefix.toString();
}
if (tenant.second.tenantGroup.present()) {
json_spirit::mObject tenantGroupObject;
std::string encodedTenantGroup =
base64::encoder::from_string(tenant.second.tenantGroup.get().toString());
// Remove trailing newline
encodedTenantGroup.resize(encodedTenantGroup.size() - 1);
tenantGroupObject["base64"] = encodedTenantGroup;
tenantGroupObject["printable"] = printable(tenant.second.tenantGroup.get());
tenantEntry["tenant_group"] = tenantGroupObject;
}
std::string tenantEntryString = json_spirit::write_string(json_spirit::mValue(tenantEntry));
ValueRef tenantEntryBytes(results->arena(), tenantEntryString);
results->push_back(results->arena(),
@ -99,20 +114,21 @@ private:
return Void();
}
ACTOR static Future<RangeResult> getTenantRange(ReadYourWritesTransaction* ryw,
KeyRangeRef kr,
GetRangeLimits limitsHint) {
ACTOR template <bool B>
static Future<RangeResult> getTenantRange(ReadYourWritesTransaction* ryw,
KeyRangeRef kr,
GetRangeLimits limitsHint) {
state RangeResult results;
kr = kr.removePrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)
.removePrefix(TenantRangeImpl::submoduleRange.begin);
.removePrefix(TenantRangeImpl<B>::submoduleRange.begin);
if (kr.intersects(TenantRangeImpl::mapSubRange)) {
if (kr.intersects(TenantRangeImpl<B>::mapSubRange)) {
GetRangeLimits limits = limitsHint;
limits.decrement(results);
wait(getTenantList(
ryw,
removePrefix(kr & TenantRangeImpl::mapSubRange, TenantRangeImpl::mapSubRange.begin, "\xff"_sr),
removePrefix(kr & TenantRangeImpl<B>::mapSubRange, TenantRangeImpl<B>::mapSubRange.begin, "\xff"_sr),
&results,
limits));
}
@ -120,14 +136,57 @@ private:
return results;
}
ACTOR static Future<Void> createTenants(ReadYourWritesTransaction* ryw, std::vector<TenantNameRef> tenants) {
static void applyTenantConfig(ReadYourWritesTransaction* ryw,
TenantNameRef tenantName,
std::vector<std::pair<Standalone<StringRef>, Optional<Value>>> configEntries,
TenantMapEntry* tenantEntry) {
std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>::iterator configItr;
for (configItr = configEntries.begin(); configItr != configEntries.end(); ++configItr) {
if (configItr->first == "tenant_group"_sr) {
tenantEntry->tenantGroup = configItr->second;
} else {
TraceEvent(SevWarn, "InvalidTenantConfig")
.detail("TenantName", tenantName)
.detail("ConfigName", configItr->first);
ryw->setSpecialKeySpaceErrorMsg(
ManagementAPIError::toJsonString(false,
"set tenant configuration",
format("invalid tenant configuration option `%s' for tenant `%s'",
configItr->first.toString().c_str(),
tenantName.toString().c_str())));
throw special_keys_api_failure();
}
}
}
ACTOR static Future<Void> createTenant(
ReadYourWritesTransaction* ryw,
TenantNameRef tenantName,
Optional<std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>> configMutations,
int64_t tenantId) {
state TenantMapEntry tenantEntry;
tenantEntry.id = tenantId;
if (configMutations.present()) {
applyTenantConfig(ryw, tenantName, configMutations.get(), &tenantEntry);
}
std::pair<TenantMapEntry, bool> entry =
wait(TenantAPI::createTenantTransaction(&ryw->getTransaction(), tenantName, tenantEntry));
return Void();
}
ACTOR static Future<Void> createTenants(
ReadYourWritesTransaction* ryw,
std::map<TenantName, Optional<std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>>> tenants) {
int64_t _nextId = wait(TenantAPI::getNextTenantId(&ryw->getTransaction()));
int64_t nextId = _nextId;
std::vector<Future<Void>> createFutures;
for (auto tenant : tenants) {
createFutures.push_back(
success(TenantAPI::createTenantTransaction(&ryw->getTransaction(), tenant, nextId++)));
for (auto const& [tenant, config] : tenants) {
createFutures.push_back(createTenant(ryw, tenant, config, nextId++));
}
ryw->getTransaction().set(tenantLastIdKey, TenantMapEntry::idToPrefix(nextId - 1));
@ -135,6 +194,18 @@ private:
return Void();
}
ACTOR static Future<Void> changeTenantConfig(
ReadYourWritesTransaction* ryw,
TenantName tenantName,
std::vector<std::pair<Standalone<StringRef>, Optional<Value>>> configEntries) {
state TenantMapEntry tenantEntry = wait(TenantAPI::getTenantTransaction(&ryw->getTransaction(), tenantName));
applyTenantConfig(ryw, tenantName, configEntries, &tenantEntry);
TenantAPI::configureTenantTransaction(&ryw->getTransaction(), tenantName, tenantEntry);
return Void();
}
ACTOR static Future<Void> deleteTenantRange(ReadYourWritesTransaction* ryw,
TenantName beginTenant,
TenantName endTenant) {
@ -160,15 +231,19 @@ private:
}
public:
// These ranges vary based on the template parameter
const static KeyRangeRef submoduleRange;
const static KeyRangeRef mapSubRange;
// These sub-ranges should only be used if HasSubRanges=true
const inline static KeyRangeRef configureSubRange = KeyRangeRef("configure/"_sr, "configure0"_sr);
explicit TenantRangeImpl(KeyRangeRef kr) : SpecialKeyRangeRWImpl(kr) {}
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
KeyRangeRef kr,
GetRangeLimits limitsHint) const override {
return getTenantRange(ryw, kr, limitsHint);
return getTenantRange<HasSubRanges>(ryw, kr, limitsHint);
}
Future<Optional<std::string>> commit(ReadYourWritesTransaction* ryw) override {
@ -176,6 +251,7 @@ public:
std::vector<Future<Void>> tenantManagementFutures;
std::vector<std::pair<KeyRangeRef, Optional<Value>>> mapMutations;
std::map<TenantName, std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>> configMutations;
for (auto range : ranges) {
if (!range.value().first) {
@ -187,25 +263,53 @@ public:
.removePrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)
.removePrefix(submoduleRange.begin);
if (mapSubRange.intersects(adjustedRange)) {
if (subRangeIntersects(mapSubRange, adjustedRange)) {
adjustedRange = mapSubRange & adjustedRange;
adjustedRange = removePrefix(adjustedRange, mapSubRange.begin, "\xff"_sr);
mapMutations.push_back(std::make_pair(adjustedRange, range.value().second));
} else if (subRangeIntersects(configureSubRange, adjustedRange) && adjustedRange.singleKeyRange()) {
StringRef configTupleStr = adjustedRange.begin.removePrefix(configureSubRange.begin);
try {
Tuple tuple = Tuple::unpack(configTupleStr);
if (tuple.size() != 2) {
throw invalid_tuple_index();
}
configMutations[tuple.getString(0)].push_back(
std::make_pair(tuple.getString(1), range.value().second));
} catch (Error& e) {
TraceEvent(SevWarn, "InvalidTenantConfigurationKey").error(e).detail("Key", adjustedRange.begin);
ryw->setSpecialKeySpaceErrorMsg(ManagementAPIError::toJsonString(
false, "configure tenant", "invalid tenant configuration key"));
throw special_keys_api_failure();
}
}
}
std::vector<TenantNameRef> tenantsToCreate;
std::map<TenantName, Optional<std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>>> tenantsToCreate;
for (auto mapMutation : mapMutations) {
TenantNameRef tenantName = mapMutation.first.begin;
if (mapMutation.second.present()) {
tenantsToCreate.push_back(tenantName);
Optional<std::vector<std::pair<Standalone<StringRef>, Optional<Value>>>> createMutations;
auto itr = configMutations.find(tenantName);
if (itr != configMutations.end()) {
createMutations = itr->second;
configMutations.erase(itr);
}
tenantsToCreate[tenantName] = createMutations;
} else {
// For a single key clear, just issue the delete
if (mapMutation.first.singleKeyRange()) {
tenantManagementFutures.push_back(
TenantAPI::deleteTenantTransaction(&ryw->getTransaction(), tenantName));
// Configuration changes made to a deleted tenant are discarded
configMutations.erase(tenantName);
} else {
tenantManagementFutures.push_back(deleteTenantRange(ryw, tenantName, mapMutation.first.end));
// Configuration changes made to a deleted tenant are discarded
configMutations.erase(configMutations.lower_bound(tenantName),
configMutations.lower_bound(mapMutation.first.end));
}
}
}
@ -213,6 +317,9 @@ public:
if (!tenantsToCreate.empty()) {
tenantManagementFutures.push_back(createTenants(ryw, tenantsToCreate));
}
for (auto configMutation : configMutations) {
tenantManagementFutures.push_back(changeTenantConfig(ryw, configMutation.first, configMutation.second));
}
return tag(waitForAll(tenantManagementFutures), Optional<std::string>());
}

View File

@ -1633,8 +1633,12 @@ ACTOR Future<Void> runTests(Reference<AsyncVar<Optional<struct ClusterController
if (useDB) {
std::vector<Future<Void>> tenantFutures;
for (auto tenant : tenantsToCreate) {
TraceEvent("CreatingTenant").detail("Tenant", tenant);
tenantFutures.push_back(success(TenantAPI::createTenant(cx.getReference(), tenant)));
TenantMapEntry entry;
if (deterministicRandom()->coinflip()) {
entry.tenantGroup = "TestTenantGroup"_sr;
}
TraceEvent("CreatingTenant").detail("Tenant", tenant).detail("TenantGroup", entry.tenantGroup);
tenantFutures.push_back(success(TenantAPI::createTenant(cx.getReference(), tenant, entry)));
}
wait(waitForAll(tenantFutures));

View File

@ -131,6 +131,7 @@ struct FuzzApiCorrectnessWorkload : TestWorkload {
std::vector<Reference<ITenant>> tenants;
std::set<TenantName> createdTenants;
int numTenants;
int numTenantGroups;
// Map from tenant number to key prefix
std::map<int, std::string> keyPrefixes;
@ -154,6 +155,9 @@ struct FuzzApiCorrectnessWorkload : TestWorkload {
int maxTenants = getOption(options, "numTenants"_sr, 4);
numTenants = deterministicRandom()->randomInt(0, maxTenants + 1);
int maxTenantGroups = getOption(options, "numTenantGroups"_sr, numTenants);
numTenantGroups = deterministicRandom()->randomInt(0, maxTenantGroups + 1);
// See https://github.com/apple/foundationdb/issues/2424
if (BUGGIFY) {
enableBuggify(true, BuggifyType::Client);
@ -206,6 +210,14 @@ struct FuzzApiCorrectnessWorkload : TestWorkload {
std::string description() const override { return "FuzzApiCorrectness"; }
static TenantName getTenant(int num) { return TenantNameRef(format("tenant_%d", num)); }
Optional<TenantGroupName> getTenantGroup(int num) {
int groupNum = num % (numTenantGroups + 1);
if (groupNum == numTenantGroups - 1) {
return Optional<TenantGroupName>();
} else {
return TenantGroupNameRef(format("tenantgroup_%d", groupNum));
}
}
bool canUseTenant(Optional<TenantName> tenant) { return !tenant.present() || createdTenants.count(tenant.get()); }
Future<Void> setup(Database const& cx) override {
@ -226,7 +238,9 @@ struct FuzzApiCorrectnessWorkload : TestWorkload {
// The last tenant will not be created
if (i < self->numTenants) {
tenantFutures.push_back(::success(TenantAPI::createTenant(cx.getReference(), tenantName)));
TenantMapEntry entry;
entry.tenantGroup = self->getTenantGroup(i);
tenantFutures.push_back(::success(TenantAPI::createTenant(cx.getReference(), tenantName, entry)));
self->createdTenants.insert(tenantName);
}
}

View File

@ -33,15 +33,17 @@
#include "flow/actorcompiler.h" // This must be the last #include.
struct TenantManagementWorkload : TestWorkload {
struct TenantState {
struct TenantData {
int64_t id;
Optional<TenantGroupName> tenantGroup;
bool empty;
TenantState() : id(-1), empty(true) {}
TenantState(int64_t id, bool empty) : id(id), empty(empty) {}
TenantData() : id(-1), empty(true) {}
TenantData(int64_t id, Optional<TenantGroupName> tenantGroup, bool empty)
: id(id), tenantGroup(tenantGroup), empty(empty) {}
};
std::map<TenantName, TenantState> createdTenants;
std::map<TenantName, TenantData> createdTenants;
int64_t maxId = -1;
Key tenantSubspace;
@ -54,8 +56,12 @@ struct TenantManagementWorkload : TestWorkload {
const Key specialKeysTenantMapPrefix = SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT)
.begin.withSuffix(TenantRangeImpl<true>::submoduleRange.begin)
.withSuffix(TenantRangeImpl<true>::mapSubRange.begin);
const Key specialKeysTenantConfigPrefix = SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT)
.begin.withSuffix(TenantRangeImpl<true>::submoduleRange.begin)
.withSuffix(TenantRangeImpl<true>::configureSubRange.begin);
int maxTenants;
int maxTenantGroups;
double testDuration;
enum class OperationType { SPECIAL_KEYS, MANAGEMENT_DATABASE, MANAGEMENT_TRANSACTION };
@ -73,6 +79,7 @@ struct TenantManagementWorkload : TestWorkload {
TenantManagementWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
maxTenants = std::min<int>(1e8 - 1, getOption(options, "maxTenants"_sr, 1000));
maxTenantGroups = std::min<int>(2 * maxTenants, getOption(options, "maxTenantGroups"_sr, 20));
testDuration = getOption(options, "testDuration"_sr, 60.0);
localTenantNamePrefix = format("%stenant_%d_", tenantNamePrefix.toString().c_str(), clientId);
@ -133,6 +140,15 @@ struct TenantManagementWorkload : TestWorkload {
return tenant;
}
Optional<TenantGroupName> chooseTenantGroup() {
Optional<TenantGroupName> tenantGroup;
if (deterministicRandom()->coinflip()) {
tenantGroup =
TenantGroupNameRef(format("tenantgroup%08d", deterministicRandom()->randomInt(0, maxTenantGroups)));
}
return tenantGroup;
}
ACTOR Future<Void> createTenant(Database cx, TenantManagementWorkload* self) {
state OperationType operationType = TenantManagementWorkload::randomOperationType();
int numTenants = 1;
@ -145,10 +161,12 @@ struct TenantManagementWorkload : TestWorkload {
state bool alreadyExists = false;
state bool hasSystemTenant = false;
state std::set<TenantName> tenantsToCreate;
state std::map<TenantName, TenantMapEntry> tenantsToCreate;
for (int i = 0; i < numTenants; ++i) {
TenantName tenant = self->chooseTenantName(true);
tenantsToCreate.insert(tenant);
TenantMapEntry entry;
entry.tenantGroup = self->chooseTenantGroup();
tenantsToCreate[tenant] = entry;
alreadyExists = alreadyExists || self->createdTenants.count(tenant);
hasSystemTenant = hasSystemTenant || tenant.startsWith("\xff"_sr);
@ -160,13 +178,19 @@ struct TenantManagementWorkload : TestWorkload {
try {
if (operationType == OperationType::SPECIAL_KEYS) {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
for (auto tenant : tenantsToCreate) {
for (auto [tenant, entry] : tenantsToCreate) {
tr->set(self->specialKeysTenantMapPrefix.withSuffix(tenant), ""_sr);
if (entry.tenantGroup.present()) {
tr->set(self->specialKeysTenantConfigPrefix.withSuffix(
Tuple().append(tenant).append("tenant_group"_sr).pack()),
entry.tenantGroup.get());
}
}
wait(tr->commit());
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
ASSERT(tenantsToCreate.size() == 1);
wait(success(TenantAPI::createTenant(cx.getReference(), *tenantsToCreate.begin())));
wait(success(TenantAPI::createTenant(
cx.getReference(), tenantsToCreate.begin()->first, tenantsToCreate.begin()->second)));
} else {
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
@ -174,8 +198,9 @@ struct TenantManagementWorkload : TestWorkload {
int64_t nextId = _nextId;
std::vector<Future<Void>> createFutures;
for (auto tenant : tenantsToCreate) {
createFutures.push_back(success(TenantAPI::createTenantTransaction(tr, tenant, nextId++)));
for (auto [tenant, entry] : tenantsToCreate) {
entry.id = nextId++;
createFutures.push_back(success(TenantAPI::createTenantTransaction(tr, tenant, entry)));
}
tr->set(tenantLastIdKey, TenantMapEntry::idToPrefix(nextId - 1));
wait(waitForAll(createFutures));
@ -188,26 +213,29 @@ struct TenantManagementWorkload : TestWorkload {
ASSERT(!hasSystemTenant);
state std::set<TenantName>::iterator tenantItr;
state std::map<TenantName, TenantMapEntry>::iterator tenantItr;
for (tenantItr = tenantsToCreate.begin(); tenantItr != tenantsToCreate.end(); ++tenantItr) {
if (self->createdTenants.count(*tenantItr)) {
if (self->createdTenants.count(tenantItr->first)) {
continue;
}
state Optional<TenantMapEntry> entry = wait(TenantAPI::tryGetTenant(cx.getReference(), *tenantItr));
state Optional<TenantMapEntry> entry =
wait(TenantAPI::tryGetTenant(cx.getReference(), tenantItr->first));
ASSERT(entry.present());
ASSERT(entry.get().id > self->maxId);
ASSERT(entry.get().prefix.startsWith(self->tenantSubspace));
ASSERT(entry.get().tenantGroup == tenantItr->second.tenantGroup);
self->maxId = entry.get().id;
self->createdTenants[*tenantItr] = TenantState(entry.get().id, true);
self->createdTenants[tenantItr->first] =
TenantData(entry.get().id, tenantItr->second.tenantGroup, true);
state bool insertData = deterministicRandom()->random01() < 0.5;
if (insertData) {
state Transaction insertTr(cx, *tenantItr);
state Transaction insertTr(cx, tenantItr->first);
loop {
try {
insertTr.set(self->keyName, *tenantItr);
insertTr.set(self->keyName, tenantItr->first);
wait(insertTr.commit());
break;
} catch (Error& e) {
@ -215,7 +243,7 @@ struct TenantManagementWorkload : TestWorkload {
}
}
self->createdTenants[*tenantItr].empty = false;
self->createdTenants[tenantItr->first].empty = false;
state Transaction checkTr(cx);
loop {
@ -223,7 +251,7 @@ struct TenantManagementWorkload : TestWorkload {
checkTr.setOption(FDBTransactionOptions::RAW_ACCESS);
Optional<Value> val = wait(checkTr.get(self->keyName.withPrefix(entry.get().prefix)));
ASSERT(val.present());
ASSERT(val.get() == *tenantItr);
ASSERT(val.get() == tenantItr->first);
break;
} catch (Error& e) {
wait(checkTr.onError(e));
@ -231,7 +259,7 @@ struct TenantManagementWorkload : TestWorkload {
}
}
wait(self->checkTenant(cx, self, *tenantItr, self->createdTenants[*tenantItr]));
wait(self->checkTenant(cx, self, tenantItr->first, self->createdTenants[tenantItr->first]));
}
return Void();
} catch (Error& e) {
@ -245,14 +273,14 @@ struct TenantManagementWorkload : TestWorkload {
ASSERT(tenantsToCreate.size() == 1);
TraceEvent(SevError, "CreateTenantFailure")
.error(e)
.detail("TenantName", *tenantsToCreate.begin());
.detail("TenantName", tenantsToCreate.begin()->first);
}
return Void();
} else {
try {
wait(tr->onError(e));
} catch (Error& e) {
for (auto tenant : tenantsToCreate) {
for (auto [tenant, _] : tenantsToCreate) {
TraceEvent(SevError, "CreateTenantFailure").error(e).detail("TenantName", tenant);
}
return Void();
@ -396,12 +424,12 @@ struct TenantManagementWorkload : TestWorkload {
ACTOR Future<Void> checkTenant(Database cx,
TenantManagementWorkload* self,
TenantName tenant,
TenantState tenantState) {
TenantData tenantData) {
state Transaction tr(cx, tenant);
loop {
try {
state RangeResult result = wait(tr.getRange(KeyRangeRef(""_sr, "\xff"_sr), 2));
if (tenantState.empty) {
if (tenantData.empty) {
ASSERT(result.size() == 0);
} else {
ASSERT(result.size() == 1);
@ -417,15 +445,22 @@ struct TenantManagementWorkload : TestWorkload {
return Void();
}
// Convert the JSON document returned by the special-key space when reading tenant metadata
// into a TenantMapEntry
static TenantMapEntry jsonToTenantMapEntry(ValueRef tenantJson) {
json_spirit::mValue jsonObject;
json_spirit::read_string(tenantJson.toString(), jsonObject);
JSONDoc jsonDoc(jsonObject);
int64_t id;
std::string prefix;
std::string base64Prefix;
std::string printablePrefix;
std::string base64TenantGroup;
std::string printableTenantGroup;
jsonDoc.get("id", id);
jsonDoc.get("prefix.base64", base64Prefix);
jsonDoc.get("prefix.printable", printablePrefix);
@ -433,8 +468,16 @@ struct TenantManagementWorkload : TestWorkload {
prefix = base64::decoder::from_string(base64Prefix);
ASSERT(prefix == unprintable(printablePrefix));
Optional<TenantGroupName> tenantGroup;
if (jsonDoc.tryGet("tenant_group.base64", base64TenantGroup)) {
jsonDoc.get("tenant_group.printable", printableTenantGroup);
std::string tenantGroupStr = base64::decoder::from_string(base64TenantGroup);
ASSERT(tenantGroupStr == unprintable(printableTenantGroup));
tenantGroup = TenantGroupNameRef(tenantGroupStr);
}
Key prefixKey = KeyRef(prefix);
TenantMapEntry entry(id, prefixKey.substr(0, prefixKey.size() - 8));
TenantMapEntry entry(id, prefixKey.substr(0, prefixKey.size() - 8), tenantGroup);
ASSERT(entry.prefix == prefixKey);
return entry;
@ -444,7 +487,7 @@ struct TenantManagementWorkload : TestWorkload {
state TenantName tenant = self->chooseTenantName(true);
auto itr = self->createdTenants.find(tenant);
state bool alreadyExists = itr != self->createdTenants.end();
state TenantState tenantState = itr->second;
state TenantData tenantData = alreadyExists ? itr->second : TenantData();
state OperationType operationType = TenantManagementWorkload::randomOperationType();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
@ -467,8 +510,9 @@ struct TenantManagementWorkload : TestWorkload {
entry = _entry;
}
ASSERT(alreadyExists);
ASSERT(entry.id == tenantState.id);
wait(self->checkTenant(cx, self, tenant, tenantState));
ASSERT(entry.id == tenantData.id);
ASSERT(entry.tenantGroup == tenantData.tenantGroup);
wait(self->checkTenant(cx, self, tenant, tenantData));
return Void();
} catch (Error& e) {
state bool retry = true;
@ -607,10 +651,10 @@ struct TenantManagementWorkload : TestWorkload {
ASSERT(newTenantEntry.present());
// Update Internal Tenant Map and check for correctness
TenantState tState = self->createdTenants[oldTenantName];
self->createdTenants[newTenantName] = tState;
TenantData tData = self->createdTenants[oldTenantName];
self->createdTenants[newTenantName] = tData;
self->createdTenants.erase(oldTenantName);
if (!tState.empty) {
if (!tData.empty) {
state Transaction insertTr(cx, newTenantName);
loop {
try {
@ -648,11 +692,94 @@ struct TenantManagementWorkload : TestWorkload {
}
}
ACTOR Future<Void> configureTenant(Database cx, TenantManagementWorkload* self) {
state TenantName tenant = self->chooseTenantName(true);
auto itr = self->createdTenants.find(tenant);
state bool exists = itr != self->createdTenants.end();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
state std::map<Standalone<StringRef>, Optional<Value>> configuration;
state Optional<TenantGroupName> newTenantGroup;
state bool hasInvalidOption = deterministicRandom()->random01() < 0.1;
if (!hasInvalidOption || deterministicRandom()->coinflip()) {
newTenantGroup = self->chooseTenantGroup();
configuration["tenant_group"_sr] = newTenantGroup;
}
if (hasInvalidOption) {
configuration["invalid_option"_sr] = ""_sr;
hasInvalidOption = true;
}
state bool hasInvalidSpecialKeyTuple = deterministicRandom()->random01() < 0.05;
loop {
try {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
for (auto [config, value] : configuration) {
Tuple t;
if (hasInvalidSpecialKeyTuple) {
// Wrong number of items
if (deterministicRandom()->coinflip()) {
int numItems = deterministicRandom()->randomInt(0, 3);
if (numItems > 0) {
t.append(tenant);
}
if (numItems > 1) {
t.append(config).append(""_sr);
}
}
// Wrong data types
else {
if (deterministicRandom()->coinflip()) {
t.append(0).append(config);
} else {
t.append(tenant).append(0);
}
}
} else {
t.append(tenant).append(config);
}
if (value.present()) {
tr->set(self->specialKeysTenantConfigPrefix.withSuffix(t.pack()), value.get());
} else {
tr->clear(self->specialKeysTenantConfigPrefix.withSuffix(t.pack()));
}
}
wait(tr->commit());
ASSERT(exists);
ASSERT(!hasInvalidOption);
ASSERT(!hasInvalidSpecialKeyTuple);
self->createdTenants[tenant].tenantGroup = newTenantGroup;
return Void();
} catch (Error& e) {
state Error error = e;
if (e.code() == error_code_tenant_not_found) {
ASSERT(!exists);
return Void();
} else if (e.code() == error_code_special_keys_api_failure) {
ASSERT(hasInvalidSpecialKeyTuple || hasInvalidOption);
return Void();
}
try {
wait(tr->onError(e));
} catch (Error&) {
TraceEvent(SevError, "ConfigureTenantFailure").error(error).detail("TenantName", tenant);
return Void();
}
}
}
}
Future<Void> start(Database const& cx) override { return _start(cx, this); }
ACTOR Future<Void> _start(Database cx, TenantManagementWorkload* self) {
state double start = now();
while (now() < start + self->testDuration) {
state int operation = deterministicRandom()->randomInt(0, 5);
state int operation = deterministicRandom()->randomInt(0, 6);
if (operation == 0) {
wait(self->createTenant(cx, self));
} else if (operation == 1) {
@ -661,8 +788,10 @@ struct TenantManagementWorkload : TestWorkload {
wait(self->getTenant(cx, self));
} else if (operation == 3) {
wait(self->listTenants(cx, self));
} else {
} else if (operation == 4) {
wait(self->renameTenant(cx, self));
} else if (operation == 5) {
wait(self->configureTenant(cx, self));
}
}
@ -684,7 +813,7 @@ struct TenantManagementWorkload : TestWorkload {
}
}
state std::map<TenantName, TenantState>::iterator itr = self->createdTenants.begin();
state std::map<TenantName, TenantData>::iterator itr = self->createdTenants.begin();
state std::vector<Future<Void>> checkTenants;
state TenantName beginTenant = ""_sr.withPrefix(self->localTenantNamePrefix);
state TenantName endTenant = "\xff\xff"_sr.withPrefix(self->localTenantNamePrefix);
@ -697,6 +826,7 @@ struct TenantManagementWorkload : TestWorkload {
for (auto tenant : tenants) {
ASSERT(itr != self->createdTenants.end());
ASSERT(tenant.first == itr->first);
ASSERT(tenant.second.tenantGroup == itr->second.tenantGroup);
checkTenants.push_back(self->checkTenant(cx, self, tenant.first, itr->second));
lastTenant = tenant.first;
++itr;

View File

@ -587,14 +587,20 @@ public:
}
// Removes bytes from begin up to and including the sep string, returns StringRef of the part before sep
StringRef eat(StringRef sep) {
StringRef eat(StringRef sep, bool* foundSep = nullptr) {
for (int i = 0, iend = size() - sep.size(); i <= iend; ++i) {
if (sep.compare(substr(i, sep.size())) == 0) {
if (foundSep) {
*foundSep = true;
}
StringRef token = substr(0, i);
*this = substr(i + sep.size());
return token;
}
}
if (foundSep) {
*foundSep = false;
}
return eat();
}
StringRef eat() {
@ -602,7 +608,9 @@ public:
*this = StringRef();
return r;
}
StringRef eat(const char* sep) { return eat(StringRef((const uint8_t*)sep, (int)strlen(sep))); }
StringRef eat(const char* sep, bool* foundSep = nullptr) {
return eat(StringRef((const uint8_t*)sep, (int)strlen(sep)), foundSep);
}
// Return StringRef of bytes from begin() up to but not including the first byte matching any byte in sep,
// and remove that sequence (including the sep byte) from *this
// Returns and removes all bytes from *this if no bytes within sep were found

View File

@ -174,6 +174,7 @@ public: // introduced features
PROTOCOL_VERSION_FEATURE(0x0FDB00B072000000LL, SWVersionTracking);
PROTOCOL_VERSION_FEATURE(0x0FDB00B072000000LL, EncryptionAtRest);
PROTOCOL_VERSION_FEATURE(0x0FDB00B072000000LL, ShardEncodeLocationMetaData);
PROTOCOL_VERSION_FEATURE(0x0FDB00B072000000LL, TenantGroups);
};
template <>

View File

@ -228,11 +228,12 @@ ERROR( tenant_name_required, 2130, "Tenant name must be specified to access data
ERROR( tenant_not_found, 2131, "Tenant does not exist" )
ERROR( tenant_already_exists, 2132, "A tenant with the given name already exists" )
ERROR( tenant_not_empty, 2133, "Cannot delete a non-empty tenant" )
ERROR( invalid_tenant_name, 2134, "Tenant name cannot begin with \\xff");
ERROR( tenant_prefix_allocator_conflict, 2135, "The database already has keys stored at the prefix allocated for the tenant");
ERROR( tenants_disabled, 2136, "Tenants have been disabled in the cluster");
ERROR( unknown_tenant, 2137, "Tenant is not available from this server")
ERROR( illegal_tenant_access, 2138, "Illegal tenant access")
ERROR( invalid_tenant_name, 2134, "Tenant name cannot begin with \\xff" )
ERROR( tenant_prefix_allocator_conflict, 2135, "The database already has keys stored at the prefix allocated for the tenant" )
ERROR( tenants_disabled, 2136, "Tenants have been disabled in the cluster" )
ERROR( unknown_tenant, 2137, "Tenant is not available from this server" )
ERROR( illegal_tenant_access, 2138, "Illegal tenant access" )
ERROR( invalid_tenant_group_name, 2139, "Tenant group name cannot begin with \\xff" )
// 2200 - errors from bindings and official APIs
ERROR( api_version_unset, 2200, "API version is not set" )

View File

@ -10,7 +10,7 @@ runSetup = true
[[test.workload]]
testName = 'TenantManagement'
maxTenants = 1000
maxTenants = 1000
testDuration = 60
[[test.workload]]

View File

@ -10,5 +10,5 @@ runSetup = true
[[test.workload]]
testName = 'TenantManagement'
maxTenants = 1000
testDuration = 60
maxTenants = 1000
testDuration = 60