diff options
author | Adam Rayner <adam.rayner@gmail.com> | 2022-03-08 15:35:04 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-03-30 07:19:10 +0000 |
commit | dca101cdb1370e25ebf1730a46467a820919c1d5 (patch) | |
tree | 5b2458a6e1f1f86eefe9c8d2ab18fa4f42c21243 /src/mongo/db | |
parent | b7cec3249e810734a34f2a095d1c994f293d201e (diff) | |
download | mongo-dca101cdb1370e25ebf1730a46467a820919c1d5.tar.gz |
SERVER-64326 setClusterParameter command unpacks object parameters and valdiates
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/commands/SConscript | 18 | ||||
-rw-r--r-- | src/mongo/db/commands/set_cluster_parameter_command.cpp | 24 | ||||
-rw-r--r-- | src/mongo/db/commands/set_cluster_parameter_invocation.cpp | 128 | ||||
-rw-r--r-- | src/mongo/db/commands/set_cluster_parameter_invocation.h | 79 | ||||
-rw-r--r-- | src/mongo/db/commands/set_cluster_parameter_invocation_test.cpp | 281 |
5 files changed, 523 insertions, 7 deletions
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 29e58008a5b..37055dd8a94 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -623,7 +623,7 @@ env.Library( 'rwc_defaults_commands', 'server_status', 'servers', - 'set_cluster_parameter_idl', + 'set_cluster_parameter_invocation', 'set_feature_compatibility_version_idl', 'set_index_commit_quorum_idl', 'set_user_write_block_mode_idl', @@ -637,6 +637,18 @@ env.Library( ) env.Library( + target='set_cluster_parameter_invocation', + source=[ + 'set_cluster_parameter_invocation.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/s/write_ops/cluster_write_ops', + 'set_cluster_parameter_idl' + ], +) + +env.Library( target='kill_common', source=[ 'kill_op_cmd_base.cpp', @@ -814,6 +826,7 @@ env.CppUnitTest( "mr_test.cpp" if get_option('js-engine') != 'none' else [], "parse_log_component_settings_test.cpp", "plan_cache_commands_test.cpp", + "set_cluster_parameter_invocation_test.cpp", ], LIBDEPS=[ "$BUILD_DIR/mongo/db/auth/authmocks", @@ -834,6 +847,7 @@ env.CppUnitTest( "create_command", "mongod", "servers", + "set_cluster_parameter_invocation", "standalone", ], -) +)
\ No newline at end of file diff --git a/src/mongo/db/commands/set_cluster_parameter_command.cpp b/src/mongo/db/commands/set_cluster_parameter_command.cpp index 0abcb976955..b5285dbeae3 100644 --- a/src/mongo/db/commands/set_cluster_parameter_command.cpp +++ b/src/mongo/db/commands/set_cluster_parameter_command.cpp @@ -27,14 +27,13 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand - #include "mongo/platform/basic.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" #include "mongo/db/commands/set_cluster_parameter_gen.h" -#include "mongo/logv2/log.h" +#include "mongo/db/commands/set_cluster_parameter_invocation.h" +#include "mongo/idl/cluster_server_parameter_gen.h" namespace mongo { @@ -60,17 +59,32 @@ public: public: using InvocationBase::InvocationBase; - void typedRun(OperationContext*) { - LOGV2(6226501, "Received setClusterParameter on node"); + void typedRun(OperationContext* opCtx) { + + uassert(ErrorCodes::IllegalOperation, + "Cannot set cluster parameter, gFeatureFlagClusterWideConfig is not enabled", + gFeatureFlagClusterWideConfig.isEnabledAndIgnoreFCV()); + + std::unique_ptr<ServerParameterService> parameterService = + std::make_unique<ClusterParameterService>(); + + DBDirectClient dbClient(opCtx); + ClusterParameterDBClientService dbService(dbClient); + + SetClusterParameterInvocation invocation{std::move(parameterService), dbService}; + + invocation.invoke(opCtx, request()); } private: bool supportsWriteConcern() const override { return false; } + NamespaceString ns() const override { return NamespaceString(); } + void doCheckAuthorization(OperationContext* opCtx) const override { uassert(ErrorCodes::Unauthorized, "Unauthorized", diff --git a/src/mongo/db/commands/set_cluster_parameter_invocation.cpp b/src/mongo/db/commands/set_cluster_parameter_invocation.cpp new file mode 100644 index 00000000000..4cb1087996a --- /dev/null +++ b/src/mongo/db/commands/set_cluster_parameter_invocation.cpp @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2022-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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/set_cluster_parameter_gen.h" +#include "mongo/db/commands/set_cluster_parameter_invocation.h" +#include "mongo/db/vector_clock.h" +#include "mongo/idl/cluster_server_parameter_gen.h" +#include "mongo/logv2/log.h" +#include "mongo/s/write_ops/batched_command_response.h" + +namespace mongo { + +void SetClusterParameterInvocation::invoke(OperationContext* opCtx, + const SetClusterParameter& cmd) { + + BSONObj cmdParamObj = cmd.getCommandParameter(); + BSONElement commandElement = cmdParamObj.firstElement(); + StringData parameterName = commandElement.fieldName(); + + const ServerParameter* serverParameter = _sps->getIfExists(parameterName); + + uassert(6432601, + str::stream() << "Unknown Cluster Parameter " << parameterName, + serverParameter != nullptr); + uassert(6432602, + "Cluster parameter value must be an object", + BSONType::Object == commandElement.type()); + + LogicalTime clusterTime = _dbService.getUpdateClusterTime(opCtx); + + BSONObjBuilder updateBuilder; + updateBuilder << "_id" << parameterName << "clusterParameterTime" << clusterTime.toBSON(); + updateBuilder.appendElements(commandElement.Obj()); + + BSONObj query = BSON("_id" << parameterName); + BSONObj update = updateBuilder.obj(); + + LOGV2(6432603, "Updating cluster parameter on-disk", "clusterParameter"_attr = parameterName); + uassertStatusOK(serverParameter->validate(update)); + + Status updateResult = _dbService.updateParameterOnDisk(query, update); + uassertStatusOK(updateResult); +} + +LogicalTime ClusterParameterDBClientService::getUpdateClusterTime(OperationContext* opCtx) { + VectorClock::VectorTime vt = VectorClock::get(opCtx)->getTime(); + return vt.clusterTime(); +} + +Status ClusterParameterDBClientService::updateParameterOnDisk(BSONObj query, BSONObj update) { + BSONObj res; + + BSONObjBuilder set; + set.append("$set", update); + set.doneFast(); + + const std::string configDb = "config"; + const auto kMajorityWriteConcern = BSON("writeConcern" << BSON("w" + << "majority")); + const NamespaceString kClusterParametersNamespace(configDb, "clusterParameters"); + + try { + _dbClient.runCommand( + configDb, + [&] { + write_ops::UpdateCommandRequest updateOp(kClusterParametersNamespace); + updateOp.setUpdates({[&] { + write_ops::UpdateOpEntry entry; + entry.setQ(query); + entry.setU(write_ops::UpdateModification::parseFromClassicUpdate(update)); + entry.setMulti(false); + entry.setUpsert(true); + return entry; + }()}); + + return updateOp.toBSON(kMajorityWriteConcern); + }(), + res); + } catch (const DBException& ex) { + return ex.toStatus(); + } + + BatchedCommandResponse response; + std::string errmsg; + + if (!response.parseBSON(res, &errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); + } + + return Status::OK(); +} + +const ServerParameter* ClusterParameterService::getIfExists(StringData name) { + return ServerParameterSet::getClusterParameterSet()->getIfExists(name); +} +} // namespace mongo diff --git a/src/mongo/db/commands/set_cluster_parameter_invocation.h b/src/mongo/db/commands/set_cluster_parameter_invocation.h new file mode 100644 index 00000000000..6077ccac5d9 --- /dev/null +++ b/src/mongo/db/commands/set_cluster_parameter_invocation.h @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2022-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/db/commands/set_cluster_parameter_gen.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/dbhelpers.h" + +namespace mongo { + +class ServerParameterService { +public: + virtual const ServerParameter* getIfExists(StringData parameterName) = 0; + virtual ~ServerParameterService() = default; +}; + +class ClusterParameterService final : public ServerParameterService { +public: + const ServerParameter* getIfExists(StringData parameterName) override; +}; + +class DBClientService { +public: + virtual Status updateParameterOnDisk(BSONObj query, BSONObj update) = 0; + virtual LogicalTime getUpdateClusterTime(OperationContext*) = 0; + virtual ~DBClientService() = default; +}; + +class ClusterParameterDBClientService final : public DBClientService { +public: + ClusterParameterDBClientService(DBDirectClient& dbDirectClient) : _dbClient(dbDirectClient) {} + Status updateParameterOnDisk(BSONObj query, BSONObj update) override; + LogicalTime getUpdateClusterTime(OperationContext*) override; + +private: + DBDirectClient& _dbClient; +}; + +class SetClusterParameterInvocation { +public: + SetClusterParameterInvocation(std::unique_ptr<ServerParameterService> serverParmeterService, + DBClientService& dbClientService) + : _sps(std::move(serverParmeterService)), _dbService(dbClientService) {} + + void invoke(OperationContext*, const SetClusterParameter&); + +private: + std::unique_ptr<ServerParameterService> _sps; + DBClientService& _dbService; + + Status updateParameterOnDisk(BSONObj query, BSONObj update); +}; + +} // namespace mongo diff --git a/src/mongo/db/commands/set_cluster_parameter_invocation_test.cpp b/src/mongo/db/commands/set_cluster_parameter_invocation_test.cpp new file mode 100644 index 00000000000..3934e890684 --- /dev/null +++ b/src/mongo/db/commands/set_cluster_parameter_invocation_test.cpp @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2022-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. + */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault + +#include "mongo/db/operation_context.h" +#include "mongo/db/operation_id.h" +#include "mongo/db/service_context.h" +#include "mongo/platform/basic.h" +#include <functional> + +#include "mongo/db/dbdirectclient.h" +#include "mongo/logv2/log.h" +#include "mongo/unittest/unittest.h" + +#include "mongo/db/commands/set_cluster_parameter_invocation.h" +#include "mongo/idl/idl_parser.h" + +namespace mongo { +namespace { + +// Mocks +class MockParameterService : public ServerParameterService { +public: + MockParameterService(std::function<ServerParameter*(StringData)> getIfExists) + : getIfExistsMock(getIfExists){}; + + const ServerParameter* getIfExists(StringData parameterName) { + return getIfExistsMock(parameterName); + } + +private: + std::function<ServerParameter*(StringData)> getIfExistsMock; +}; + +class MockServerParameter : public ServerParameter { +public: + MockServerParameter(StringData name, + std::function<Status(const BSONElement& newValueElement)> validateImpl) + : ServerParameter(name, ServerParameterType::kRuntimeOnly) { + this->validateImpl = validateImpl; + } + void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) {} + + void appendSupportingRoundtrip(OperationContext* opCtx, + BSONObjBuilder& b, + const std::string& name) {} + + Status set(const BSONElement& newValueElement) { + return Status(ErrorCodes::BadValue, "Should not call set() in this test"); + } + + Status setFromString(const std::string& str) { + return Status(ErrorCodes::BadValue, "Should not call setFromString in this test"); + } + + Status validate(const BSONElement& newValueElement) const { + return validateImpl(newValueElement); + } + +private: + std::function<Status(const BSONElement& newValueElement)> validateImpl; +}; + +class DBClientMock : public DBClientService { +public: + DBClientMock(std::function<Status(BSONObj, BSONObj)> updateParameterOnDiskMock) { + this->updateParameterOnDiskMockImpl = updateParameterOnDiskMock; + } + + Status updateParameterOnDisk(BSONObj cmd, BSONObj info) override { + return updateParameterOnDiskMockImpl(cmd, info); + } + + LogicalTime getUpdateClusterTime(OperationContext*) override { + LogicalTime lt; + return lt; + } + +private: + std::function<Status(BSONObj, BSONObj)> updateParameterOnDiskMockImpl; +}; + +MockServerParameter alwaysValidatingServerParameter(StringData name) { + + MockServerParameter sp(name, [&](const BSONElement& newValueElement) { return Status::OK(); }); + + return sp; +} + +MockServerParameter alwaysInvalidatingServerParameter(StringData name) { + + MockServerParameter sp(name, [&](const BSONElement& newValueElement) { + return Status(ErrorCodes::BadValue, "Parameter Validation Failed"); + }); + + return sp; +} + +DBClientMock alwaysSucceedingDbClient() { + DBClientMock dbServiceMock([&](BSONObj cmd, BSONObj info) { return Status::OK(); }); + + return dbServiceMock; +} + +DBClientMock alwaysFailingDbClient() { + DBClientMock dbServiceMock([&](BSONObj cmd, BSONObj info) { + return Status(ErrorCodes::UnknownError, "DB Client Update Failed"); + }); + + return dbServiceMock; +} + +// Tests +TEST(SetClusterParameterCommand, SucceedsWithObjectParameter) { + + MockServerParameter sp = alwaysValidatingServerParameter("Succeeds"_sd); + DBClientMock dbServiceMock = alwaysSucceedingDbClient(); + + auto serviceCtx = ServiceContext::make(); + auto client = serviceCtx->makeClient("SomeTest"); + + auto mpsPtr = std::make_unique<MockParameterService>([&](StringData s) { return &sp; }); + + Client* clientPtr = client.get(); + + BSONObjBuilder testCmdBson; + + BSONObj subObj = BSON("ok" + << "hello_there"); + testCmdBson.append("testCmd"_sd, subObj); + + BSONObj obj = testCmdBson.obj(); + + SetClusterParameterInvocation fixture(std::move(mpsPtr), dbServiceMock); + + OperationContext spyCtx(clientPtr, 1234); + spyCtx.setWriteConcern(WriteConcernOptions::deserializerForIDL(BSON("w" + << "majority"))); + SetClusterParameter testCmd(obj); + + fixture.invoke(&spyCtx, testCmd); +} + +TEST(SetClusterParameterCommand, ThrowsWithNonObjectParameter) { + + MockServerParameter sp = alwaysValidatingServerParameter("Succeeds"_sd); + DBClientMock dbServiceMock = alwaysSucceedingDbClient(); + + auto serviceCtx = ServiceContext::make(); + auto client = serviceCtx->makeClient("SomeTest"); + + auto mpsPtr = std::make_unique<MockParameterService>([&](StringData s) { return &sp; }); + + Client* clientPtr = client.get(); + + BSONObjBuilder testCmdBson; + testCmdBson << "testCommand" << 5; + + BSONObj obj = testCmdBson.obj(); + + SetClusterParameterInvocation fixture(std::move(mpsPtr), dbServiceMock); + + OperationContext spyCtx(clientPtr, 1234); + SetClusterParameter testCmd(obj); + + ASSERT_THROWS_CODE(fixture.invoke(&spyCtx, testCmd), DBException, 6432602); +} + +TEST(SetClusterParameterCommand, ThrowsWhenServerParameterValidationFails) { + + MockServerParameter sp = + alwaysInvalidatingServerParameter("CommandFailsWhenServerParameterValidationFails"_sd); + DBClientMock dbServiceMock = alwaysSucceedingDbClient(); + + auto serviceCtx = ServiceContext::make(); + auto client = serviceCtx->makeClient("SomeTest"); + + auto mpsPtr = std::make_unique<MockParameterService>([&](StringData s) { return &sp; }); + + Client* clientPtr = client.get(); + + BSONObjBuilder testCmdBson; + testCmdBson << "testCommand" + << BSON("ok" + << "someval"); + + BSONObj obj = testCmdBson.obj(); + + SetClusterParameterInvocation fixture(std::move(mpsPtr), dbServiceMock); + + OperationContext spyCtx(clientPtr, 1234); + SetClusterParameter testCmd(obj); + + ASSERT_THROWS_CODE_AND_WHAT(fixture.invoke(&spyCtx, testCmd), + DBException, + ErrorCodes::BadValue, + "Parameter Validation Failed"_sd); +} + +TEST(SetClusterParameterCommand, ThrowsWhenDBUpdateFails) { + + MockServerParameter sp = alwaysValidatingServerParameter("CommandFailsWhenDBUpdateFails"_sd); + DBClientMock dbServiceMock = alwaysFailingDbClient(); + + auto serviceCtx = ServiceContext::make(); + auto client = serviceCtx->makeClient("SomeTest"); + + auto mpsPtr = std::make_unique<MockParameterService>([&](StringData s) { return &sp; }); + + Client* clientPtr = client.get(); + + BSONObjBuilder testCmdBson; + testCmdBson << "testCommand" + << BSON("ok" + << "someval"); + + BSONObj obj = testCmdBson.obj(); + + SetClusterParameterInvocation fixture(std::move(mpsPtr), dbServiceMock); + + OperationContext spyCtx(clientPtr, 1234); + + SetClusterParameter testCmd(obj); + + ASSERT_THROWS_WHAT(fixture.invoke(&spyCtx, testCmd), DBException, "DB Client Update Failed"_sd); +} + +TEST(SetClusterParameterCommand, ThrowsWhenParameterNotPresent) { + + DBClientMock dbServiceMock = alwaysSucceedingDbClient(); + + auto serviceCtx = ServiceContext::make(); + auto client = serviceCtx->makeClient("SomeTest"); + + auto mpsPtr = std::make_unique<MockParameterService>([&](StringData s) { return nullptr; }); + + Client* clientPtr = client.get(); + + BSONObjBuilder testCmdBson; + testCmdBson << "testCommand" + << BSON("ok" + << "someval"); + + BSONObj obj = testCmdBson.obj(); + + SetClusterParameterInvocation fixture(std::move(mpsPtr), dbServiceMock); + + OperationContext spyCtx(clientPtr, 1234); + + SetClusterParameter testCmd(obj); + + ASSERT_THROWS_CODE(fixture.invoke(&spyCtx, testCmd), DBException, 6432601); +} +} // namespace +} // namespace mongo |