Merge pull request #8592 from sfc-gh-jfu/tenant-list-filter

Add option to filter the tenant list command for metacluster
This commit is contained in:
Jon Fu 2022-11-17 09:04:58 -08:00 committed by GitHub
commit 349f5821d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 22 deletions

View File

@ -475,7 +475,7 @@ Deletes a tenant from the cluster. The tenant must be empty.
list
^^^^
``tenant list [BEGIN] [END] [LIMIT]``
``tenant list [BEGIN] [END] [limit=LIMIT] [offset=OFFSET] [state=<STATE1>,<STATE2>,...]``
Lists the tenants present in the cluster.
@ -485,6 +485,10 @@ Lists the tenants present in the cluster.
``LIMIT`` - the number of tenants to list. Defaults to 100.
``OFFSET`` - the number of items to skip over, starting from the beginning of the range. Defaults to 0.
``STATE``` - TenantState(s) to filter the list with. Defaults to no filters.
get
^^^

View File

@ -87,6 +87,56 @@ parseTenantConfiguration(std::vector<StringRef> const& tokens, int startIndex, b
return configParams;
}
bool parseTenantListOptions(std::vector<StringRef> const& tokens,
int startIndex,
int& limit,
int& offset,
std::vector<TenantState>& filters) {
for (int tokenNum = startIndex; tokenNum < tokens.size(); ++tokenNum) {
Optional<Value> value;
StringRef token = tokens[tokenNum];
StringRef param;
bool foundEquals;
param = token.eat("=", &foundEquals);
if (!foundEquals) {
fmt::print(stderr,
"ERROR: invalid option string `{}'. String must specify a value using `='.\n",
param.toString().c_str());
return false;
}
value = token;
if (tokencmp(param, "limit")) {
int n = 0;
if (sscanf(value.get().toString().c_str(), "%d%n", &limit, &n) != 1 || n != value.get().size() ||
limit <= 0) {
fmt::print(stderr, "ERROR: invalid limit `{}'\n", token.toString().c_str());
return false;
}
} else if (tokencmp(param, "offset")) {
int n = 0;
if (sscanf(value.get().toString().c_str(), "%d%n", &offset, &n) != 1 || n != value.get().size() ||
offset < 0) {
fmt::print(stderr, "ERROR: invalid offset `{}'\n", token.toString().c_str());
return false;
}
} else if (tokencmp(param, "state")) {
auto filterStrings = value.get().splitAny(","_sr);
try {
for (auto sref : filterStrings) {
filters.push_back(TenantMapEntry::stringToTenantState(sref.toString()));
}
} catch (Error& e) {
fmt::print(stderr, "ERROR: unrecognized tenant state(s) `{}'.\n", value.get().toString());
return false;
}
} else {
fmt::print(stderr, "ERROR: unrecognized parameter `{}'.\n", param.toString().c_str());
return false;
}
}
return true;
}
Key makeConfigKey(TenantNameRef tenantName, StringRef configName) {
return tenantConfigSpecialKeyRange.begin.withSuffix(Tuple().append(tenantName).append(configName).pack());
}
@ -225,17 +275,21 @@ ACTOR Future<bool> tenantDeleteCommand(Reference<IDatabase> db, std::vector<Stri
// tenant list command
ACTOR Future<bool> tenantListCommand(Reference<IDatabase> db, std::vector<StringRef> tokens) {
if (tokens.size() > 5) {
fmt::print("Usage: tenant list [BEGIN] [END] [LIMIT]\n\n");
if (tokens.size() > 7) {
fmt::print("Usage: tenant list [BEGIN] [END] [limit=LIMIT] [offset=OFFSET] [state=<STATE1>,<STATE2>,...]\n\n");
fmt::print("Lists the tenants in a cluster.\n");
fmt::print("Only tenants in the range BEGIN - END will be printed.\n");
fmt::print("An optional LIMIT can be specified to limit the number of results (default 100).\n");
fmt::print("Optionally skip over the first OFFSET results (default 0).\n");
fmt::print("Optional comma-separated tenant state(s) can be provided to filter the list.\n");
return false;
}
state StringRef beginTenant = ""_sr;
state StringRef endTenant = "\xff\xff"_sr;
state int limit = 100;
state int offset = 0;
state std::vector<TenantState> filters;
if (tokens.size() >= 3) {
beginTenant = tokens[2];
@ -243,14 +297,12 @@ ACTOR Future<bool> tenantListCommand(Reference<IDatabase> db, std::vector<String
if (tokens.size() >= 4) {
endTenant = tokens[3];
if (endTenant <= beginTenant) {
fmt::print(stderr, "ERROR: end must be larger than begin");
fmt::print(stderr, "ERROR: end must be larger than begin\n");
return false;
}
}
if (tokens.size() == 5) {
int n = 0;
if (sscanf(tokens[4].toString().c_str(), "%d%n", &limit, &n) != 1 || n != tokens[4].size() || limit <= 0) {
fmt::print(stderr, "ERROR: invalid limit `{}'\n", tokens[4].toString().c_str());
if (tokens.size() >= 5) {
if (!parseTenantListOptions(tokens, 4, limit, offset, filters)) {
return false;
}
}
@ -266,7 +318,7 @@ ACTOR Future<bool> tenantListCommand(Reference<IDatabase> db, std::vector<String
state std::vector<TenantName> tenantNames;
if (clusterType == ClusterType::METACLUSTER_MANAGEMENT) {
std::vector<std::pair<TenantName, TenantMapEntry>> tenants =
wait(MetaclusterAPI::listTenantsTransaction(tr, beginTenant, endTenant, limit));
wait(MetaclusterAPI::listTenants(db, beginTenant, endTenant, limit, offset, filters));
for (auto tenant : tenants) {
tenantNames.push_back(tenant.first);
}
@ -613,8 +665,10 @@ std::vector<const char*> tenantHintGenerator(std::vector<StringRef> const& token
} else if (tokencmp(tokens[1], "delete") && tokens.size() < 3) {
static std::vector<const char*> opts = { "<NAME>" };
return std::vector<const char*>(opts.begin() + tokens.size() - 2, opts.end());
} else if (tokencmp(tokens[1], "list") && tokens.size() < 5) {
static std::vector<const char*> opts = { "[BEGIN]", "[END]", "[LIMIT]" };
} else if (tokencmp(tokens[1], "list") && tokens.size() < 7) {
static std::vector<const char*> opts = {
"[BEGIN]", "[END]", "[limit=LIMIT]", "[offset=OFFSET]", "[state=<STATE1>,<STATE2>,...]"
};
return std::vector<const char*>(opts.begin() + tokens.size() - 2, opts.end());
} else if (tokencmp(tokens[1], "get") && tokens.size() < 4) {
static std::vector<const char*> opts = { "<NAME>", "[JSON]" };

View File

@ -786,7 +786,7 @@ def tenant_list(logger):
output = run_fdbcli_command('tenant list')
assert output == '1. tenant\n 2. tenant2'
output = run_fdbcli_command('tenant list a z 1')
output = run_fdbcli_command('tenant list a z limit=1')
assert output == '1. tenant'
output = run_fdbcli_command('tenant list a tenant2')
@ -801,9 +801,15 @@ def tenant_list(logger):
output = run_fdbcli_command_and_get_error('tenant list b a')
assert output == 'ERROR: end must be larger than begin'
output = run_fdbcli_command_and_get_error('tenant list a b 12x')
output = run_fdbcli_command_and_get_error('tenant list a b limit=12x')
assert output == 'ERROR: invalid limit `12x\''
output = run_fdbcli_command_and_get_error('tenant list a b offset=13y')
assert output == 'ERROR: invalid offset `13y\''
output = run_fdbcli_command_and_get_error('tenant list a b state=14z')
assert output == 'ERROR: unrecognized tenant state(s) `14z\'.'
@enable_logging()
def tenant_get(logger):
setup_tenants(['tenant', 'tenant2 tenant_group=tenant_group2'])

View File

@ -70,6 +70,7 @@ std::string TenantMapEntry::tenantStateToString(TenantState tenantState) {
}
TenantState TenantMapEntry::stringToTenantState(std::string stateStr) {
std::transform(stateStr.begin(), stateStr.end(), stateStr.begin(), [](unsigned char c) { return std::tolower(c); });
if (stateStr == "registering") {
return TenantState::REGISTERING;
} else if (stateStr == "ready") {
@ -86,7 +87,7 @@ TenantState TenantMapEntry::stringToTenantState(std::string stateStr) {
return TenantState::ERROR;
}
UNREACHABLE();
throw invalid_option();
}
std::string TenantMapEntry::tenantLockStateToString(TenantLockState tenantState) {
@ -103,6 +104,7 @@ std::string TenantMapEntry::tenantLockStateToString(TenantLockState tenantState)
}
TenantLockState TenantMapEntry::stringToTenantLockState(std::string stateStr) {
std::transform(stateStr.begin(), stateStr.end(), stateStr.begin(), [](unsigned char c) { return std::tolower(c); });
if (stateStr == "unlocked") {
return TenantLockState::UNLOCKED;
} else if (stateStr == "read only") {

View File

@ -1561,26 +1561,64 @@ Future<std::vector<std::pair<TenantName, TenantMapEntry>>> listTenantsTransactio
int limit) {
tr->setOption(FDBTransactionOptions::RAW_ACCESS);
KeyBackedRangeResult<std::pair<TenantName, TenantMapEntry>> results =
state KeyBackedRangeResult<std::pair<TenantName, TenantMapEntry>> results =
wait(ManagementClusterMetadata::tenantMetadata().tenantMap.getRange(tr, begin, end, limit));
return results.results;
}
ACTOR template <class DB>
Future<std::vector<std::pair<TenantName, TenantMapEntry>>> listTenants(Reference<DB> db,
TenantName begin,
TenantName end,
int limit) {
Future<std::vector<std::pair<TenantName, TenantMapEntry>>> listTenants(
Reference<DB> db,
TenantName begin,
TenantName end,
int limit,
int offset = 0,
std::vector<TenantState> filters = std::vector<TenantState>()) {
state Reference<typename DB::TransactionT> tr = db->createTransaction();
loop {
try {
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE);
std::vector<std::pair<TenantName, TenantMapEntry>> tenants =
wait(listTenantsTransaction(tr, begin, end, limit));
return tenants;
if (filters.empty()) {
state std::vector<std::pair<TenantName, TenantMapEntry>> tenants;
wait(store(tenants, listTenantsTransaction(tr, begin, end, limit + offset)));
if (offset >= tenants.size()) {
tenants.clear();
} else if (offset > 0) {
tenants.erase(tenants.begin(), tenants.begin() + offset);
}
return tenants;
}
tr->setOption(FDBTransactionOptions::RAW_ACCESS);
state KeyBackedRangeResult<std::pair<TenantName, TenantMapEntry>> results =
wait(ManagementClusterMetadata::tenantMetadata().tenantMap.getRange(
tr, begin, end, std::max(limit + offset, 100)));
state std::vector<std::pair<TenantName, TenantMapEntry>> filterResults;
state int count = 0;
loop {
for (auto pair : results.results) {
if (filters.empty() || std::count(filters.begin(), filters.end(), pair.second.tenantState)) {
++count;
if (count > offset) {
filterResults.push_back(pair);
if (count - offset == limit) {
ASSERT(count - offset == filterResults.size());
return filterResults;
}
}
}
}
if (!results.more) {
return filterResults;
}
begin = keyAfter(results.results.back().first);
wait(store(results,
ManagementClusterMetadata::tenantMetadata().tenantMap.getRange(
tr, begin, end, std::max(limit + offset, 100))));
}
} catch (Error& e) {
wait(safeThreadFutureToFuture(tr->onError(e)));
}

View File

@ -392,6 +392,35 @@ struct MetaclusterManagementWorkload : TestWorkload {
return Void();
}
ACTOR static Future<Void> verifyListFilter(MetaclusterManagementWorkload* self, TenantName tenant) {
try {
state TenantMapEntry checkEntry = wait(MetaclusterAPI::getTenant(self->managementDb, tenant));
state TenantState checkState = checkEntry.tenantState;
state std::vector<TenantState> filters;
filters.push_back(checkState);
state std::vector<std::pair<TenantName, TenantMapEntry>> tenantList;
// Possible to have changed state between now and the getTenant call above
state TenantMapEntry checkEntry2;
wait(store(checkEntry2, MetaclusterAPI::getTenant(self->managementDb, tenant)) &&
store(tenantList,
MetaclusterAPI::listTenants(self->managementDb, ""_sr, "\xff\xff"_sr, 10e6, 0, filters)));
bool found = false;
for (auto pair : tenantList) {
ASSERT(pair.second.tenantState == checkState);
if (pair.first == tenant) {
found = true;
}
}
ASSERT(found || checkEntry2.tenantState != checkState);
} catch (Error& e) {
if (e.code() != error_code_tenant_not_found) {
TraceEvent(SevError, "VerifyListFilterFailure").error(e).detail("Tenant", tenant);
throw;
}
}
return Void();
}
ACTOR static Future<Void> createTenant(MetaclusterManagementWorkload* self) {
state TenantName tenant = self->chooseTenantName();
state Optional<TenantGroupName> tenantGroup = self->chooseTenantGroup();
@ -433,6 +462,7 @@ struct MetaclusterManagementWorkload : TestWorkload {
break;
} else {
retried = true;
wait(verifyListFilter(self, tenant));
}
} catch (Error& e) {
if (e.code() == error_code_tenant_already_exists && retried && !exists) {
@ -533,6 +563,7 @@ struct MetaclusterManagementWorkload : TestWorkload {
break;
} else {
retried = true;
wait(verifyListFilter(self, tenant));
}
} catch (Error& e) {
if (e.code() == error_code_tenant_not_found && retried && exists) {
@ -622,6 +653,7 @@ struct MetaclusterManagementWorkload : TestWorkload {
if (result.present()) {
break;
}
wait(verifyListFilter(self, tenant));
}
ASSERT(exists);
@ -716,6 +748,8 @@ struct MetaclusterManagementWorkload : TestWorkload {
}
retried = true;
wait(verifyListFilter(self, tenant));
wait(verifyListFilter(self, newTenantName));
} catch (Error& e) {
// If we retry the rename after it had succeeded, we will get an error that we should ignore
if (e.code() == error_code_tenant_not_found && exists && !newTenantExists && retried) {