foundationdb/fdbserver/workloads/TenantLock.actor.cpp

178 lines
7.4 KiB
C++

/*
* TenantLock.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 "fdbclient/TenantManagement.actor.h"
#include "fdbclient/ReadYourWrites.h"
#include "fdbserver/workloads/workloads.actor.h"
#include "flow/actorcompiler.h" // has to be last include
namespace {
struct TenantLock : TestWorkload {
static constexpr auto NAME = "TenantLock";
Value tenant1;
Value tenant2;
enum class LockAwareTx { NO, READ, READ_WRITE };
TenantLock(WorkloadContext const& wcx) : TestWorkload(wcx) {
tenant1 = getOption(options, "tenant1"_sr, ""_sr);
tenant2 = getOption(options, "tenant2"_sr, ""_sr);
// Both tenants should be created using the CreateTenant workload and then passed to this workload in the
// toml file
ASSERT(!tenant1.empty() && !tenant2.empty());
}
ACTOR static Future<Optional<UID>> probeTx(Database db,
TenantName name,
Optional<UID> expectedVal,
bool readOnly = false,
LockAwareTx lA = LockAwareTx::NO) {
state ReadYourWritesTransaction tr(db, makeReference<Tenant>(db, name));
state UID result = deterministicRandom()->randomUniqueID();
state Value resultValue = BinaryWriter::toValue(result, IncludeVersion());
loop {
try {
switch (lA) {
case LockAwareTx::NO:
break;
case LockAwareTx::READ:
tr.setOption(FDBTransactionOptions::READ_LOCK_AWARE);
break;
case LockAwareTx::READ_WRITE:
tr.setOption(FDBTransactionOptions::LOCK_AWARE);
}
Optional<Value> val = wait(tr.get("foo"_sr));
if (val != resultValue) {
ASSERT(expectedVal.present() == val.present());
ASSERT(!val.present() ||
BinaryReader::fromStringRef<UID>(val.get(), IncludeVersion()) == expectedVal.get());
}
if (readOnly) {
return BinaryReader::fromStringRef<UID>(val.get(), IncludeVersion());
}
tr.set("foo"_sr, resultValue);
wait(tr.commit());
return result;
} catch (Error& e) {
TraceEvent("ProbeTxError").error(e);
wait(tr.onError(e));
}
}
}
ACTOR static Future<Void> changeLockState(Database db,
TenantName name,
TenantAPI::TenantLockState desiredState,
UID lockID) {
state Reference<Tenant> tenant = makeReference<Tenant>(db, name);
state ReadYourWritesTransaction tr(db);
loop {
try {
tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
wait(tenant->ready());
wait(TenantAPI::changeLockState(&tr, tenant->id(), desiredState, lockID));
wait(tr.commit());
return Void();
} catch (Error& e) {
wait(tr.onError(e));
}
}
}
ACTOR static Future<Void> _start(TenantLock* self, Database db) {
state Optional<UID> currentValue1;
state Optional<UID> currentValue2;
state UID lockID1 = deterministicRandom()->randomUniqueID();
state UID lockID2 = deterministicRandom()->randomUniqueID();
TraceEvent("TenantLockProgress").detail("Phase", "First Tx against unlocked tenant").log();
wait(store(currentValue1, probeTx(db, self->tenant1, currentValue1)));
wait(store(currentValue2, probeTx(db, self->tenant2, currentValue2)));
TraceEvent("TenantLockProgress")
.detail("Phase", "Lock Both tenants")
.detail("LockID1", lockID1)
.detail("LockID2", lockID2)
.log();
wait(changeLockState(db, self->tenant1, TenantAPI::TenantLockState::LOCKED, lockID1));
wait(changeLockState(db, self->tenant2, TenantAPI::TenantLockState::LOCKED, lockID2));
TraceEvent("TenantLockProgress").detail("Phase", "Tx against locked tenant").log();
wait(testExpectedError(success(probeTx(db, self->tenant1, currentValue1)),
"AccessLockedTenant",
tenant_locked(),
Optional<bool*>(),
{},
Error::fromCode(error_code_internal_error)));
TraceEvent("TenantLockProgress").detail("Phase", "Read-only Tx against locked tenant").log();
wait(testExpectedError(success(probeTx(db, self->tenant2, currentValue2, true)),
"AccessLockedTenant",
tenant_locked(),
Optional<bool*>(),
{},
Error::fromCode(error_code_internal_error)));
// make sure we can read with read-lock-aware
TraceEvent("TenantLockProgress")
.detail("Phase", "Read-only Tx against locked tenant with read-lock-aware")
.log();
wait(success(probeTx(db, self->tenant1, currentValue1, true, LockAwareTx::READ)));
// make sure we can write with a lock-aware transaction
TraceEvent("TenantLockProgress").detail("Phase", "Read-write Tx against locked tenant with lock-aware").log();
wait(store(currentValue2, probeTx(db, self->tenant2, currentValue2, false, LockAwareTx::READ_WRITE)));
TraceEvent("TenantLockProgress").detail("Phase", "Unlock tenant1").log();
wait(changeLockState(db, self->tenant1, TenantAPI::TenantLockState::UNLOCKED, lockID1));
TraceEvent("TenantLockProgress").detail("Phase", "Tx against unlocked tenant1").log();
wait(store(currentValue1, probeTx(db, self->tenant1, currentValue1)));
TraceEvent("TenantLockProgress").detail("Phase", "Tx against locked tenant2").log();
wait(testExpectedError(success(probeTx(db, self->tenant2, currentValue2, true)),
"AccessLockedTenant",
tenant_locked(),
Optional<bool*>(),
{},
Error::fromCode(error_code_internal_error)));
TraceEvent("TenantLockProgress").detail("Phase", "Unlock tenant2 with wrong lockID").log();
wait(testExpectedError(changeLockState(db, self->tenant2, TenantAPI::TenantLockState::UNLOCKED, lockID1),
"UnlockWithWrongID",
tenant_locked(),
Optional<bool*>(),
{},
Error::fromCode(error_code_internal_error)));
TraceEvent("TenantLockProgress").detail("Phase", "Unlock tenant2").log();
wait(changeLockState(db, self->tenant2, TenantAPI::TenantLockState::UNLOCKED, lockID2));
TraceEvent("TenantLockProgress").detail("Phase", "Second Tx against both unlocked tenants").log();
wait(store(currentValue1, probeTx(db, self->tenant1, currentValue1)));
wait(store(currentValue2, probeTx(db, self->tenant2, currentValue2)));
return Void();
}
Future<Void> start(const Database& cx) override {
if (clientId == 0) {
return _start(this, cx);
}
return Void();
}
Future<bool> check(const Database& cx) override { return true; }
private:
void getMetrics(std::vector<PerfMetric>& m) override {}
};
} // namespace
WorkloadFactory<TenantLock> TenantLockFactory;