712 lines
24 KiB
C++
712 lines
24 KiB
C++
/*
|
|
* BlobGranuleRangesWorkload.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/FDBOptions.g.h"
|
|
#include "fdbclient/FDBTypes.h"
|
|
#include "fdbclient/ManagementAPI.actor.h"
|
|
#include "fdbclient/NativeAPI.actor.h"
|
|
#include "fdbclient/SystemData.h"
|
|
#include "fdbclient/TenantManagement.actor.h"
|
|
#include "fdbserver/TesterInterface.actor.h"
|
|
#include "fdbserver/workloads/workloads.actor.h"
|
|
#include "fdbserver/workloads/BulkSetup.actor.h"
|
|
#include "flow/Arena.h"
|
|
#include "flow/IRandom.h"
|
|
#include "flow/Trace.h"
|
|
#include "flow/Util.h"
|
|
#include "flow/serialize.h"
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
#define BGRW_DEBUG true
|
|
|
|
// FIXME: need to do multiple changes per commit to properly exercise future change feed logic
|
|
// A workload specifically designed to stress the blob range management of the blob manager + blob worker, and test the
|
|
// blob database api functions
|
|
struct BlobGranuleRangesWorkload : TestWorkload {
|
|
static constexpr auto NAME = "BlobGranuleRanges";
|
|
// test settings
|
|
double testDuration;
|
|
int operationsPerSecond;
|
|
int targetRanges;
|
|
bool sequential;
|
|
int sequentialGap;
|
|
|
|
Future<Void> client;
|
|
Future<Void> unitClient;
|
|
bool stopUnitClient;
|
|
Optional<TenantName> tenantName;
|
|
|
|
int32_t nextKey;
|
|
|
|
std::vector<KeyRange> inactiveRanges;
|
|
std::vector<KeyRange> activeRanges;
|
|
|
|
BlobGranuleRangesWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
|
|
testDuration = getOption(options, "testDuration"_sr, 30.0);
|
|
operationsPerSecond = getOption(options, "opsPerSecond"_sr, deterministicRandom()->randomInt(1, 100));
|
|
operationsPerSecond /= clientCount;
|
|
if (operationsPerSecond <= 0) {
|
|
operationsPerSecond = 1;
|
|
}
|
|
|
|
int64_t rand = wcx.sharedRandomNumber;
|
|
targetRanges = deterministicRandom()->randomExp(1, 1 + rand % 10);
|
|
targetRanges *= (0.8 + (deterministicRandom()->random01() * 0.4));
|
|
targetRanges /= clientCount;
|
|
if (targetRanges <= 0) {
|
|
targetRanges = 1;
|
|
}
|
|
rand /= 10;
|
|
|
|
sequential = rand % 2;
|
|
rand /= 2;
|
|
|
|
sequentialGap = 1 + rand % 2;
|
|
rand /= 2;
|
|
|
|
nextKey = 10000000 * clientId;
|
|
|
|
stopUnitClient = false;
|
|
if (deterministicRandom()->coinflip()) {
|
|
tenantName = StringRef("bgrwTenant" + std::to_string(clientId));
|
|
}
|
|
|
|
TraceEvent("BlobGranuleRangesWorkloadInit").detail("TargetRanges", targetRanges);
|
|
}
|
|
|
|
Future<Void> setup(Database const& cx) override { return _setup(cx, this); }
|
|
|
|
std::string newKey() {
|
|
if (sequential) {
|
|
nextKey += sequentialGap;
|
|
return format("%08x", nextKey);
|
|
} else {
|
|
return deterministicRandom()->randomUniqueID().toString();
|
|
}
|
|
}
|
|
|
|
ACTOR Future<Void> registerNewRange(Database cx, BlobGranuleRangesWorkload* self, Optional<TenantName> tenantName) {
|
|
std::string nextRangeKey = "R_" + self->newKey();
|
|
state KeyRange range(KeyRangeRef(StringRef(nextRangeKey), strinc(StringRef(nextRangeKey))));
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Registering new range [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
// don't put in active ranges until AFTER set range command succeeds, to avoid checking a range that maybe
|
|
// wasn't initialized
|
|
bool success = wait(cx->blobbifyRange(range, tenantName.present() ? tenantName.get() : self->tenantName));
|
|
ASSERT(success);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Registered new range [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
self->activeRanges.push_back(range);
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Key> versionedForcePurge(Database cx, KeyRange range, Optional<TenantName> tenantName) {
|
|
Version rv = deterministicRandom()->coinflip() ? latestVersion : 1;
|
|
Key purgeKey = wait(cx->purgeBlobGranules(range, rv, tenantName, true));
|
|
|
|
return purgeKey;
|
|
}
|
|
|
|
ACTOR Future<Void> unregisterRandomRange(Database cx, BlobGranuleRangesWorkload* self) {
|
|
int randomRangeIdx = deterministicRandom()->randomInt(0, self->activeRanges.size());
|
|
state KeyRange range = self->activeRanges[randomRangeIdx];
|
|
// remove range from active BEFORE committing txn but add to remove AFTER, to avoid checking a range that could
|
|
// potentially be in either state
|
|
swapAndPop(&self->activeRanges, randomRangeIdx);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Unregistering new range [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
if (deterministicRandom()->coinflip()) {
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Force purging range before un-registering: [{0} - {1})\n",
|
|
range.begin.printable(),
|
|
range.end.printable());
|
|
}
|
|
Key purgeKey = wait(self->versionedForcePurge(cx, range, self->tenantName));
|
|
wait(cx->waitPurgeGranulesComplete(purgeKey));
|
|
}
|
|
bool success = wait(cx->unblobbifyRange(range, self->tenantName));
|
|
ASSERT(success);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Unregistered new range [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
self->inactiveRanges.push_back(range);
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<TenantMapEntry> setupTenant(Database cx, TenantName name) {
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Creating tenant: {0}\n", name.printable());
|
|
}
|
|
|
|
Optional<TenantMapEntry> entry = wait(TenantAPI::createTenant(cx.getReference(), name));
|
|
ASSERT(entry.present());
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Created tenant {0}: {1}\n", name.printable(), entry.get().prefix.printable());
|
|
}
|
|
|
|
return entry.get();
|
|
}
|
|
|
|
ACTOR Future<Void> _setup(Database cx, BlobGranuleRangesWorkload* self) {
|
|
// create initial target ranges
|
|
TraceEvent("BlobGranuleRangesSetup").detail("InitialRanges", self->targetRanges).log();
|
|
// set up blob granules
|
|
wait(success(ManagementAPI::changeConfig(cx.getReference(), "blob_granules_enabled=1", true)));
|
|
|
|
if (self->tenantName.present()) {
|
|
wait(success(ManagementAPI::changeConfig(cx.getReference(), "tenant_mode=optional_experimental", true)));
|
|
wait(success(self->setupTenant(cx, self->tenantName.get())));
|
|
|
|
try {
|
|
wait(self->registerNewRange(cx, self, "BogusTenant"_sr));
|
|
ASSERT(false);
|
|
} catch (Error& e) {
|
|
if (e.code() != error_code_tenant_not_found) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
state int i;
|
|
std::vector<Future<Void>> createInitialRanges;
|
|
for (i = 0; i < self->targetRanges; i++) {
|
|
wait(self->registerNewRange(cx, self, {}));
|
|
}
|
|
TraceEvent("BlobGranuleRangesSetupComplete");
|
|
return Void();
|
|
}
|
|
|
|
Future<Void> start(Database const& cx) override {
|
|
client = blobGranuleRangesClient(cx->clone(), this);
|
|
if (clientId == 0) {
|
|
unitClient = blobGranuleRangesUnitTests(cx->clone(), this);
|
|
} else {
|
|
unitClient = Future<Void>(Void());
|
|
}
|
|
return delay(testDuration);
|
|
}
|
|
|
|
Future<bool> check(Database const& cx) override {
|
|
client = Future<Void>();
|
|
stopUnitClient = true;
|
|
return _check(cx, this);
|
|
}
|
|
|
|
ACTOR Future<bool> isRangeActive(Database cx, KeyRange range, Optional<TenantName> tenantName) {
|
|
Optional<Version> rv;
|
|
if (deterministicRandom()->coinflip()) {
|
|
rv = latestVersion;
|
|
}
|
|
state Version v = wait(cx->verifyBlobRange(range, rv, tenantName));
|
|
return v != invalidVersion;
|
|
}
|
|
|
|
ACTOR Future<Void> checkRange(Database cx, BlobGranuleRangesWorkload* self, KeyRange range, bool isActive) {
|
|
// Check that a read completes for the range. If not loop around and try again
|
|
loop {
|
|
bool completed = wait(self->isRangeActive(cx, range, self->tenantName));
|
|
|
|
if (completed == isActive) {
|
|
break;
|
|
}
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("CHECK: {0} range [{1} - {2}) failed!\n",
|
|
isActive ? "Active" : "Inactive",
|
|
range.begin.printable(),
|
|
range.end.printable());
|
|
}
|
|
|
|
wait(delay(1.0));
|
|
}
|
|
|
|
Standalone<VectorRef<KeyRangeRef>> blobRanges =
|
|
wait(cx->listBlobbifiedRanges(range, 1000000, self->tenantName));
|
|
if (isActive) {
|
|
ASSERT(blobRanges.size() == 1);
|
|
ASSERT(blobRanges[0].begin <= range.begin);
|
|
ASSERT(blobRanges[0].end >= range.end);
|
|
} else {
|
|
ASSERT(blobRanges.empty());
|
|
}
|
|
|
|
state Transaction tr(cx, self->tenantName);
|
|
loop {
|
|
try {
|
|
Standalone<VectorRef<KeyRangeRef>> granules = wait(tr.getBlobGranuleRanges(range, 1000000));
|
|
if (isActive) {
|
|
ASSERT(granules.size() >= 1);
|
|
ASSERT(granules.front().begin <= range.begin);
|
|
ASSERT(granules.back().end >= range.end);
|
|
for (int i = 0; i < granules.size() - 1; i++) {
|
|
ASSERT(granules[i].end == granules[i + 1].begin);
|
|
}
|
|
} else {
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Granules for [{0} - {1}) not empty! ({2}):\n",
|
|
range.begin.printable(),
|
|
range.end.printable(),
|
|
granules.size());
|
|
for (auto& it : granules) {
|
|
fmt::print(" [{0} - {1})\n", it.begin.printable(), it.end.printable());
|
|
}
|
|
}
|
|
ASSERT(granules.empty());
|
|
}
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(tr.onError(e));
|
|
}
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<bool> _check(Database cx, BlobGranuleRangesWorkload* self) {
|
|
TraceEvent("BlobGranuleRangesCheck")
|
|
.detail("ActiveRanges", self->activeRanges.size())
|
|
.detail("InactiveRanges", self->inactiveRanges.size())
|
|
.log();
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Checking {0} active and {1} inactive ranges\n",
|
|
self->activeRanges.size(),
|
|
self->inactiveRanges.size());
|
|
}
|
|
state std::vector<Future<Void>> checks;
|
|
for (int i = 0; i < self->activeRanges.size(); i++) {
|
|
checks.push_back(self->checkRange(cx, self, self->activeRanges[i], true));
|
|
}
|
|
|
|
// FIXME: re-enable! if we don't force purge there are weird races that cause granules to technically still
|
|
// exist
|
|
/*for (int i = 0; i < self->inactiveRanges.size(); i++) {
|
|
checks.push_back(self->checkRange(cx, self, self->inactiveRanges[i], false));
|
|
}*/
|
|
wait(waitForAll(checks));
|
|
wait(self->unitClient);
|
|
TraceEvent("BlobGranuleRangesCheckComplete");
|
|
return true;
|
|
}
|
|
|
|
void getMetrics(std::vector<PerfMetric>& m) override {}
|
|
|
|
ACTOR Future<Void> blobGranuleRangesClient(Database cx, BlobGranuleRangesWorkload* self) {
|
|
state double last = now();
|
|
loop {
|
|
state Future<Void> waitNextOp = poisson(&last, 1.0 / self->operationsPerSecond);
|
|
|
|
if (self->activeRanges.empty() || deterministicRandom()->coinflip()) {
|
|
wait(self->registerNewRange(cx, self, {}));
|
|
} else {
|
|
wait(self->unregisterRandomRange(cx, self));
|
|
}
|
|
|
|
wait(waitNextOp);
|
|
}
|
|
}
|
|
|
|
ACTOR Future<Void> tearDownRangeAfterUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Tearing down [{0} - {1}) after unit!\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
// tear down range at end
|
|
Key purgeKey = wait(self->versionedForcePurge(cx, range, self->tenantName));
|
|
wait(cx->waitPurgeGranulesComplete(purgeKey));
|
|
bool success = wait(cx->unblobbifyRange(range, self->tenantName));
|
|
ASSERT(success);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("Range [{0} - {1}) torn down.\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> verifyRangeUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
state KeyRange activeRange(KeyRangeRef(range.begin.withSuffix("A"_sr), range.begin.withSuffix("B"_sr)));
|
|
state Key middleKey = range.begin.withSuffix("AF"_sr);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("VerifyRangeUnit: [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
bool setSuccess = wait(cx->blobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(setSuccess);
|
|
wait(self->checkRange(cx, self, activeRange, true));
|
|
|
|
bool success1 = wait(self->isRangeActive(cx, KeyRangeRef(activeRange.begin, middleKey), self->tenantName));
|
|
ASSERT(success1);
|
|
|
|
bool success2 = wait(self->isRangeActive(cx, KeyRangeRef(middleKey, activeRange.end), self->tenantName));
|
|
ASSERT(success2);
|
|
|
|
bool fail1 = wait(self->isRangeActive(cx, range, self->tenantName));
|
|
ASSERT(!fail1);
|
|
|
|
bool fail2 = wait(self->isRangeActive(cx, KeyRangeRef(range.begin, activeRange.begin), self->tenantName));
|
|
ASSERT(!fail2);
|
|
|
|
bool fail3 = wait(self->isRangeActive(cx, KeyRangeRef(activeRange.end, range.end), self->tenantName));
|
|
ASSERT(!fail3);
|
|
|
|
bool fail4 = wait(self->isRangeActive(cx, KeyRangeRef(range.begin, middleKey), self->tenantName));
|
|
ASSERT(!fail4);
|
|
|
|
bool fail5 = wait(self->isRangeActive(cx, KeyRangeRef(middleKey, range.end), self->tenantName));
|
|
ASSERT(!fail5);
|
|
|
|
bool fail6 = wait(self->isRangeActive(cx, KeyRangeRef(range.begin, activeRange.end), self->tenantName));
|
|
ASSERT(!fail6);
|
|
|
|
bool fail7 = wait(self->isRangeActive(cx, KeyRangeRef(activeRange.begin, range.end), self->tenantName));
|
|
ASSERT(!fail7);
|
|
|
|
wait(self->tearDownRangeAfterUnit(cx, self, activeRange));
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> verifyRangeGapUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
state std::vector<Key> boundaries;
|
|
boundaries.push_back(range.begin);
|
|
state int rangeCount = deterministicRandom()->randomExp(3, 6) + 1;
|
|
for (int i = 0; i < rangeCount - 1; i++) {
|
|
std::string suffix = format("%04x", i);
|
|
boundaries.push_back(range.begin.withSuffix(suffix));
|
|
}
|
|
boundaries.push_back(range.end);
|
|
|
|
ASSERT(boundaries.size() - 1 == rangeCount);
|
|
|
|
state int rangeToNotBlobbify = deterministicRandom()->randomInt(0, rangeCount);
|
|
state int i;
|
|
for (i = 0; i < rangeCount; i++) {
|
|
state KeyRange subRange(KeyRangeRef(boundaries[i], boundaries[i + 1]));
|
|
if (i != rangeToNotBlobbify) {
|
|
bool setSuccess = wait(cx->blobbifyRange(subRange, self->tenantName));
|
|
ASSERT(setSuccess);
|
|
wait(self->checkRange(cx, self, subRange, true));
|
|
} else {
|
|
wait(self->checkRange(cx, self, subRange, false));
|
|
}
|
|
}
|
|
|
|
bool success = wait(self->isRangeActive(cx, range, self->tenantName));
|
|
ASSERT(!success);
|
|
|
|
if (rangeToNotBlobbify != 0) {
|
|
wait(self->tearDownRangeAfterUnit(cx, self, KeyRangeRef(boundaries[0], boundaries[rangeToNotBlobbify])));
|
|
}
|
|
if (rangeToNotBlobbify != rangeCount - 1) {
|
|
wait(self->tearDownRangeAfterUnit(
|
|
cx, self, KeyRangeRef(boundaries[rangeToNotBlobbify + 1], boundaries.back())));
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> checkRangesMisaligned(Database cx,
|
|
BlobGranuleRangesWorkload* self,
|
|
KeyRange expectedRange,
|
|
KeyRange queryRange) {
|
|
Standalone<VectorRef<KeyRangeRef>> blobRanges =
|
|
wait(cx->listBlobbifiedRanges(queryRange, 1000000, self->tenantName));
|
|
ASSERT(blobRanges.size() == 1);
|
|
ASSERT(blobRanges[0] == expectedRange);
|
|
|
|
state Transaction tr(cx, self->tenantName);
|
|
loop {
|
|
try {
|
|
Standalone<VectorRef<KeyRangeRef>> granules = wait(tr.getBlobGranuleRanges(queryRange, 1000000));
|
|
ASSERT(granules.size() == 1);
|
|
ASSERT(granules[0] == expectedRange);
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(tr.onError(e));
|
|
}
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> rangesMisalignedUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
bool setSuccess = wait(cx->blobbifyRange(range, self->tenantName));
|
|
ASSERT(setSuccess);
|
|
state KeyRange subRange(KeyRangeRef(range.begin.withSuffix("A"_sr), range.begin.withSuffix("B"_sr)));
|
|
|
|
// validate range set up correctly
|
|
wait(self->checkRange(cx, self, range, true));
|
|
wait(self->checkRangesMisaligned(cx, self, range, range));
|
|
|
|
// getBlobGranules and getBlobRanges on sub ranges- should return actual granule/range instead of clipped
|
|
wait(self->checkRange(cx, self, subRange, true));
|
|
wait(self->checkRangesMisaligned(cx, self, range, subRange));
|
|
wait(self->checkRangesMisaligned(cx, self, range, KeyRangeRef(range.begin, subRange.end)));
|
|
wait(self->checkRangesMisaligned(cx, self, range, KeyRangeRef(subRange.begin, range.end)));
|
|
|
|
try {
|
|
wait(success(cx->purgeBlobGranules(subRange, 1, self->tenantName, false)));
|
|
ASSERT(false);
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_operation_cancelled) {
|
|
throw e;
|
|
}
|
|
ASSERT(e.code() == error_code_unsupported_operation);
|
|
}
|
|
|
|
try {
|
|
wait(success(cx->purgeBlobGranules(subRange, 1, self->tenantName, true)));
|
|
ASSERT(false);
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_operation_cancelled) {
|
|
throw e;
|
|
}
|
|
ASSERT(e.code() == error_code_unsupported_operation);
|
|
}
|
|
|
|
// ensure range still there after unaligned purges
|
|
wait(self->checkRange(cx, self, range, true));
|
|
wait(self->checkRangesMisaligned(cx, self, range, range));
|
|
|
|
wait(self->tearDownRangeAfterUnit(cx, self, range));
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> blobbifyIdempotentUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
state KeyRange activeRange(KeyRangeRef(range.begin.withSuffix("A"_sr), range.begin.withSuffix("B"_sr)));
|
|
state Key middleKey = range.begin.withSuffix("AF"_sr);
|
|
state Key middleKey2 = range.begin.withSuffix("AG"_sr);
|
|
|
|
if (BGRW_DEBUG) {
|
|
fmt::print("IdempotentUnit: [{0} - {1})\n", range.begin.printable(), range.end.printable());
|
|
}
|
|
|
|
// unblobbifying range that already doesn't exist should be no-op
|
|
if (deterministicRandom()->coinflip()) {
|
|
bool unblobbifyStartSuccess = wait(cx->blobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(unblobbifyStartSuccess);
|
|
}
|
|
|
|
bool success = wait(cx->blobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(success);
|
|
wait(self->checkRange(cx, self, activeRange, true));
|
|
|
|
// check that re-blobbifying same range is successful
|
|
bool retrySuccess = wait(cx->blobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(retrySuccess);
|
|
wait(self->checkRange(cx, self, activeRange, true));
|
|
|
|
// check that blobbifying range that overlaps but does not match existing blob range fails
|
|
bool fail1 = wait(cx->blobbifyRange(range, self->tenantName));
|
|
ASSERT(!fail1);
|
|
|
|
bool fail2 = wait(cx->blobbifyRange(KeyRangeRef(range.begin, activeRange.end), self->tenantName));
|
|
ASSERT(!fail2);
|
|
|
|
bool fail3 = wait(cx->blobbifyRange(KeyRangeRef(activeRange.begin, range.end), self->tenantName));
|
|
ASSERT(!fail3);
|
|
|
|
bool fail4 = wait(cx->blobbifyRange(KeyRangeRef(range.begin, middleKey), self->tenantName));
|
|
ASSERT(!fail4);
|
|
|
|
bool fail5 = wait(cx->blobbifyRange(KeyRangeRef(middleKey, range.end), self->tenantName));
|
|
ASSERT(!fail5);
|
|
|
|
bool fail6 = wait(cx->blobbifyRange(KeyRangeRef(activeRange.begin, middleKey), self->tenantName));
|
|
ASSERT(!fail6);
|
|
|
|
bool fail7 = wait(cx->blobbifyRange(KeyRangeRef(middleKey, activeRange.end), self->tenantName));
|
|
ASSERT(!fail7);
|
|
|
|
bool fail8 = wait(cx->blobbifyRange(KeyRangeRef(middleKey, middleKey2), self->tenantName));
|
|
ASSERT(!fail8);
|
|
|
|
{
|
|
Standalone<VectorRef<KeyRangeRef>> blobRanges =
|
|
wait(cx->listBlobbifiedRanges(range, 1000000, self->tenantName));
|
|
ASSERT(blobRanges.size() == 1);
|
|
ASSERT(blobRanges[0] == activeRange);
|
|
|
|
state Transaction tr(cx, self->tenantName);
|
|
loop {
|
|
try {
|
|
Standalone<VectorRef<KeyRangeRef>> granules = wait(tr.getBlobGranuleRanges(range, 1000000));
|
|
ASSERT(granules.size() == 1);
|
|
ASSERT(granules[0] == activeRange);
|
|
break;
|
|
} catch (Error& e) {
|
|
wait(tr.onError(e));
|
|
}
|
|
}
|
|
|
|
state Version purgeVersion = deterministicRandom()->coinflip() ? latestVersion : 1;
|
|
state KeyRangeRef purgeRange = deterministicRandom()->coinflip() ? activeRange : range;
|
|
Key purgeKey = wait(cx->purgeBlobGranules(purgeRange, purgeVersion, self->tenantName, true));
|
|
wait(cx->waitPurgeGranulesComplete(purgeKey));
|
|
|
|
if (deterministicRandom()->coinflip()) {
|
|
// force purge again and ensure it is idempotent
|
|
Key purgeKeyAgain = wait(cx->purgeBlobGranules(purgeRange, purgeVersion, self->tenantName, true));
|
|
wait(cx->waitPurgeGranulesComplete(purgeKeyAgain));
|
|
}
|
|
}
|
|
|
|
// Check that the blob range is still listed
|
|
{
|
|
Standalone<VectorRef<KeyRangeRef>> blobRanges =
|
|
wait(cx->listBlobbifiedRanges(range, 1000000, self->tenantName));
|
|
ASSERT(blobRanges.size() == 1);
|
|
ASSERT(blobRanges[0] == activeRange);
|
|
|
|
bool unblobbifyFail1 = wait(cx->unblobbifyRange(range, self->tenantName));
|
|
ASSERT(!unblobbifyFail1);
|
|
|
|
bool unblobbifyFail2 =
|
|
wait(cx->unblobbifyRange(KeyRangeRef(range.begin, activeRange.end), self->tenantName));
|
|
ASSERT(!unblobbifyFail2);
|
|
|
|
bool unblobbifyFail3 =
|
|
wait(cx->unblobbifyRange(KeyRangeRef(activeRange.begin, range.end), self->tenantName));
|
|
ASSERT(!unblobbifyFail3);
|
|
|
|
bool unblobbifyFail4 =
|
|
wait(cx->unblobbifyRange(KeyRangeRef(activeRange.begin, middleKey), self->tenantName));
|
|
ASSERT(!unblobbifyFail4);
|
|
|
|
bool unblobbifyFail5 = wait(cx->unblobbifyRange(KeyRangeRef(middleKey, activeRange.end), self->tenantName));
|
|
ASSERT(!unblobbifyFail5);
|
|
|
|
bool unblobbifyFail6 =
|
|
wait(cx->unblobbifyRange(KeyRangeRef(activeRange.begin, middleKey), self->tenantName));
|
|
ASSERT(!unblobbifyFail6);
|
|
|
|
bool unblobbifyFail7 = wait(cx->unblobbifyRange(KeyRangeRef(middleKey, activeRange.end), self->tenantName));
|
|
ASSERT(!unblobbifyFail7);
|
|
|
|
bool unblobbifyFail8 = wait(cx->unblobbifyRange(KeyRangeRef(middleKey, middleKey2), self->tenantName));
|
|
ASSERT(!unblobbifyFail8);
|
|
|
|
bool unblobbifySuccess = wait(cx->unblobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(unblobbifySuccess);
|
|
|
|
bool unblobbifySuccessAgain = wait(cx->unblobbifyRange(activeRange, self->tenantName));
|
|
ASSERT(unblobbifySuccessAgain);
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR Future<Void> reBlobbifyUnit(Database cx, BlobGranuleRangesWorkload* self, KeyRange range) {
|
|
bool setSuccess = wait(cx->blobbifyRange(range, self->tenantName));
|
|
ASSERT(setSuccess);
|
|
wait(self->checkRange(cx, self, range, true));
|
|
|
|
// force purge range
|
|
Key purgeKey = wait(self->versionedForcePurge(cx, range, self->tenantName));
|
|
wait(cx->waitPurgeGranulesComplete(purgeKey));
|
|
wait(self->checkRange(cx, self, range, false));
|
|
|
|
bool unsetSuccess = wait(cx->unblobbifyRange(range, self->tenantName));
|
|
ASSERT(unsetSuccess);
|
|
wait(self->checkRange(cx, self, range, false));
|
|
|
|
bool reSetSuccess = wait(cx->blobbifyRange(range, self->tenantName));
|
|
ASSERT(reSetSuccess);
|
|
wait(self->checkRange(cx, self, range, true));
|
|
|
|
wait(self->tearDownRangeAfterUnit(cx, self, range));
|
|
|
|
return Void();
|
|
}
|
|
|
|
enum UnitTestTypes {
|
|
VERIFY_RANGE_UNIT,
|
|
VERIFY_RANGE_GAP_UNIT,
|
|
RANGES_MISALIGNED,
|
|
BLOBBIFY_IDEMPOTENT,
|
|
RE_BLOBBIFY,
|
|
OP_COUNT = 5 /* keep this last */
|
|
};
|
|
|
|
ACTOR Future<Void> blobGranuleRangesUnitTests(Database cx, BlobGranuleRangesWorkload* self) {
|
|
loop {
|
|
if (self->stopUnitClient) {
|
|
return Void();
|
|
}
|
|
std::set<UnitTestTypes> excludedTypes;
|
|
excludedTypes.insert(OP_COUNT);
|
|
|
|
// FIXME: fix bugs and enable these tests!
|
|
excludedTypes.insert(RE_BLOBBIFY); // TODO - fix is non-trivial, is desired behavior eventually
|
|
|
|
std::string nextRangeKey = "U_" + self->newKey();
|
|
state KeyRange range(KeyRangeRef(StringRef(nextRangeKey), strinc(StringRef(nextRangeKey))));
|
|
// prevent infinite loop
|
|
int loopTries = 1000;
|
|
int op = OP_COUNT;
|
|
loop {
|
|
op = deterministicRandom()->randomInt(0, OP_COUNT);
|
|
if (!excludedTypes.count((UnitTestTypes)op)) {
|
|
break;
|
|
}
|
|
loopTries--;
|
|
ASSERT(loopTries >= 0);
|
|
}
|
|
if (BGRW_DEBUG) {
|
|
fmt::print(
|
|
"Selected range [{0} - {1}) for unit {2}.\n", range.begin.printable(), range.end.printable(), op);
|
|
}
|
|
|
|
if (op == VERIFY_RANGE_UNIT) {
|
|
wait(self->verifyRangeUnit(cx, self, range));
|
|
} else if (op == VERIFY_RANGE_GAP_UNIT) {
|
|
wait(self->verifyRangeGapUnit(cx, self, range));
|
|
} else if (op == RANGES_MISALIGNED) {
|
|
wait(self->rangesMisalignedUnit(cx, self, range));
|
|
} else if (op == BLOBBIFY_IDEMPOTENT) {
|
|
wait(self->blobbifyIdempotentUnit(cx, self, range));
|
|
} else if (op == RE_BLOBBIFY) {
|
|
wait(self->reBlobbifyUnit(cx, self, range));
|
|
} else {
|
|
ASSERT(false);
|
|
}
|
|
|
|
wait(delay(1.0));
|
|
}
|
|
}
|
|
};
|
|
|
|
WorkloadFactory<BlobGranuleRangesWorkload> BlobGranuleRangesWorkloadFactory;
|