summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Pulo <kevin.pulo@mongodb.com>2020-02-16 04:51:35 +0000
committerevergreen <evergreen@mongodb.com>2020-02-16 04:51:35 +0000
commit95c8fc6a4a98f70b07f32565e3ef3e48172efa1e (patch)
tree5a4288ed6451cee86fdd319dcb52a592ec387c0e /src
parent1d21e89fa5c9d838b2f40045c26fe41f306a8873 (diff)
downloadmongo-95c8fc6a4a98f70b07f32565e3ef3e48172efa1e.tar.gz
SERVER-45623 read/write concern provenance
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/SConscript18
-rw-r--r--src/mongo/db/read_write_concern_defaults.cpp8
-rw-r--r--src/mongo/db/read_write_concern_provenance.cpp61
-rw-r--r--src/mongo/db/read_write_concern_provenance.h144
-rw-r--r--src/mongo/db/read_write_concern_provenance_base.idl55
-rw-r--r--src/mongo/db/read_write_concern_provenance_test.cpp193
-rw-r--r--src/mongo/db/repl/SConscript1
-rw-r--r--src/mongo/db/repl/read_concern_args.cpp9
-rw-r--r--src/mongo/db/repl/read_concern_args.h10
-rw-r--r--src/mongo/db/service_entry_point_common.cpp28
-rw-r--r--src/mongo/db/sessions_collection.cpp12
-rw-r--r--src/mongo/db/write_concern.cpp32
-rw-r--r--src/mongo/db/write_concern_options.cpp9
-rw-r--r--src/mongo/db/write_concern_options.h11
-rw-r--r--src/mongo/s/commands/strategy.cpp77
15 files changed, 656 insertions, 12 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index f1810e06368..f09e88afb7f 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -361,12 +361,28 @@ env.Library(
)
env.Library(
+ target="read_write_concern_provenance",
+ source=[
+ "read_write_concern_provenance.cpp",
+ env.Idlc('read_write_concern_provenance_base.idl')[0],
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/idl/idl_parser',
+ ],
+ LIBDEPS_PRIVATE=[
+ 'server_options_core',
+ ],
+)
+
+env.Library(
target="write_concern_options",
source=[
"write_concern_options.cpp",
],
LIBDEPS=[
'$BUILD_DIR/mongo/bson/util/bson_extract',
+ 'read_write_concern_provenance',
],
)
@@ -422,6 +438,7 @@ env.Library(
'$BUILD_DIR/mongo/util/fail_point',
'$BUILD_DIR/mongo/util/net/network',
'$BUILD_DIR/mongo/util/periodic_runner',
+ 'write_concern_options',
],
)
@@ -1853,6 +1870,7 @@ envWithAsio.CppUnitTest(
'operation_time_tracker_test.cpp',
'range_arithmetic_test.cpp',
'read_write_concern_defaults_test.cpp',
+ 'read_write_concern_provenance_test.cpp',
'record_id_test.cpp',
'server_options_test.cpp',
'session_catalog_mongod_test.cpp',
diff --git a/src/mongo/db/read_write_concern_defaults.cpp b/src/mongo/db/read_write_concern_defaults.cpp
index a09653a1a31..4460159502e 100644
--- a/src/mongo/db/read_write_concern_defaults.cpp
+++ b/src/mongo/db/read_write_concern_defaults.cpp
@@ -74,12 +74,20 @@ void ReadWriteConcernDefaults::checkSuitabilityAsDefault(const ReadConcern& rc)
str::stream() << "'" << ReadConcern::kAtClusterTimeFieldName
<< "' is not suitable for the default read concern",
!rc.getArgsAtClusterTime());
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "'" << ReadWriteConcernProvenance::kSourceFieldName
+ << "' must be unset in default read concern",
+ !rc.getProvenance().hasSource());
}
void ReadWriteConcernDefaults::checkSuitabilityAsDefault(const WriteConcern& wc) {
uassert(ErrorCodes::BadValue,
"Unacknowledged write concern is not suitable for the default write concern",
!(wc.wMode.empty() && wc.wNumNodes < 1));
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "'" << ReadWriteConcernProvenance::kSourceFieldName
+ << "' must be unset in default write concern",
+ !wc.getProvenance().hasSource());
}
RWConcernDefault ReadWriteConcernDefaults::generateNewConcerns(
diff --git a/src/mongo/db/read_write_concern_provenance.cpp b/src/mongo/db/read_write_concern_provenance.cpp
new file mode 100644
index 00000000000..591ed77679d
--- /dev/null
+++ b/src/mongo/db/read_write_concern_provenance.cpp
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/read_write_concern_provenance.h"
+
+#include "mongo/db/server_options.h"
+
+namespace mongo {
+
+void ReadWriteConcernProvenance::setSource(boost::optional<Source> source) & {
+ invariant(!hasSource() || getSource() == source,
+ str::stream() << "attempting to re-set provenance, from "
+ << sourceToString(getSource()) << " to " << sourceToString(source));
+ ReadWriteConcernProvenanceBase::setSource(std::move(source));
+}
+
+ReadWriteConcernProvenance ReadWriteConcernProvenance::parse(const IDLParserErrorContext& ctxt,
+ const BSONObj& bsonObject) {
+ return ReadWriteConcernProvenance(ReadWriteConcernProvenanceBase::parse(ctxt, bsonObject));
+}
+
+void ReadWriteConcernProvenance::serialize(BSONObjBuilder* builder) const {
+ if (serverGlobalParams.featureCompatibility.isVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44)) {
+ ReadWriteConcernProvenanceBase::serialize(builder);
+ }
+}
+
+StringData ReadWriteConcernProvenance::sourceToString(boost::optional<Source> source) {
+ return source ? ReadWriteConcernProvenanceSource_serializer(*source) : "(unset)";
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/read_write_concern_provenance.h b/src/mongo/db/read_write_concern_provenance.h
new file mode 100644
index 00000000000..3ab1ba27668
--- /dev/null
+++ b/src/mongo/db/read_write_concern_provenance.h
@@ -0,0 +1,144 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/read_write_concern_provenance_base_gen.h"
+
+namespace mongo {
+
+/**
+ * This class represents the "provenance" (ie. original source) of a read or write concern.
+ *
+ * This information is serialized as a 'provenance' field inside readConcern/writeConcern objects,
+ * which is parsed, stored, and forwarded, but has no other effect on the meaning of the read or
+ * write concern (RWC). This means it is possible to see (in logs, currentOp, profiling, etc) where
+ * a given RWC has come from. It also makes it possible for the server to adjust its behavior based
+ * on the origin of the RWC (even though the semantics of the RWC itself have not changed), for
+ * example, by having special metrics that only apply to RWC from certain sources.
+ *
+ * Possible sources are:
+ *
+ * - unset (ie. the 'provenance' field was absent):
+ * Implicitly means "clientSupplied" (so drivers do not need to send a 'provenance' field).
+ *
+ * - "clientSupplied":
+ * The RWC was originally explicitly passed to a mongos or plain replica set member by the
+ * driver.
+ *
+ * - "implicitDefault":
+ * The client did not supply an explicit RWC, and there was no RWC default, so this RWC
+ * represents the corresponding implicit server default, ie. read concern of "local" (except
+ * "available" on sharded secondaries) and {w: 1, wtimeout: 0}.
+ *
+ * - "customDefault":
+ * Represents an admin-defined cluster-wide RWC default that was applied to the operation
+ * because no explicit RWC was provided, and there was a custom RWC default defined at the time.
+ *
+ * - "getLastErrorDefaults":
+ * Only applicable to write concern, and indicates that it originated from the (deprecated)
+ * 'settings.getLastErrorDefaults' field of the replica set configuration.
+ *
+ * A ReadWriteConcernProvenance object may only have a single Source value throughout its lifetime;
+ * once the Source has been set, attempting to change it will trigger an invariant. This ensures
+ * the integrity of the provenance value as the operation makes its way through the server (and
+ * through the overall cluster).
+ */
+class ReadWriteConcernProvenance : public ReadWriteConcernProvenanceBase {
+public:
+ using Source = ReadWriteConcernProvenanceSourceEnum;
+
+ static constexpr StringData kClientSupplied = "clientSupplied"_sd;
+ static constexpr StringData kImplicitDefault = "implicitDefault"_sd;
+ static constexpr StringData kCustomDefault = "customDefault"_sd;
+ static constexpr StringData kGetLastErrorDefaults = "getLastErrorDefaults"_sd;
+
+ ReadWriteConcernProvenance() = default;
+
+ ReadWriteConcernProvenance(Source source) {
+ setSource(source);
+ }
+
+ ReadWriteConcernProvenance(ReadWriteConcernProvenanceBase&& base)
+ : ReadWriteConcernProvenanceBase(base) {}
+
+ /**
+ * Returns true if this provenance has been set to an actual source, or false if it is unset.
+ */
+ const bool hasSource() const {
+ return static_cast<bool>(getSource());
+ }
+
+ /**
+ * Returns true if the RWC has been supplied by the client, ie. if this provenance's source is
+ * either unset (the client specified RWC but without provenance) or explicitly the
+ * "clientSupplied" source.
+ */
+ const bool isClientSupplied() const {
+ return !hasSource() || *getSource() == Source::clientSupplied;
+ }
+
+ /**
+ * Sets the source of this provenance. In order to prevent accidental clobbering of provenance
+ * with incorrect values, a source cannot change during the provenance's lifetime, except for
+ * the initial transition from kUnset to some other Source value.
+ *
+ * Apart from the initial transition from kUnset to some other Source value, the source of a
+ * provenance must not be changed during its lifetime, and this function enforces that property
+ * with an invariant. This is to prevent provenances from being accidentally clobbered with
+ * incorrect values.
+ */
+ void setSource(boost::optional<Source> source) &;
+
+ /**
+ * Creates a provenance with source according to the given object's 'provenance' field.
+ */
+ static ReadWriteConcernProvenance parse(const IDLParserErrorContext& ctxt,
+ const BSONObj& bsonObject);
+
+ /**
+ * Serialises this provenance by appending a field named "provenance" to the given
+ * BSONObjBuilder.
+ *
+ * Since only servers running 4.4+ can successfully parse read/writeConcern fields with a
+ * 'provenance' field, this output serialization only occurs when FCV is kFullyUpgradedTo44.
+ */
+ void serialize(BSONObjBuilder* builder) const;
+
+ /**
+ * Convenience function.
+ */
+ static StringData sourceToString(boost::optional<Source> source);
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/read_write_concern_provenance_base.idl b/src/mongo/db/read_write_concern_provenance_base.idl
new file mode 100644
index 00000000000..e77d8a4c6be
--- /dev/null
+++ b/src/mongo/db/read_write_concern_provenance_base.idl
@@ -0,0 +1,55 @@
+# Copyright (C) 2020-present MongoDB, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Server Side Public License, version 1,
+# as published by MongoDB, Inc.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Server Side Public License for more details.
+#
+# You should have received a copy of the Server Side Public License
+# along with this program. If not, see
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# As a special exception, the copyright holders give permission to link the
+# code of portions of this program with the OpenSSL library under certain
+# conditions as described in each individual source file and distribute
+# linked combinations including the program with the OpenSSL library. You
+# must comply with the Server Side Public License in all respects for
+# all of the code used other than as permitted herein. If you modify file(s)
+# with this exception, you may extend this exception to your version of the
+# file(s), but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version. If you delete this
+# exception statement from all source files in the program, then also delete
+# it in the license file.
+#
+global:
+ cpp_namespace: "mongo"
+
+
+imports:
+ - "mongo/idl/basic_types.idl"
+
+
+enums:
+ ReadWriteConcernProvenanceSource:
+ description: "Provenance sources"
+ type: string
+ values:
+ clientSupplied: "clientSupplied"
+ implicitDefault: "implicitDefault"
+ customDefault: "customDefault"
+ getLastErrorDefaults: "getLastErrorDefaults"
+
+structs:
+ ReadWriteConcernProvenanceBase:
+ description: "Represents the 'provenance' (ie. original source) of a read or write concern."
+ strict: false
+ fields:
+ provenance:
+ description: "The source for this provenance"
+ cpp_name: source
+ type: ReadWriteConcernProvenanceSource
+ optional: true
diff --git a/src/mongo/db/read_write_concern_provenance_test.cpp b/src/mongo/db/read_write_concern_provenance_test.cpp
new file mode 100644
index 00000000000..8929d448f4e
--- /dev/null
+++ b/src/mongo/db/read_write_concern_provenance_test.cpp
@@ -0,0 +1,193 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/read_write_concern_provenance.h"
+#include "mongo/db/server_options.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(ReadWriteConcernProvenanceTest, DefaultUnset) {
+ ReadWriteConcernProvenance provenance;
+ ASSERT_FALSE(provenance.hasSource());
+ ASSERT_TRUE(provenance.isClientSupplied());
+}
+
+TEST(ReadWriteConcernProvenanceTest, ClientSupplied) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ ASSERT_TRUE(provenance.hasSource());
+ ASSERT_TRUE(provenance.isClientSupplied());
+}
+
+TEST(ReadWriteConcernProvenanceTest, ImplicitDefault) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::implicitDefault);
+ ASSERT_TRUE(provenance.hasSource());
+ ASSERT_FALSE(provenance.isClientSupplied());
+}
+
+TEST(ReadWriteConcernProvenanceTest, CustomDefault) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::customDefault);
+ ASSERT_TRUE(provenance.hasSource());
+ ASSERT_FALSE(provenance.isClientSupplied());
+}
+
+TEST(ReadWriteConcernProvenanceTest, GetLastErrorDefaults) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::getLastErrorDefaults);
+ ASSERT_TRUE(provenance.hasSource());
+ ASSERT_FALSE(provenance.isClientSupplied());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SetSourceFromUnsetToUnset) {
+ ReadWriteConcernProvenance provenance;
+ provenance.setSource(boost::none);
+ ASSERT_FALSE(provenance.hasSource());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SetSourceFromUnsetToSomething) {
+ ReadWriteConcernProvenance provenance;
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ ASSERT_TRUE(ReadWriteConcernProvenance::Source::clientSupplied == provenance.getSource());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SetSourceFromSomethingToSame) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ ASSERT_TRUE(ReadWriteConcernProvenance::Source::clientSupplied == provenance.getSource());
+}
+
+DEATH_TEST(ReadWriteConcernProvenanceTest,
+ SetSourceFromSomethingToUnset,
+ "attempting to re-set provenance") {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ provenance.setSource(boost::none);
+}
+
+DEATH_TEST(ReadWriteConcernProvenanceTest,
+ SetSourceFromSomethingToSomethingElse,
+ "attempting to re-set provenance") {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ provenance.setSource(ReadWriteConcernProvenance::Source::implicitDefault);
+}
+
+TEST(ReadWriteConcernProvenanceTest, ParseAbsentElement) {
+ BSONObj obj = BSON("something"
+ << "else");
+ auto provenance = ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("ReadWriteConcernProvenanceTest"), obj);
+ ASSERT_FALSE(provenance.hasSource());
+}
+
+TEST(ReadWriteConcernProvenanceTest, ParseNonString) {
+ BSONObj obj = BSON("provenance" << 42);
+ ASSERT_THROWS_CODE(ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("ReadWriteConcernProvenanceTest"), obj),
+ DBException,
+ ErrorCodes::TypeMismatch);
+}
+
+TEST(ReadWriteConcernProvenanceTest, ParseValidSource) {
+ BSONObj obj = BSON("provenance"
+ << "clientSupplied");
+ auto provenance = ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("ReadWriteConcernProvenanceTest"), obj);
+ ASSERT_TRUE(ReadWriteConcernProvenance::Source::clientSupplied == provenance.getSource());
+}
+
+TEST(ReadWriteConcernProvenanceTest, ParseInvalidSource) {
+ BSONObj obj = BSON("provenance"
+ << "foobar");
+ ASSERT_THROWS_CODE(ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("ReadWriteConcernProvenanceTest"), obj),
+ DBException,
+ ErrorCodes::BadValue);
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeUnsetVersionUnset) {
+ ReadWriteConcernProvenance provenance;
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault42Behavior);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSONObj(), builder.obj());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeSetVersionUnset) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kUnsetDefault42Behavior);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSONObj(), builder.obj());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeUnsetVersion42) {
+ ReadWriteConcernProvenance provenance;
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo42);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSONObj(), builder.obj());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeSetVersion42) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo42);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSONObj(), builder.obj());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeUnsetVersion44) {
+ ReadWriteConcernProvenance provenance;
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSONObj(), builder.obj());
+}
+
+TEST(ReadWriteConcernProvenanceTest, SerializeSetVersion44) {
+ ReadWriteConcernProvenance provenance(ReadWriteConcernProvenance::Source::clientSupplied);
+ BSONObjBuilder builder;
+ serverGlobalParams.featureCompatibility.setVersion(
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44);
+ provenance.serialize(&builder);
+ ASSERT_BSONOBJ_EQ(BSON("provenance"
+ << "clientSupplied"),
+ builder.obj());
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index e90bae51b06..0a01cd2f57d 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -759,6 +759,7 @@ env.Library('read_concern_args',
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/bson/util/bson_extract',
'$BUILD_DIR/mongo/db/logical_time',
+ '$BUILD_DIR/mongo/db/read_write_concern_provenance',
'optime',
])
diff --git a/src/mongo/db/repl/read_concern_args.cpp b/src/mongo/db/repl/read_concern_args.cpp
index 45e826482ee..2ef071a38cc 100644
--- a/src/mongo/db/repl/read_concern_args.cpp
+++ b/src/mongo/db/repl/read_concern_args.cpp
@@ -182,6 +182,13 @@ Status ReadConcernArgs::parse(const BSONObj& readConcernObj) {
<< readConcernLevels::kAvailableName << "', or '"
<< readConcernLevels::kSnapshotName << "'");
}
+ } else if (fieldName == ReadWriteConcernProvenance::kSourceFieldName) {
+ try {
+ _provenance = ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("ReadConcernArgs::parse"), readConcernObj);
+ } catch (const DBException&) {
+ return exceptionToStatus();
+ }
} else {
return Status(ErrorCodes::InvalidOptions,
str::stream() << "Unrecognized option in " << kReadConcernFieldName
@@ -283,6 +290,8 @@ void ReadConcernArgs::_appendInfoInner(BSONObjBuilder* builder) const {
if (_atClusterTime) {
builder->append(kAtClusterTimeFieldName, _atClusterTime->asTimestamp());
}
+
+ _provenance.serialize(builder);
}
void ReadConcernArgs::appendInfo(BSONObjBuilder* builder) const {
diff --git a/src/mongo/db/repl/read_concern_args.h b/src/mongo/db/repl/read_concern_args.h
index 8a34b4248b9..1adba9777fc 100644
--- a/src/mongo/db/repl/read_concern_args.h
+++ b/src/mongo/db/repl/read_concern_args.h
@@ -35,6 +35,7 @@
#include "mongo/base/status.h"
#include "mongo/db/json.h"
#include "mongo/db/logical_time.h"
+#include "mongo/db/read_write_concern_provenance.h"
#include "mongo/db/repl/optime.h"
#include "mongo/db/repl/read_concern_level.h"
#include "mongo/util/time_support.h"
@@ -168,6 +169,13 @@ public:
BSONObj toBSONInner() const;
std::string toString() const;
+ ReadWriteConcernProvenance& getProvenance() {
+ return _provenance;
+ }
+ const ReadWriteConcernProvenance& getProvenance() const {
+ return _provenance;
+ }
+
private:
/**
* Appends level, afterOpTime, and the other "inner" fields of the read concern args.
@@ -200,6 +208,8 @@ private:
* opposed to being absent or missing.
*/
bool _specified;
+
+ ReadWriteConcernProvenance _provenance;
};
} // namespace repl
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index ccab76f4357..30fe2096622 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -257,6 +257,8 @@ StatusWith<repl::ReadConcernArgs> _extractReadConcern(OperationContext* opCtx,
return readConcernParseStatus;
}
+ bool clientSuppliedReadConcern = readConcernArgs.isSpecified();
+ bool customDefaultWasApplied = false;
auto readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
if (readConcernSupport.defaultReadConcernPermit.isOK() &&
(startTransaction || !opCtx->inMultiDocumentTransaction()) &&
@@ -283,6 +285,7 @@ StatusWith<repl::ReadConcernArgs> _extractReadConcern(OperationContext* opCtx,
const auto rcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
.getDefaultReadConcern(opCtx);
if (rcDefault) {
+ customDefaultWasApplied = true;
readConcernArgs = std::move(*rcDefault);
LOGV2_DEBUG(21955,
2,
@@ -299,6 +302,19 @@ StatusWith<repl::ReadConcernArgs> _extractReadConcern(OperationContext* opCtx,
}
}
+ // It's fine for clients to provide any provenance value to mongod. But if they haven't, then an
+ // appropriate provenance needs to be determined.
+ auto& provenance = readConcernArgs.getProvenance();
+ if (!provenance.hasSource()) {
+ if (clientSuppliedReadConcern) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ } else if (customDefaultWasApplied) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::customDefault);
+ } else {
+ provenance.setSource(ReadWriteConcernProvenance::Source::implicitDefault);
+ }
+ }
+
// If we are starting a transaction, we only need to check whether the read concern is
// appropriate for running a transaction. There is no need to check whether the specific
// command supports the read concern, because all commands that are allowed to run in a
@@ -701,6 +717,12 @@ bool runCommandImpl(OperationContext* opCtx,
validateWriteConcernForTransaction(*extractedWriteConcern,
invocation->definition()->getName());
}
+
+ // Ensure that the WC being set on the opCtx has provenance.
+ invariant(extractedWriteConcern->getProvenance().hasSource(),
+ str::stream() << "unexpected unset provenance on writeConcern: "
+ << extractedWriteConcern->toBSON());
+
opCtx->setWriteConcern(*extractedWriteConcern);
}
@@ -1018,6 +1040,12 @@ void execCommandDatabase(OperationContext* opCtx,
if (!skipReadConcern) {
auto newReadConcernArgs = uassertStatusOK(
_extractReadConcern(opCtx, invocation.get(), request.body, startTransaction));
+
+ // Ensure that the RC being set on the opCtx has provenance.
+ invariant(newReadConcernArgs.getProvenance().hasSource(),
+ str::stream() << "unexpected unset provenance on readConcern: "
+ << newReadConcernArgs.toBSONInner());
+
{
// We must obtain the client lock to set the ReadConcernArgs on the operation
// context as it may be concurrently read by CurrentOp.
diff --git a/src/mongo/db/sessions_collection.cpp b/src/mongo/db/sessions_collection.cpp
index 38a843769c6..4e0e121abaf 100644
--- a/src/mongo/db/sessions_collection.cpp
+++ b/src/mongo/db/sessions_collection.cpp
@@ -56,11 +56,9 @@ constexpr size_t kMaxBatchSize = 1000;
// Used to refresh or remove items from the session collection with write
// concern majority
-const BSONObj kMajorityWriteConcern =
- WriteConcernOptions(WriteConcernOptions::kMajority,
- WriteConcernOptions::SyncMode::UNSET,
- WriteConcernOptions::kWriteConcernTimeoutSystem)
- .toBSON();
+const WriteConcernOptions kMajorityWriteConcern{WriteConcernOptions::kMajority,
+ WriteConcernOptions::SyncMode::UNSET,
+ WriteConcernOptions::kWriteConcernTimeoutSystem};
BSONObj lsidQuery(const LogicalSessionId& lsid) {
@@ -198,7 +196,7 @@ void SessionsCollection::_doRefresh(const NamespaceString& ns,
batch->append("update", ns.coll());
batch->append("ordered", false);
batch->append("allowImplicitCollectionCreation", false);
- batch->append(WriteConcernOptions::kWriteConcernField, kMajorityWriteConcern);
+ batch->append(WriteConcernOptions::kWriteConcernField, kMajorityWriteConcern.toBSON());
};
auto add = [](BSONArrayBuilder* entries, const LogicalSessionRecord& record) {
@@ -215,7 +213,7 @@ void SessionsCollection::_doRemove(const NamespaceString& ns,
auto init = [ns](BSONObjBuilder* batch) {
batch->append("delete", ns.coll());
batch->append("ordered", false);
- batch->append(WriteConcernOptions::kWriteConcernField, kMajorityWriteConcern);
+ batch->append(WriteConcernOptions::kWriteConcernField, kMajorityWriteConcern.toBSON());
};
auto add = [](BSONArrayBuilder* builder, const LogicalSessionId& lsid) {
diff --git a/src/mongo/db/write_concern.cpp b/src/mongo/db/write_concern.cpp
index 3f55e87de40..e1fa1b4540f 100644
--- a/src/mongo/db/write_concern.cpp
+++ b/src/mongo/db/write_concern.cpp
@@ -82,10 +82,14 @@ StatusWith<WriteConcernOptions> extractWriteConcern(OperationContext* opCtx,
WriteConcernOptions writeConcern = wcResult.getValue();
- // If no write concern is specified in the command (so usedDefault is true), then use the
- // cluster-wide default WC (if there is one), or else the default WC from the ReplSetConfig
- // (which takes the ReplicationCoordinator mutex).
- if (writeConcern.usedDefault) {
+ bool clientSuppliedWriteConcern = !writeConcern.usedDefault;
+ bool customDefaultWasApplied = false;
+ bool getLastErrorDefaultsWasApplied = false;
+
+ // If no write concern is specified in the command, then use the cluster-wide default WC (if
+ // there is one), or else the default WC from the ReplSetConfig (which takes the
+ // ReplicationCoordinator mutex).
+ if (!clientSuppliedWriteConcern) {
writeConcern = ([&]() {
// WriteConcern defaults can only be applied on regular replica set members. Operations
// received by shard and config servers should always have WC explicitly specified.
@@ -95,9 +99,11 @@ StatusWith<WriteConcernOptions> extractWriteConcern(OperationContext* opCtx,
(!opCtx->inMultiDocumentTransaction() ||
isTransactionCommand(cmdObj.firstElementFieldName())) &&
!opCtx->getClient()->isInDirectClient()) {
+
auto wcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
.getDefaultWriteConcern(opCtx);
if (wcDefault) {
+ customDefaultWasApplied = true;
LOGV2_DEBUG(22548,
2,
"Applying default writeConcern on {cmdObj_firstElementFieldName} "
@@ -128,6 +134,8 @@ StatusWith<WriteConcernOptions> extractWriteConcern(OperationContext* opCtx,
if (getLastErrorDefault.wNumNodes == 1 && getLastErrorDefault.wTimeout == 0) {
getLastErrorDefault.usedDefault = true;
getLastErrorDefault.usedDefaultW = true;
+ } else {
+ getLastErrorDefaultsWasApplied = true;
}
return getLastErrorDefault;
})();
@@ -137,6 +145,21 @@ StatusWith<WriteConcernOptions> extractWriteConcern(OperationContext* opCtx,
writeConcern.usedDefaultW = true;
}
+ // It's fine for clients to provide any provenance value to mongod. But if they haven't, then an
+ // appropriate provenance needs to be determined.
+ auto& provenance = writeConcern.getProvenance();
+ if (!provenance.hasSource()) {
+ if (clientSuppliedWriteConcern) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ } else if (customDefaultWasApplied) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::customDefault);
+ } else if (getLastErrorDefaultsWasApplied) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::getLastErrorDefaults);
+ } else {
+ provenance.setSource(ReadWriteConcernProvenance::Source::implicitDefault);
+ }
+ }
+
if (writeConcern.usedDefault && serverGlobalParams.clusterRole == ClusterRole::ConfigServer &&
!opCtx->getClient()->isInDirectClient() &&
(opCtx->getClient()->session() &&
@@ -146,6 +169,7 @@ StatusWith<WriteConcernOptions> extractWriteConcern(OperationContext* opCtx,
// does not specify writeConcern when writing to the config server.
writeConcern = {
WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, Seconds(30)};
+ writeConcern.getProvenance().setSource(ReadWriteConcernProvenance::Source::implicitDefault);
} else {
Status wcStatus = validateWriteConcern(opCtx, writeConcern);
if (!wcStatus.isOK()) {
diff --git a/src/mongo/db/write_concern_options.cpp b/src/mongo/db/write_concern_options.cpp
index f24ec703469..298941fbdef 100644
--- a/src/mongo/db/write_concern_options.cpp
+++ b/src/mongo/db/write_concern_options.cpp
@@ -131,6 +131,13 @@ StatusWith<WriteConcernOptions> WriteConcernOptions::parse(const BSONObj& obj) {
// Ignore.
} else if (fieldName.equalCaseInsensitive(kGetLastErrorFieldName)) {
// Ignore GLE field.
+ } else if (fieldName == ReadWriteConcernProvenance::kSourceFieldName) {
+ try {
+ writeConcern._provenance = ReadWriteConcernProvenance::parse(
+ IDLParserErrorContext("WriteConcernOptions::parse"), obj);
+ } catch (const DBException&) {
+ return exceptionToStatus();
+ }
} else {
return Status(ErrorCodes::FailedToParse,
str::stream() << "unrecognized write concern field: " << fieldName);
@@ -234,6 +241,8 @@ BSONObj WriteConcernOptions::toBSON() const {
builder.append("wtimeout", wTimeout);
+ _provenance.serialize(&builder);
+
return builder.obj();
}
diff --git a/src/mongo/db/write_concern_options.h b/src/mongo/db/write_concern_options.h
index cca80457e4e..e6ea2ddbb55 100644
--- a/src/mongo/db/write_concern_options.h
+++ b/src/mongo/db/write_concern_options.h
@@ -32,6 +32,7 @@
#include <string>
#include "mongo/db/jsobj.h"
+#include "mongo/db/read_write_concern_provenance.h"
namespace mongo {
@@ -97,6 +98,13 @@ public:
*/
bool needToWaitForOtherNodes() const;
+ ReadWriteConcernProvenance& getProvenance() {
+ return _provenance;
+ }
+ const ReadWriteConcernProvenance& getProvenance() const {
+ return _provenance;
+ }
+
// Returns the BSON representation of this object.
// Warning: does not return the same object passed on the last parse() call.
BSONObj toBSON() const;
@@ -119,6 +127,9 @@ public:
// True if the default 'w' value of w:1 was used.
bool usedDefaultW = false;
+
+private:
+ ReadWriteConcernProvenance _provenance;
};
} // namespace mongo
diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp
index 81104682c3c..990ae16a972 100644
--- a/src/mongo/s/commands/strategy.cpp
+++ b/src/mongo/s/commands/strategy.cpp
@@ -33,6 +33,8 @@
#include "mongo/s/commands/strategy.h"
+#include <fmt/format.h>
+
#include "mongo/base/data_cursor.h"
#include "mongo/base/init.h"
#include "mongo/base/status.h"
@@ -87,6 +89,8 @@
#include "mongo/util/str.h"
#include "mongo/util/timer.h"
+using namespace fmt::literals;
+
namespace mongo {
namespace {
@@ -447,13 +451,17 @@ void runCommand(OperationContext* opCtx,
return;
}
- if (supportsWriteConcern && wc.usedDefault &&
+ bool clientSuppliedWriteConcern = !wc.usedDefault;
+ bool customDefaultWriteConcernWasApplied = false;
+
+ if (supportsWriteConcern && !clientSuppliedWriteConcern &&
(!TransactionRouter::get(opCtx) || isTransactionCommand(commandName))) {
// This command supports WC, but wasn't given one - so apply the default, if there is
// one.
if (const auto wcDefault = ReadWriteConcernDefaults::get(opCtx->getServiceContext())
.getDefaultWriteConcern(opCtx)) {
wc = *wcDefault;
+ customDefaultWriteConcernWasApplied = true;
LOGV2_DEBUG(
22766,
2,
@@ -468,9 +476,42 @@ void runCommand(OperationContext* opCtx,
}
if (supportsWriteConcern) {
+ auto& provenance = wc.getProvenance();
+
+ // ClientSupplied is the only provenance that clients are allowed to pass to mongos.
+ if (provenance.hasSource() && !provenance.isClientSupplied()) {
+ auto responseBuilder = replyBuilder->getBodyBuilder();
+ CommandHelpers::appendCommandStatusNoThrow(
+ responseBuilder,
+ Status{ErrorCodes::InvalidOptions,
+ "writeConcern provenance must be unset or \"{}\""_format(
+ ReadWriteConcernProvenance::kClientSupplied)});
+ return;
+ }
+
+ // If the client didn't provide a provenance, then an appropriate value needs to be
+ // determined.
+ if (!provenance.hasSource()) {
+ if (clientSuppliedWriteConcern) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ } else if (customDefaultWriteConcernWasApplied) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::customDefault);
+ } else {
+ provenance.setSource(ReadWriteConcernProvenance::Source::implicitDefault);
+ }
+ }
+
+ // Ensure that the WC being set on the opCtx has provenance.
+ invariant(wc.getProvenance().hasSource(),
+ str::stream()
+ << "unexpected unset provenance on writeConcern: " << wc.toBSON());
+
opCtx->setWriteConcern(wc);
}
+ bool clientSuppliedReadConcern = readConcernArgs.isSpecified();
+ bool customDefaultReadConcernWasApplied = false;
+
auto readConcernSupport = invocation->supportsReadConcern(readConcernArgs.getLevel());
if (readConcernSupport.defaultReadConcernPermit.isOK() &&
(startTransaction || !TransactionRouter::get(opCtx))) {
@@ -485,6 +526,7 @@ void runCommand(OperationContext* opCtx,
stdx::lock_guard<Client> lk(*opCtx->getClient());
readConcernArgs = std::move(*rcDefault);
}
+ customDefaultReadConcernWasApplied = true;
LOGV2_DEBUG(22767,
2,
"Applying default readConcern on {invocation_definition_getName} "
@@ -499,6 +541,39 @@ void runCommand(OperationContext* opCtx,
}
}
+ auto& provenance = readConcernArgs.getProvenance();
+
+ // ClientSupplied is the only provenance that clients are allowed to pass to mongos.
+ if (provenance.hasSource() && !provenance.isClientSupplied()) {
+ auto responseBuilder = replyBuilder->getBodyBuilder();
+ CommandHelpers::appendCommandStatusNoThrow(
+ responseBuilder,
+ Status{ErrorCodes::InvalidOptions,
+ "readConcern provenance must be unset or \"{}\""_format(
+ ReadWriteConcernProvenance::kClientSupplied)});
+ return;
+ }
+
+ // If the client didn't provide a provenance, then an appropriate value needs to be
+ // determined.
+ if (!provenance.hasSource()) {
+ // We must obtain the client lock to set the provenance of the opCtx's ReadConcernArgs
+ // as it may be concurrently read by CurrentOp.
+ stdx::lock_guard<Client> lk(*opCtx->getClient());
+ if (clientSuppliedReadConcern) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::clientSupplied);
+ } else if (customDefaultReadConcernWasApplied) {
+ provenance.setSource(ReadWriteConcernProvenance::Source::customDefault);
+ } else {
+ provenance.setSource(ReadWriteConcernProvenance::Source::implicitDefault);
+ }
+ }
+
+ // Ensure that the RC on the opCtx has provenance.
+ invariant(readConcernArgs.getProvenance().hasSource(),
+ str::stream() << "unexpected unset provenance on readConcern: "
+ << readConcernArgs.toBSONInner());
+
// If we are starting a transaction, we only need to check whether the read concern is
// appropriate for running a transaction. There is no need to check whether the specific
// command supports the read concern, because all commands that are allowed to run in a