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:
commit
349f5821d7
|
@ -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
|
||||
^^^
|
||||
|
||||
|
|
|
@ -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]" };
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue