SERVER-123734 Add new metrics to track the new user write block (#52232)
GitOrigin-RevId: 55a9450a650785083cbc49c7084fbf575aac3515
This commit is contained in:
parent
ceec5e80e8
commit
7e39b9cb03
@ -70,6 +70,98 @@ describe("Test blockReplicaSetWrites command on shard replica sets in a sharded
|
||||
this.st.stop();
|
||||
});
|
||||
|
||||
it("Test blockReplicaSetWrites command counter increments on enable", function () {
|
||||
// Enable per-shard write blocking on shard0.
|
||||
assert.commandWorked(
|
||||
this.shard0PrimaryAdminDB.runCommand({
|
||||
blockReplicaSetWrites: 1,
|
||||
enabled: true,
|
||||
allowDeletions: false,
|
||||
reason: "InsufficientDiskSpace",
|
||||
}),
|
||||
);
|
||||
|
||||
// Check replica set write block, reason, and command counter metrics when blockReplicaSetWrites is enabled.
|
||||
let replStatus = assert.commandWorked(this.shard0PrimaryAdminDB.serverStatus()).repl;
|
||||
assert.eq(replStatus.replicaSetWriteBlock, 2, "replicaSetWriteBlock metric should be 2 (Enabled)");
|
||||
assert.eq(
|
||||
replStatus.replicaSetWriteBlockReason,
|
||||
0,
|
||||
"replicaSetWriteBlockReason metric should be 0 (InsufficientDiskSpace)",
|
||||
);
|
||||
assert.eq(
|
||||
replStatus.replicaSetWritesBlockCounters.InsufficientDiskSpace,
|
||||
1,
|
||||
"repl.replicaSetWritesBlockCounters counter for InsufficientDiskSpace should be 1",
|
||||
);
|
||||
|
||||
//Disable per-shard write blocking on shard0.
|
||||
assert.commandWorked(
|
||||
this.shard0PrimaryAdminDB.runCommand({
|
||||
blockReplicaSetWrites: 1,
|
||||
enabled: false,
|
||||
allowDeletions: false,
|
||||
reason: "InsufficientDiskSpace",
|
||||
}),
|
||||
);
|
||||
|
||||
// Check replica set write block, reason, and command counter when blockReplicaSetWrites is disabled.
|
||||
replStatus = assert.commandWorked(this.shard0PrimaryAdminDB.serverStatus()).repl;
|
||||
assert.eq(replStatus.replicaSetWriteBlock, 1, "replicaSetWriteBlock metric should be 1 (Disabled)");
|
||||
assert(
|
||||
!replStatus.hasOwnProperty("replicaSetWriteBlockReason"),
|
||||
"replicaSetWriteBlockReason should be absent when replica set write block is disabled",
|
||||
);
|
||||
assert.eq(
|
||||
replStatus.replicaSetWritesBlockCounters.InsufficientDiskSpace,
|
||||
1,
|
||||
"repl.replicaSetWritesBlockCounters counter for InsufficientDiskSpace should not change on disable",
|
||||
);
|
||||
});
|
||||
|
||||
it("Test blocked inserts, updates, and deletes counters increment after blockReplicaSetWrites is enabled", function () {
|
||||
assert.commandWorked(this.testShardedColl.insert({x: 0}));
|
||||
|
||||
// Enable per-shard write blocking on shard0.
|
||||
assert.commandWorked(
|
||||
this.shard0PrimaryAdminDB.runCommand({
|
||||
blockReplicaSetWrites: 1,
|
||||
enabled: true,
|
||||
allowDeletions: false,
|
||||
reason: "InsufficientDiskSpace",
|
||||
}),
|
||||
);
|
||||
|
||||
// Check blocked inserts, updates, and deletes are blocked and counters are incremented.
|
||||
let replStatus = assert.commandWorked(this.shard0PrimaryAdminDB.serverStatus()).repl;
|
||||
assert(replStatus.hasOwnProperty("replicaSetWritesBlockRejected"));
|
||||
const insertsAfterEnable = replStatus.replicaSetWritesBlockRejected.inserts;
|
||||
const updatesAfterEnable = replStatus.replicaSetWritesBlockRejected.updates;
|
||||
const deletesAfterEnable = replStatus.replicaSetWritesBlockRejected.deletes;
|
||||
|
||||
assert.commandFailedWithCode(this.testShardedColl.insert({x: 1}), ErrorCodes.UserWritesBlocked);
|
||||
assert.commandFailedWithCode(this.testShardedColl.insert({x: 2}), ErrorCodes.UserWritesBlocked);
|
||||
assert.commandFailedWithCode(this.testShardedColl.update({x: 0}, {$set: {y: 1}}), ErrorCodes.UserWritesBlocked);
|
||||
assert.commandFailedWithCode(this.testShardedColl.remove({x: 0}), ErrorCodes.UserWritesBlocked);
|
||||
|
||||
replStatus = assert.commandWorked(this.shard0PrimaryAdminDB.serverStatus()).repl;
|
||||
assert.eq(
|
||||
replStatus.replicaSetWritesBlockRejected.inserts,
|
||||
insertsAfterEnable + 2,
|
||||
"Each blocked insert should increment repl.replicaSetWritesBlockRejected.inserts",
|
||||
);
|
||||
assert.eq(
|
||||
replStatus.replicaSetWritesBlockRejected.updates,
|
||||
updatesAfterEnable + 1,
|
||||
"Each blocked update should increment repl.replicaSetWritesBlockRejected.updates",
|
||||
);
|
||||
assert.eq(
|
||||
replStatus.replicaSetWritesBlockRejected.deletes,
|
||||
deletesAfterEnable + 1,
|
||||
"Each blocked delete should increment repl.replicaSetWritesBlockRejected.deletes",
|
||||
);
|
||||
});
|
||||
|
||||
it("Test user writes respect both setUserWriteBlockMode and blockReplicaSetWrites on a shard", function () {
|
||||
// Insert a document on shard0.
|
||||
assert.commandWorked(this.testShardedColl.insert({x: 1}));
|
||||
|
||||
@ -1100,6 +1100,7 @@ mongo_cc_library(
|
||||
"//src/mongo/db:dbhelpers",
|
||||
"//src/mongo/db:query_exec",
|
||||
"//src/mongo/db:read_write_concern_defaults",
|
||||
"//src/mongo/db:shard_role_api",
|
||||
"//src/mongo/db/auth",
|
||||
"//src/mongo/db/auth:saslauth",
|
||||
"//src/mongo/db/commands:test_commands_enabled",
|
||||
|
||||
@ -76,6 +76,7 @@
|
||||
#include "mongo/db/tenant_id.h"
|
||||
#include "mongo/db/topology/cluster_role.h"
|
||||
#include "mongo/db/topology/user_write_block/global_user_write_block_state.h"
|
||||
#include "mongo/db/topology/user_write_block/replica_set_write_block_state.h"
|
||||
#include "mongo/db/wire_version.h"
|
||||
#include "mongo/db/write_concern_options.h"
|
||||
#include "mongo/idl/idl_parser.h"
|
||||
@ -221,6 +222,7 @@ TopologyVersion appendReplicationInfo(OperationContext* opCtx,
|
||||
class ReplicationInfoServerStatus : public ServerStatusSection {
|
||||
public:
|
||||
enum class UserWriteBlockState { kUnknown = 0, kDisabled = 1, kEnabled = 2 };
|
||||
enum class ReplicaSetLevelWriteBlockState { kUnknown = 0, kDisabled = 1, kEnabled = 2 };
|
||||
|
||||
using ServerStatusSection::ServerStatusSection;
|
||||
|
||||
@ -253,6 +255,8 @@ public:
|
||||
{
|
||||
auto state = UserWriteBlockState::kUnknown;
|
||||
auto reason = UserWritesBlockReasonEnum::kUnspecified;
|
||||
auto replicaSetState = ReplicaSetLevelWriteBlockState::kUnknown;
|
||||
boost::optional<int> replicaSetReason;
|
||||
// Try to lock. If we fail (i.e. lock is already held in write mode), don't read the
|
||||
// GlobalUserWriteBlockState and set the userWriteBlockMode field to kUnknown.
|
||||
Lock::GlobalLock lk(
|
||||
@ -262,16 +266,32 @@ public:
|
||||
return options;
|
||||
}());
|
||||
if (!lk.isLocked()) {
|
||||
LOGV2_DEBUG(6345700, 2, "Failed to retrieve user write block state");
|
||||
LOGV2_DEBUG(6345700, 2, "Failed to retrieve write block states");
|
||||
} else {
|
||||
state = GlobalUserWriteBlockState::get(opCtx)->isUserWriteBlockingEnabled(opCtx)
|
||||
? UserWriteBlockState::kEnabled
|
||||
: UserWriteBlockState::kDisabled;
|
||||
reason = GlobalUserWriteBlockState::get(opCtx)->getUserWriteBlockingReason(opCtx);
|
||||
replicaSetState =
|
||||
ReplicaSetWriteBlockState::get(opCtx)->isReplicaSetWriteBlockingEnabled()
|
||||
? ReplicaSetLevelWriteBlockState::kEnabled
|
||||
: ReplicaSetLevelWriteBlockState::kDisabled;
|
||||
if (replicaSetState == ReplicaSetLevelWriteBlockState::kEnabled) {
|
||||
replicaSetReason =
|
||||
ReplicaSetWriteBlockState::get(opCtx)->getReplicaSetWriteBlockingReason(
|
||||
opCtx);
|
||||
}
|
||||
}
|
||||
result.append("userWriteBlockMode", state);
|
||||
result.append("userWriteBlockReason", reason);
|
||||
GlobalUserWriteBlockState::get(opCtx)->appendUserWriteBlockModeCounters(result);
|
||||
result.append("replicaSetWriteBlock", replicaSetState);
|
||||
if (replicaSetReason) {
|
||||
result.append("replicaSetWriteBlockReason", *replicaSetReason);
|
||||
}
|
||||
ReplicaSetWriteBlockState::get(opCtx)->appendReplicaSetWritesBlockCounters(result);
|
||||
ReplicaSetWriteBlockState::get(opCtx)->appendReplicaSetWriteBlockRejectionMetrics(
|
||||
result);
|
||||
}
|
||||
|
||||
return result.obj();
|
||||
|
||||
@ -50,15 +50,17 @@ bool ReplicaSetWriteBlockOpObserver::_isReplSetAndCanAcceptWritesForNamespace(
|
||||
return replCoord->getSettings().isReplSet() && replCoord->canAcceptWritesFor(opCtx, nss);
|
||||
}
|
||||
|
||||
void ReplicaSetWriteBlockOpObserver::_checkReplicaSetWriteAllowed(OperationContext* opCtx,
|
||||
const NamespaceString& nss,
|
||||
bool fromMigrate) {
|
||||
void ReplicaSetWriteBlockOpObserver::_checkReplicaSetWriteAllowed(
|
||||
OperationContext* opCtx,
|
||||
const NamespaceString& nss,
|
||||
bool fromMigrate,
|
||||
ReplicaSetWriteBlockRejectedWriteOp opType) {
|
||||
if (!_isReplSetAndCanAcceptWritesForNamespace(opCtx, nss)) {
|
||||
return;
|
||||
}
|
||||
auto* replicaSetWriteBlockState = ReplicaSetWriteBlockState::get(opCtx);
|
||||
if (!fromMigrate) {
|
||||
replicaSetWriteBlockState->checkReplicaSetWritesAllowed(opCtx, nss);
|
||||
replicaSetWriteBlockState->checkReplicaSetWritesAllowed(opCtx, nss, opType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +85,8 @@ void ReplicaSetWriteBlockOpObserver::onInserts(OperationContext* opCtx,
|
||||
OpStateAccumulator* opAccumulator) {
|
||||
const auto nss = coll->ns();
|
||||
|
||||
_checkReplicaSetWriteAllowed(opCtx, nss, defaultFromMigrate);
|
||||
_checkReplicaSetWriteAllowed(
|
||||
opCtx, nss, defaultFromMigrate, ReplicaSetWriteBlockRejectedWriteOp::kInsert);
|
||||
|
||||
// TODO (SERVER-91506): Determine if we should change this to check isDataConsistent.
|
||||
if (nss == NamespaceString::kReplicaSetWritesCriticalSectionsNamespace &&
|
||||
@ -118,7 +121,8 @@ void ReplicaSetWriteBlockOpObserver::onUpdate(OperationContext* opCtx,
|
||||
const auto nss = args.coll->ns();
|
||||
|
||||
const bool fromMigrate = args.updateArgs->source == OperationSource::kFromMigrate;
|
||||
_checkReplicaSetWriteAllowed(opCtx, nss, fromMigrate);
|
||||
_checkReplicaSetWriteAllowed(
|
||||
opCtx, nss, fromMigrate, ReplicaSetWriteBlockRejectedWriteOp::kUpdate);
|
||||
|
||||
// TODO (SERVER-91506): Determine if we should change this to check isDataConsistent.
|
||||
if (nss == NamespaceString::kReplicaSetWritesCriticalSectionsNamespace &&
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "mongo/db/namespace_string.h"
|
||||
#include "mongo/db/op_observer/op_observer_noop.h"
|
||||
#include "mongo/db/shard_role/shard_catalog/collection.h"
|
||||
#include "mongo/db/topology/user_write_block/replica_set_write_block_state.h"
|
||||
#include "mongo/util/modules.h"
|
||||
|
||||
namespace mongo {
|
||||
@ -76,7 +77,8 @@ private:
|
||||
|
||||
void _checkReplicaSetWriteAllowed(OperationContext* opCtx,
|
||||
const NamespaceString& nss,
|
||||
bool fromMigrate);
|
||||
bool fromMigrate,
|
||||
ReplicaSetWriteBlockRejectedWriteOp opType);
|
||||
|
||||
void _checkReplicaSetDeleteAllowed(OperationContext* opCtx, const NamespaceString& nss);
|
||||
};
|
||||
|
||||
@ -30,10 +30,12 @@
|
||||
#include "mongo/db/topology/user_write_block/replica_set_write_block_state.h"
|
||||
|
||||
#include "mongo/base/error_codes.h"
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/db/server_options.h"
|
||||
#include "mongo/db/shard_role/transaction_resources.h"
|
||||
#include "mongo/db/topology/cluster_role.h"
|
||||
#include "mongo/db/topology/user_write_block/replica_set_write_block_bypass.h"
|
||||
#include "mongo/idl/idl_parser.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
#include "mongo/util/decorable.h"
|
||||
|
||||
@ -58,6 +60,7 @@ ReplicaSetWriteBlockState* ReplicaSetWriteBlockState::get(OperationContext* opCt
|
||||
void ReplicaSetWriteBlockState::enableReplicaSetWriteBlocking(
|
||||
ReplicaSetWritesBlockReasonEnum reason) {
|
||||
_writeBlockInfo.store(WriteBlockInfo{.blocked = true, .reason = reason});
|
||||
_replicaSetWritesBlockCounters[static_cast<size_t>(reason)].fetchAndAdd(1);
|
||||
LOGV2(12097000, "Blocking replica set writes", "reason"_attr = idl::serialize(reason));
|
||||
}
|
||||
|
||||
@ -73,15 +76,27 @@ void ReplicaSetWriteBlockState::disableReplicaSetWriteBlocking() {
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicaSetWriteBlockState::checkReplicaSetWritesAllowed(OperationContext* opCtx,
|
||||
const NamespaceString& nss) const {
|
||||
void ReplicaSetWriteBlockState::checkReplicaSetWritesAllowed(
|
||||
OperationContext* opCtx,
|
||||
const NamespaceString& nss,
|
||||
ReplicaSetWriteBlockRejectedWriteOp opType) const {
|
||||
const auto info = _writeBlockInfo.load();
|
||||
if (!info.blocked || ReplicaSetWriteBlockBypass::get(opCtx).isEnabled() ||
|
||||
nss.isOnInternalDb() || nss.isTemporaryReshardingCollection() || nss.isSystemDotProfile()) {
|
||||
return;
|
||||
const bool writesAllowed = !info.blocked ||
|
||||
ReplicaSetWriteBlockBypass::get(opCtx).isEnabled() || nss.isOnInternalDb() ||
|
||||
nss.isTemporaryReshardingCollection() || nss.isSystemDotProfile();
|
||||
if (!writesAllowed) {
|
||||
switch (opType) {
|
||||
case ReplicaSetWriteBlockRejectedWriteOp::kInsert:
|
||||
_replicaSetWriteBlockRejectedInserts.fetchAndAdd(1);
|
||||
break;
|
||||
case ReplicaSetWriteBlockRejectedWriteOp::kUpdate:
|
||||
_replicaSetWriteBlockRejectedUpdates.fetchAndAdd(1);
|
||||
break;
|
||||
}
|
||||
uasserted(
|
||||
ErrorCodes::UserWritesBlocked,
|
||||
fmt::format("Replica set write blocked, reason: {}", idl::serialize(info.reason)));
|
||||
}
|
||||
uasserted(ErrorCodes::UserWritesBlocked,
|
||||
fmt::format("Replica set write blocked, reason: {}", idl::serialize(info.reason)));
|
||||
}
|
||||
|
||||
bool ReplicaSetWriteBlockState::isReplicaSetWriteBlockingEnabled() const {
|
||||
@ -98,14 +113,38 @@ void ReplicaSetWriteBlockState::disableReplicaSetDeletionsBlocking() {
|
||||
|
||||
void ReplicaSetWriteBlockState::checkReplicaSetDeletionsAllowed(OperationContext* opCtx,
|
||||
const NamespaceString& nss) const {
|
||||
uassert(ErrorCodes::UserWritesBlocked,
|
||||
"User writes blocked",
|
||||
!_deletionsBlocked.load() || ReplicaSetWriteBlockBypass::get(opCtx).isEnabled() ||
|
||||
nss.isOnInternalDb() || nss.isSystemDotProfile());
|
||||
const bool deletesAllowed = !_deletionsBlocked.load() ||
|
||||
ReplicaSetWriteBlockBypass::get(opCtx).isEnabled() || nss.isOnInternalDb() ||
|
||||
nss.isSystemDotProfile();
|
||||
if (!deletesAllowed) {
|
||||
_replicaSetWriteBlockRejectedDeletes.fetchAndAdd(1);
|
||||
uasserted(ErrorCodes::UserWritesBlocked, "User writes blocked");
|
||||
}
|
||||
}
|
||||
|
||||
bool ReplicaSetWriteBlockState::isReplicaSetDeletionsBlockingEnabled_forTest() const {
|
||||
return _deletionsBlocked.load();
|
||||
}
|
||||
|
||||
void ReplicaSetWriteBlockState::appendReplicaSetWritesBlockCounters(BSONObjBuilder& bob) const {
|
||||
BSONObjBuilder result(bob.subobjStart("replicaSetWritesBlockCounters"));
|
||||
for (size_t reasonIdx = 0; reasonIdx < idlEnumCount<ReplicaSetWritesBlockReasonEnum>;
|
||||
++reasonIdx) {
|
||||
result.appendNumber(
|
||||
idl::serialize(static_cast<ReplicaSetWritesBlockReasonEnum>(reasonIdx)),
|
||||
static_cast<long long>(_replicaSetWritesBlockCounters[reasonIdx].load()));
|
||||
}
|
||||
}
|
||||
|
||||
void ReplicaSetWriteBlockState::appendReplicaSetWriteBlockRejectionMetrics(
|
||||
BSONObjBuilder& bob) const {
|
||||
BSONObjBuilder sub(bob.subobjStart("replicaSetWritesBlockRejected"));
|
||||
sub.appendNumber("inserts",
|
||||
static_cast<long long>(_replicaSetWriteBlockRejectedInserts.load()));
|
||||
sub.appendNumber("updates",
|
||||
static_cast<long long>(_replicaSetWriteBlockRejectedUpdates.load()));
|
||||
sub.appendNumber("deletes",
|
||||
static_cast<long long>(_replicaSetWriteBlockRejectedDeletes.load()));
|
||||
}
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -39,11 +39,17 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace mongo {
|
||||
|
||||
// Identifies the kind of write rejected by checkReplicaSetWritesAllowed so the matching counter
|
||||
// can be incremented. Deletes are not included since they are validated separately by
|
||||
// checkReplicaSetDeletionsAllowed and tracked through their own counter.
|
||||
enum class ReplicaSetWriteBlockRejectedWriteOp { kInsert, kUpdate };
|
||||
|
||||
class MONGO_MOD_NEEDS_REPLACEMENT ReplicaSetWriteBlockState {
|
||||
public:
|
||||
ReplicaSetWriteBlockState() = default;
|
||||
@ -60,20 +66,21 @@ public:
|
||||
/**
|
||||
* Gets the reason why the replica set writes are blocked.
|
||||
*/
|
||||
boost::optional<ReplicaSetWritesBlockReasonEnum> getReplicaSetWriteBlockingReason(
|
||||
OperationContext* opCtx) const {
|
||||
boost::optional<int> getReplicaSetWriteBlockingReason(OperationContext* opCtx) const {
|
||||
const auto info = _writeBlockInfo.load();
|
||||
if (!info.blocked) {
|
||||
return boost::none;
|
||||
}
|
||||
return info.reason;
|
||||
return static_cast<int>(info.reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that replica set writes are allowed on the specified namespace. Throws
|
||||
* UserWritesBlocked if user writes are disallowed.
|
||||
*/
|
||||
void checkReplicaSetWritesAllowed(OperationContext* opCtx, const NamespaceString& nss) const;
|
||||
void checkReplicaSetWritesAllowed(OperationContext* opCtx,
|
||||
const NamespaceString& nss,
|
||||
ReplicaSetWriteBlockRejectedWriteOp opKind) const;
|
||||
|
||||
/**
|
||||
* Returns whether replica set write blocking is enabled, disregarding a specific namespace
|
||||
@ -99,6 +106,16 @@ public:
|
||||
*/
|
||||
MONGO_MOD_FILE_PRIVATE bool isReplicaSetDeletionsBlockingEnabled_forTest() const;
|
||||
|
||||
/**
|
||||
* Reports replica set write blocking counters, specifying one counter per blocking reason.
|
||||
*/
|
||||
void appendReplicaSetWritesBlockCounters(BSONObjBuilder& bob) const;
|
||||
|
||||
/**
|
||||
* Reports how many operations were rejected by replica set write/deletion blocking.
|
||||
*/
|
||||
void appendReplicaSetWriteBlockRejectionMetrics(BSONObjBuilder& bob) const;
|
||||
|
||||
private:
|
||||
struct WriteBlockInfo {
|
||||
bool blocked{false};
|
||||
@ -108,6 +125,11 @@ private:
|
||||
// Atomic<T> enforces lock-free access at compile time.
|
||||
Atomic<WriteBlockInfo> _writeBlockInfo{WriteBlockInfo{}};
|
||||
Atomic<bool> _deletionsBlocked{false};
|
||||
std::array<AtomicWord<std::uint64_t>, idlEnumCount<ReplicaSetWritesBlockReasonEnum>>
|
||||
_replicaSetWritesBlockCounters{};
|
||||
mutable AtomicWord<std::uint64_t> _replicaSetWriteBlockRejectedInserts{0};
|
||||
mutable AtomicWord<std::uint64_t> _replicaSetWriteBlockRejectedUpdates{0};
|
||||
mutable AtomicWord<std::uint64_t> _replicaSetWriteBlockRejectedDeletes{0};
|
||||
};
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
#include "mongo/db/topology/user_write_block/replica_set_write_block_state.h"
|
||||
|
||||
#include "mongo/base/error_codes.h"
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/db/auth/authorization_session.h"
|
||||
#include "mongo/db/client.h"
|
||||
#include "mongo/db/service_context_d_test_fixture.h"
|
||||
@ -38,11 +39,29 @@
|
||||
#include "mongo/unittest/unittest.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace mongo {
|
||||
namespace {
|
||||
|
||||
class ReplicaSetWriteBlockStateTest : public ServiceContextMongoDTest {};
|
||||
|
||||
struct ReplicaSetWritesBlockRejectedSnapshot {
|
||||
long long inserts;
|
||||
long long updates;
|
||||
long long deletes;
|
||||
};
|
||||
|
||||
auto readReplicaSetWritesBlockRejected(const ReplicaSetWriteBlockState* state) {
|
||||
BSONObjBuilder bob;
|
||||
state->appendReplicaSetWriteBlockRejectionMetrics(bob);
|
||||
BSONObj metrics = bob.obj();
|
||||
const auto sub = metrics.getObjectField("replicaSetWritesBlockRejected");
|
||||
return ReplicaSetWritesBlockRejectedSnapshot{sub["inserts"].safeNumberLong(),
|
||||
sub["updates"].safeNumberLong(),
|
||||
sub["deletes"].safeNumberLong()};
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, GetFromServiceContextMatchesGetFromOperationContext) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
@ -60,7 +79,8 @@ TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingDisabledAllowsUserNamespace)
|
||||
ASSERT_FALSE(state->isReplicaSetWriteBlockingEnabled());
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(opCtx.get(), nss));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingEnabledBlocksUserNamespace) {
|
||||
@ -74,10 +94,11 @@ TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingEnabledBlocksUserNamespace) {
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
ASSERT_TRUE(state->isReplicaSetWriteBlockingEnabled());
|
||||
ASSERT_EQ(state->getReplicaSetWriteBlockingReason(opCtx.get()),
|
||||
ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
static_cast<int>(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace));
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
ASSERT_THROWS_CODE(state->checkReplicaSetWritesAllowed(opCtx.get(), nss),
|
||||
ASSERT_THROWS_CODE(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert),
|
||||
AssertionException,
|
||||
ErrorCodes::UserWritesBlocked);
|
||||
}
|
||||
@ -91,11 +112,17 @@ TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingAllowsInternalDatabaseNamespa
|
||||
ReplicaSetWriteBlockBypass::get(opCtx.get()).set(false);
|
||||
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), NamespaceString::createNamespaceString_forTest("admin.coll")));
|
||||
opCtx.get(),
|
||||
NamespaceString::createNamespaceString_forTest("admin.coll"),
|
||||
ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), NamespaceString::createNamespaceString_forTest("config.coll")));
|
||||
opCtx.get(),
|
||||
NamespaceString::createNamespaceString_forTest("config.coll"),
|
||||
ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), NamespaceString::createNamespaceString_forTest("local.coll")));
|
||||
opCtx.get(),
|
||||
NamespaceString::createNamespaceString_forTest("local.coll"),
|
||||
ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingAllowsSystemDotProfile) {
|
||||
@ -108,7 +135,8 @@ TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingAllowsSystemDotProfile) {
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB", "system.profile");
|
||||
ASSERT(nss.isSystemDotProfile());
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(opCtx.get(), nss));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingAllowsWhenBypassEnabled) {
|
||||
@ -124,7 +152,8 @@ TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingAllowsWhenBypassEnabled) {
|
||||
ASSERT(ReplicaSetWriteBlockBypass::get(opCtx.get()).isEnabled());
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(opCtx.get(), nss));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, DisableWriteBlockingClearsIsEnabled) {
|
||||
@ -139,7 +168,8 @@ TEST_F(ReplicaSetWriteBlockStateTest, DisableWriteBlockingClearsIsEnabled) {
|
||||
ASSERT_FALSE(state->isReplicaSetWriteBlockingEnabled());
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(opCtx.get(), nss));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, DisableDeletionsBlockingClearsIsEnabled) {
|
||||
@ -214,5 +244,128 @@ TEST_F(ReplicaSetWriteBlockStateTest, DeletionsBlockingAllowsWhenBypassEnabled)
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetDeletionsAllowed(opCtx.get(), nss));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingIncrementsOnlyRejectedInsertAndUpdateMetrics) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
|
||||
auto* state = ReplicaSetWriteBlockState::get(opCtx.get());
|
||||
state->disableReplicaSetWriteBlocking();
|
||||
ReplicaSetWriteBlockBypass::get(opCtx.get()).set(false);
|
||||
|
||||
const auto before = readReplicaSetWritesBlockRejected(state);
|
||||
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_THROWS_CODE(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert),
|
||||
AssertionException,
|
||||
ErrorCodes::UserWritesBlocked);
|
||||
}
|
||||
ASSERT_THROWS_CODE(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kUpdate),
|
||||
AssertionException,
|
||||
ErrorCodes::UserWritesBlocked);
|
||||
|
||||
const auto after = readReplicaSetWritesBlockRejected(state);
|
||||
ASSERT_EQ(after.inserts, before.inserts + 2);
|
||||
ASSERT_EQ(after.updates, before.updates + 1);
|
||||
ASSERT_EQ(after.deletes, before.deletes);
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingRejectedWritesMetricUnchangedForExemptions) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
|
||||
auto* state = ReplicaSetWriteBlockState::get(opCtx.get());
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
ReplicaSetWriteBlockBypass::get(opCtx.get()).set(false);
|
||||
|
||||
const auto before = readReplicaSetWritesBlockRejected(state);
|
||||
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(),
|
||||
NamespaceString::createNamespaceString_forTest("admin.coll"),
|
||||
ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(),
|
||||
NamespaceString::createNamespaceString_forTest("userDB", "system.profile"),
|
||||
ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
|
||||
const auto after = readReplicaSetWritesBlockRejected(state);
|
||||
ASSERT_EQ(after.inserts, before.inserts);
|
||||
ASSERT_EQ(after.updates, before.updates);
|
||||
ASSERT_EQ(after.deletes, before.deletes);
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, WriteBlockingRejectedWritesMetricUnchangedWhenBypassEnabled) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
|
||||
auto* state = ReplicaSetWriteBlockState::get(opCtx.get());
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
|
||||
auto authSession = AuthorizationSession::get(opCtx->getClient());
|
||||
authSession->grantInternalAuthorization();
|
||||
ReplicaSetWriteBlockBypass::get(opCtx.get()).setFromMetadata(opCtx.get(), {});
|
||||
|
||||
const auto before = readReplicaSetWritesBlockRejected(state);
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
ASSERT_DOES_NOT_THROW(state->checkReplicaSetWritesAllowed(
|
||||
opCtx.get(), nss, ReplicaSetWriteBlockRejectedWriteOp::kInsert));
|
||||
|
||||
const auto after = readReplicaSetWritesBlockRejected(state);
|
||||
ASSERT_EQ(after.inserts, before.inserts);
|
||||
ASSERT_EQ(after.updates, before.updates);
|
||||
ASSERT_EQ(after.deletes, before.deletes);
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest,
|
||||
DeletionBlockingIncrementsOnlyRejectedDeletesMetricOnEachBlock) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
|
||||
auto* state = ReplicaSetWriteBlockState::get(opCtx.get());
|
||||
state->disableReplicaSetDeletionsBlocking();
|
||||
ReplicaSetWriteBlockBypass::get(opCtx.get()).set(false);
|
||||
|
||||
const auto before = readReplicaSetWritesBlockRejected(state);
|
||||
|
||||
state->enableReplicaSetDeletionsBlocking();
|
||||
|
||||
const auto nss = NamespaceString::createNamespaceString_forTest("userDB.coll");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_THROWS_CODE(state->checkReplicaSetDeletionsAllowed(opCtx.get(), nss),
|
||||
AssertionException,
|
||||
ErrorCodes::UserWritesBlocked);
|
||||
}
|
||||
|
||||
const auto after = readReplicaSetWritesBlockRejected(state);
|
||||
ASSERT_EQ(after.inserts, before.inserts);
|
||||
ASSERT_EQ(after.updates, before.updates);
|
||||
ASSERT_EQ(after.deletes, before.deletes + 2);
|
||||
}
|
||||
|
||||
TEST_F(ReplicaSetWriteBlockStateTest, BlockReplicaSetWritesCommandCountersIncrementPerEnable) {
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
Lock::GlobalLock lock(opCtx.get(), MODE_IX);
|
||||
|
||||
auto* state = ReplicaSetWriteBlockState::get(opCtx.get());
|
||||
state->disableReplicaSetWriteBlocking();
|
||||
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
state->disableReplicaSetWriteBlocking();
|
||||
state->enableReplicaSetWriteBlocking(ReplicaSetWritesBlockReasonEnum::kInsufficientDiskSpace);
|
||||
|
||||
BSONObjBuilder bob;
|
||||
state->appendReplicaSetWritesBlockCounters(bob);
|
||||
BSONObj doc = bob.obj();
|
||||
const auto sub = doc.getObjectField("replicaSetWritesBlockCounters");
|
||||
ASSERT_EQ(sub["InsufficientDiskSpace"].safeNumberLong(), 2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mongo
|
||||
|
||||
Loading…
Reference in New Issue
Block a user