From 95c8fc6a4a98f70b07f32565e3ef3e48172efa1e Mon Sep 17 00:00:00 2001 From: Kevin Pulo Date: Sun, 16 Feb 2020 04:51:35 +0000 Subject: SERVER-45623 read/write concern provenance --- src/mongo/db/SConscript | 18 ++ src/mongo/db/read_write_concern_defaults.cpp | 8 + src/mongo/db/read_write_concern_provenance.cpp | 61 +++++++ src/mongo/db/read_write_concern_provenance.h | 144 +++++++++++++++ .../db/read_write_concern_provenance_base.idl | 55 ++++++ .../db/read_write_concern_provenance_test.cpp | 193 +++++++++++++++++++++ src/mongo/db/repl/SConscript | 1 + src/mongo/db/repl/read_concern_args.cpp | 9 + src/mongo/db/repl/read_concern_args.h | 10 ++ src/mongo/db/service_entry_point_common.cpp | 28 +++ src/mongo/db/sessions_collection.cpp | 12 +- src/mongo/db/write_concern.cpp | 32 +++- src/mongo/db/write_concern_options.cpp | 9 + src/mongo/db/write_concern_options.h | 11 ++ src/mongo/s/commands/strategy.cpp | 77 +++++++- 15 files changed, 656 insertions(+), 12 deletions(-) create mode 100644 src/mongo/db/read_write_concern_provenance.cpp create mode 100644 src/mongo/db/read_write_concern_provenance.h create mode 100644 src/mongo/db/read_write_concern_provenance_base.idl create mode 100644 src/mongo/db/read_write_concern_provenance_test.cpp (limited to 'src') 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 @@ -360,6 +360,21 @@ 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=[ @@ -367,6 +382,7 @@ env.Library( ], 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 + * . + * + * 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) & { + 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) { + 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 + * . + * + * 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(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) &; + + /** + * 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); +}; + +} // 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 +# . +# +# 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 + * . + * + * 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 _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 _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 _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 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 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 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 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 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::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 #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 + #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 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 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 -- cgit v1.2.1