SERVER-123734 Add new metrics to track the new user write block (#52232)

GitOrigin-RevId: 55a9450a650785083cbc49c7084fbf575aac3515
This commit is contained in:
Anna Maria Nestorov 2026-05-22 10:35:19 +02:00 committed by MongoDB Bot
parent ceec5e80e8
commit 7e39b9cb03
8 changed files with 365 additions and 32 deletions

View File

@ -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}));

View File

@ -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",

View File

@ -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();

View File

@ -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 &&

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -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