Add support for tenant groups
This commit is contained in:
parent
02ab3b05ab
commit
c08592368f
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <>
|
||||
|
|
|
@ -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" )
|
||||
|
|
|
@ -10,7 +10,7 @@ runSetup = true
|
|||
|
||||
[[test.workload]]
|
||||
testName = 'TenantManagement'
|
||||
maxTenants = 1000
|
||||
maxTenants = 1000
|
||||
testDuration = 60
|
||||
|
||||
[[test.workload]]
|
||||
|
|
|
@ -10,5 +10,5 @@ runSetup = true
|
|||
|
||||
[[test.workload]]
|
||||
testName = 'TenantManagement'
|
||||
maxTenants = 1000
|
||||
testDuration = 60
|
||||
maxTenants = 1000
|
||||
testDuration = 60
|
||||
|
|
Loading…
Reference in New Issue