178 lines
7.4 KiB
C++
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; |