foundationdb/fdbserver/workloads/TenantManagement.actor.cpp

591 lines
19 KiB
C++

/*
* 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 <cstdint>
#include <limits>
#include "fdbclient/FDBOptions.g.h"
#include "fdbclient/GenericManagementAPI.actor.h"
#include "fdbrpc/simulator.h"
#include "fdbserver/workloads/workloads.actor.h"
#include "fdbserver/Knobs.h"
#include "flow/Error.h"
#include "flow/IRandom.h"
#include "flow/flow.h"
#include "flow/actorcompiler.h" // This must be the last #include.
struct TenantManagementWorkload : TestWorkload {
struct TenantState {
int64_t id;
bool empty;
TenantState() : id(-1), empty(true) {}
TenantState(int64_t id, bool empty) : id(id), empty(empty) {}
};
std::map<TenantName, TenantState> createdTenants;
int64_t maxId = -1;
Key tenantSubspace;
const Key keyName = "key"_sr;
const Key tenantSubspaceKey = "tenant_subspace"_sr;
const Value noTenantValue = "no_tenant"_sr;
const TenantName tenantNamePrefix = "tenant_management_workload_"_sr;
TenantName localTenantNamePrefix;
const Key specialKeysTenantMapPrefix = TenantMapRangeImpl::submoduleRange.begin.withPrefix(
SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin);
int maxTenants;
double testDuration;
enum class OperationType { SPECIAL_KEYS, MANAGEMENT_DATABASE, MANAGEMENT_TRANSACTION };
static OperationType randomOperationType() {
int randomNum = deterministicRandom()->randomInt(0, 3);
if (randomNum == 0) {
return OperationType::SPECIAL_KEYS;
} else if (randomNum == 1) {
return OperationType::MANAGEMENT_DATABASE;
} else {
return OperationType::MANAGEMENT_TRANSACTION;
}
}
TenantManagementWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
maxTenants = std::min<int>(1e8 - 1, getOption(options, "maxTenants"_sr, 1000));
testDuration = getOption(options, "testDuration"_sr, 60.0);
localTenantNamePrefix = format("%stenant_%d_", tenantNamePrefix.toString().c_str(), clientId);
}
std::string description() const override { return "TenantManagement"; }
Future<Void> setup(Database const& cx) override { return _setup(cx, this); }
ACTOR Future<Void> _setup(Database cx, TenantManagementWorkload* self) {
state Transaction tr(cx);
if (self->clientId == 0) {
self->tenantSubspace = makeString(deterministicRandom()->randomInt(0, 10));
loop {
generateRandomData(mutateString(self->tenantSubspace), self->tenantSubspace.size());
if (!self->tenantSubspace.startsWith(systemKeys.begin)) {
break;
}
}
loop {
try {
tr.setOption(FDBTransactionOptions::RAW_ACCESS);
tr.set(self->keyName, self->noTenantValue);
tr.set(self->tenantSubspaceKey, self->tenantSubspace);
tr.set(tenantDataPrefixKey, self->tenantSubspace);
wait(tr.commit());
break;
} catch (Error& e) {
wait(tr.onError(e));
}
}
} else {
loop {
try {
tr.setOption(FDBTransactionOptions::RAW_ACCESS);
Optional<Value> val = wait(tr.get(self->tenantSubspaceKey));
if (val.present()) {
self->tenantSubspace = val.get();
break;
}
wait(delay(1.0));
} catch (Error& e) {
wait(tr.onError(e));
}
}
}
return Void();
}
TenantName chooseTenantName(bool allowSystemTenant) {
TenantName tenant(format(
"%s%08d", localTenantNamePrefix.toString().c_str(), deterministicRandom()->randomInt(0, maxTenants)));
if (allowSystemTenant && deterministicRandom()->random01() < 0.02) {
tenant = tenant.withPrefix("\xff"_sr);
}
return tenant;
}
ACTOR Future<Void> createTenant(Database cx, TenantManagementWorkload* self) {
state TenantName tenant = self->chooseTenantName(true);
state bool alreadyExists = self->createdTenants.count(tenant);
state OperationType operationType = TenantManagementWorkload::randomOperationType();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
loop {
try {
if (operationType == OperationType::SPECIAL_KEYS) {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
Key key = self->specialKeysTenantMapPrefix.withSuffix(tenant);
tr->set(key, ""_sr);
wait(tr->commit());
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
wait(ManagementAPI::createTenant(cx.getReference(), tenant));
} else {
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
Optional<TenantMapEntry> _ = wait(ManagementAPI::createTenantTransaction(tr, tenant));
wait(tr->commit());
}
if (operationType != OperationType::MANAGEMENT_DATABASE && alreadyExists) {
return Void();
}
ASSERT(!alreadyExists);
ASSERT(!tenant.startsWith("\xff"_sr));
state Optional<TenantMapEntry> entry = wait(ManagementAPI::tryGetTenant(cx.getReference(), tenant));
ASSERT(entry.present());
ASSERT(entry.get().id > self->maxId);
ASSERT(entry.get().prefix.startsWith(self->tenantSubspace));
self->maxId = entry.get().id;
self->createdTenants[tenant] = TenantState(entry.get().id, true);
state bool insertData = deterministicRandom()->random01() < 0.5;
if (insertData) {
state Transaction insertTr(cx, tenant);
loop {
try {
insertTr.set(self->keyName, tenant);
wait(insertTr.commit());
break;
} catch (Error& e) {
wait(insertTr.onError(e));
}
}
self->createdTenants[tenant].empty = false;
state Transaction checkTr(cx);
loop {
try {
checkTr.setOption(FDBTransactionOptions::RAW_ACCESS);
Optional<Value> val = wait(checkTr.get(self->keyName.withPrefix(entry.get().prefix)));
ASSERT(val.present());
ASSERT(val.get() == tenant);
break;
} catch (Error& e) {
wait(checkTr.onError(e));
}
}
}
wait(self->checkTenant(cx, self, tenant, self->createdTenants[tenant]));
return Void();
} catch (Error& e) {
if (e.code() == error_code_invalid_tenant_name) {
ASSERT(tenant.startsWith("\xff"_sr));
return Void();
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
if (e.code() == error_code_tenant_already_exists) {
ASSERT(alreadyExists && operationType == OperationType::MANAGEMENT_DATABASE);
} else {
TraceEvent(SevError, "CreateTenantFailure").error(e).detail("TenantName", tenant);
}
return Void();
} else {
try {
wait(tr->onError(e));
} catch (Error& e) {
TraceEvent(SevError, "CreateTenantFailure").error(e).detail("TenantName", tenant);
return Void();
}
}
}
}
}
ACTOR Future<Void> deleteTenant(Database cx, TenantManagementWorkload* self) {
state TenantName beginTenant = self->chooseTenantName(true);
state OperationType operationType = TenantManagementWorkload::randomOperationType();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
state Optional<TenantName> endTenant = operationType != OperationType::MANAGEMENT_DATABASE &&
!beginTenant.startsWith("\xff"_sr) &&
deterministicRandom()->random01() < 0.2
? Optional<TenantName>(self->chooseTenantName(false))
: Optional<TenantName>();
if (endTenant.present() && endTenant < beginTenant) {
TenantName temp = beginTenant;
beginTenant = endTenant.get();
endTenant = temp;
}
auto itr = self->createdTenants.find(beginTenant);
state bool alreadyExists = itr != self->createdTenants.end();
state bool isEmpty = true;
state std::vector<TenantName> tenants;
if (!endTenant.present()) {
tenants.push_back(beginTenant);
} else if (endTenant.present()) {
for (auto itr = self->createdTenants.lower_bound(beginTenant);
itr != self->createdTenants.end() && itr->first < endTenant.get();
++itr) {
tenants.push_back(itr->first);
}
}
state int tenantIndex;
try {
if (alreadyExists || endTenant.present()) {
for (tenantIndex = 0; tenantIndex < tenants.size(); ++tenantIndex) {
if (deterministicRandom()->random01() < 0.9) {
state Transaction clearTr(cx, tenants[tenantIndex]);
loop {
try {
clearTr.clear(self->keyName);
wait(clearTr.commit());
auto itr = self->createdTenants.find(tenants[tenantIndex]);
ASSERT(itr != self->createdTenants.end());
itr->second.empty = true;
break;
} catch (Error& e) {
wait(clearTr.onError(e));
}
}
} else {
auto itr = self->createdTenants.find(tenants[tenantIndex]);
ASSERT(itr != self->createdTenants.end());
isEmpty = isEmpty && itr->second.empty;
}
}
}
} catch (Error& e) {
TraceEvent(SevError, "DeleteTenantFailure")
.error(e)
.detail("TenantName", beginTenant)
.detail("EndTenant", endTenant);
return Void();
}
loop {
try {
if (operationType == OperationType::SPECIAL_KEYS) {
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
Key key = self->specialKeysTenantMapPrefix.withSuffix(beginTenant);
if (endTenant.present()) {
tr->clear(KeyRangeRef(key, self->specialKeysTenantMapPrefix.withSuffix(endTenant.get())));
} else {
tr->clear(key);
}
wait(tr->commit());
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
ASSERT(tenants.size() == 1);
for (tenantIndex = 0; tenantIndex != tenants.size(); ++tenantIndex) {
wait(ManagementAPI::deleteTenant(cx.getReference(), tenants[tenantIndex]));
}
} else {
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
std::vector<Future<Void>> deleteFutures;
for (tenantIndex = 0; tenantIndex != tenants.size(); ++tenantIndex) {
deleteFutures.push_back(ManagementAPI::deleteTenantTransaction(tr, tenants[tenantIndex]));
}
wait(waitForAll(deleteFutures));
wait(tr->commit());
}
if (!alreadyExists && !endTenant.present() && operationType != OperationType::MANAGEMENT_DATABASE) {
return Void();
}
ASSERT(alreadyExists || endTenant.present());
ASSERT(isEmpty);
for (auto tenant : tenants) {
self->createdTenants.erase(tenant);
}
return Void();
} catch (Error& e) {
if (e.code() == error_code_tenant_not_empty) {
ASSERT(!isEmpty);
return Void();
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
if (e.code() == error_code_tenant_not_found) {
ASSERT(!alreadyExists && !endTenant.present());
} else {
TraceEvent(SevError, "DeleteTenantFailure")
.error(e)
.detail("TenantName", beginTenant)
.detail("EndTenant", endTenant);
}
return Void();
} else {
try {
wait(tr->onError(e));
} catch (Error& e) {
TraceEvent(SevError, "DeleteTenantFailure")
.error(e)
.detail("TenantName", beginTenant)
.detail("EndTenant", endTenant);
return Void();
}
}
}
}
}
ACTOR Future<Void> checkTenant(Database cx,
TenantManagementWorkload* self,
TenantName tenant,
TenantState tenantState) {
state Transaction tr(cx, tenant);
loop {
try {
state RangeResult result = wait(tr.getRange(KeyRangeRef(""_sr, "\xff"_sr), 2));
if (tenantState.empty) {
ASSERT(result.size() == 0);
} else {
ASSERT(result.size() == 1);
ASSERT(result[0].key == self->keyName);
ASSERT(result[0].value == tenant);
}
break;
} catch (Error& e) {
wait(tr.onError(e));
}
}
return Void();
}
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;
jsonDoc.get("id", id);
jsonDoc.get("prefix", prefix);
Key prefixKey = KeyRef(prefix);
TenantMapEntry entry(id, prefixKey.substr(0, prefixKey.size() - 8));
ASSERT(entry.prefix == prefixKey);
return entry;
}
ACTOR Future<Void> getTenant(Database cx, TenantManagementWorkload* self) {
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 OperationType operationType = TenantManagementWorkload::randomOperationType();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
loop {
try {
state TenantMapEntry entry;
if (operationType == OperationType::SPECIAL_KEYS) {
Key key = self->specialKeysTenantMapPrefix.withSuffix(tenant);
Optional<Value> value = wait(tr->get(key));
if (!value.present()) {
throw tenant_not_found();
}
entry = TenantManagementWorkload::jsonToTenantMapEntry(value.get());
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
TenantMapEntry _entry = wait(ManagementAPI::getTenant(cx.getReference(), tenant));
entry = _entry;
} else {
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
TenantMapEntry _entry = wait(ManagementAPI::getTenantTransaction(tr, tenant));
entry = _entry;
}
ASSERT(alreadyExists);
ASSERT(entry.id == tenantState.id);
wait(self->checkTenant(cx, self, tenant, tenantState));
return Void();
} catch (Error& e) {
state bool retry = true;
state Error error = e;
if (e.code() == error_code_tenant_not_found) {
ASSERT(!alreadyExists);
return Void();
} else if (operationType != OperationType::MANAGEMENT_DATABASE) {
try {
wait(tr->onError(e));
} catch (Error& e) {
error = e;
retry = false;
}
}
if (!retry) {
TraceEvent(SevError, "GetTenantFailure").error(error).detail("TenantName", tenant);
return Void();
}
}
}
}
ACTOR Future<Void> listTenants(Database cx, TenantManagementWorkload* self) {
state TenantName beginTenant = self->chooseTenantName(false);
state TenantName endTenant = self->chooseTenantName(false);
state int limit = std::min(CLIENT_KNOBS->TOO_MANY, deterministicRandom()->randomInt(1, self->maxTenants * 2));
state OperationType operationType = TenantManagementWorkload::randomOperationType();
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
if (beginTenant > endTenant) {
std::swap(beginTenant, endTenant);
}
loop {
try {
state std::map<TenantName, TenantMapEntry> tenants;
if (operationType == OperationType::SPECIAL_KEYS) {
KeyRange range = KeyRangeRef(beginTenant, endTenant).withPrefix(self->specialKeysTenantMapPrefix);
RangeResult results = wait(tr->getRange(range, limit));
for (auto result : results) {
tenants[result.key.removePrefix(self->specialKeysTenantMapPrefix)] =
TenantManagementWorkload::jsonToTenantMapEntry(result.value);
}
} else if (operationType == OperationType::MANAGEMENT_DATABASE) {
std::map<TenantName, TenantMapEntry> _tenants =
wait(ManagementAPI::listTenants(cx.getReference(), beginTenant, endTenant, limit));
tenants = _tenants;
} else {
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
std::map<TenantName, TenantMapEntry> _tenants =
wait(ManagementAPI::listTenantsTransaction(tr, beginTenant, endTenant, limit));
tenants = _tenants;
}
ASSERT(tenants.size() <= limit);
auto localItr = self->createdTenants.lower_bound(beginTenant);
auto tenantMapItr = tenants.begin();
for (; tenantMapItr != tenants.end(); ++tenantMapItr, ++localItr) {
ASSERT(localItr != self->createdTenants.end());
ASSERT(localItr->first == tenantMapItr->first);
}
if (!(tenants.size() == limit || localItr == self->createdTenants.end())) {
for (auto tenant : self->createdTenants) {
TraceEvent("ExistingTenant").detail("Tenant", tenant.first);
}
}
ASSERT(tenants.size() == limit || localItr == self->createdTenants.end() ||
localItr->first >= endTenant);
return Void();
} catch (Error& e) {
state bool retry = true;
state Error error = e;
if (operationType != OperationType::MANAGEMENT_DATABASE) {
try {
wait(tr->onError(e));
} catch (Error& e) {
error = e;
retry = false;
}
}
if (!retry) {
TraceEvent(SevError, "ListTenantFailure")
.error(error)
.detail("BeginTenant", beginTenant)
.detail("EndTenant", endTenant);
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, 4);
if (operation == 0) {
wait(self->createTenant(cx, self));
} else if (operation == 1) {
wait(self->deleteTenant(cx, self));
} else if (operation == 2) {
wait(self->getTenant(cx, self));
} else {
wait(self->listTenants(cx, self));
}
}
return Void();
}
Future<bool> check(Database const& cx) override { return _check(cx, this); }
ACTOR Future<bool> _check(Database cx, TenantManagementWorkload* self) {
state Transaction tr(cx);
loop {
try {
tr.setOption(FDBTransactionOptions::RAW_ACCESS);
Optional<Value> val = wait(tr.get(self->keyName));
ASSERT(val.present() && val.get() == self->noTenantValue);
break;
} catch (Error& e) {
wait(tr.onError(e));
}
}
state std::map<TenantName, TenantState>::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);
loop {
std::map<TenantName, TenantMapEntry> tenants =
wait(ManagementAPI::listTenants(cx.getReference(), beginTenant, endTenant, 1000));
TenantNameRef lastTenant;
for (auto tenant : tenants) {
ASSERT(itr != self->createdTenants.end());
ASSERT(tenant.first == itr->first);
checkTenants.push_back(self->checkTenant(cx, self, tenant.first, itr->second));
lastTenant = tenant.first;
++itr;
}
if (tenants.size() < 1000) {
break;
} else {
beginTenant = keyAfter(lastTenant);
}
}
ASSERT(itr == self->createdTenants.end());
wait(waitForAll(checkTenants));
return true;
}
void getMetrics(std::vector<PerfMetric>& m) override {}
};
WorkloadFactory<TenantManagementWorkload> TenantManagementWorkload("TenantManagement");