SERVER-109991 Load config files to individual extensions (#41094)
GitOrigin-RevId: 570033339afe3f03f0e978488743b355b0289cce
This commit is contained in:
parent
cb44035fc4
commit
2f9e0a26ac
@ -445,6 +445,9 @@ extensions_with_config(
|
||||
# TODO SERVER-109108: Remove this entry when the bar extension is no longer needed.
|
||||
"//src/mongo/db/extension/test_examples:bar_mongo_extension",
|
||||
"//src/mongo/db/extension/test_examples:foo_mongo_extension",
|
||||
# TODO SERVER-110326: Add these tests when possible.
|
||||
# "//src/mongo/db/extension/test_examples:test_options_mongo_extension",
|
||||
# "//src/mongo/db/extension/test_examples:parse_options_mongo_extension",
|
||||
|
||||
# Any extension that is just loaded in a no-passthrough test MUST NOT have the
|
||||
# "_mongo_extension" suffix.
|
||||
|
||||
@ -69,6 +69,8 @@ mongo_cc_unit_test(
|
||||
"//src/mongo/db/extension/test_examples:duplicate_version_bad_extension",
|
||||
"//src/mongo/db/extension/test_examples:no_compatible_version_bad_extension",
|
||||
"//src/mongo/db/extension/test_examples:loadHighestCompatibleVersion_mongo_extension",
|
||||
"//src/mongo/db/extension/test_examples:test_options_mongo_extension",
|
||||
"//src/mongo/db/extension/test_examples:parse_options_mongo_extension",
|
||||
],
|
||||
tags = ["mongo_unittest_seventh_group"],
|
||||
target_compatible_with = select({
|
||||
|
||||
@ -47,4 +47,9 @@ void registerStageDescriptor(const ::MongoExtensionAggregationStageDescriptor* d
|
||||
return sdk::enterCXX([&]() { return registerStageDescriptor(stageDesc); });
|
||||
}
|
||||
|
||||
::MongoExtensionByteView HostPortal::_extGetOptions(
|
||||
const ::MongoExtensionHostPortal* portal) noexcept {
|
||||
return sdk::stringViewAsByteView(static_cast<const HostPortal*>(portal)->_extensionOpts);
|
||||
}
|
||||
|
||||
} // namespace mongo::extension::host
|
||||
|
||||
@ -30,20 +30,31 @@
|
||||
|
||||
#include "mongo/db/extension/public/api.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mongo::extension::host {
|
||||
|
||||
void registerStageDescriptor(const ::MongoExtensionAggregationStageDescriptor* descriptor);
|
||||
|
||||
class HostPortal final : public ::MongoExtensionHostPortal {
|
||||
public:
|
||||
HostPortal(::MongoExtensionAPIVersion apiVersion, int maxWireVersion)
|
||||
: ::MongoExtensionHostPortal(&VTABLE, apiVersion, maxWireVersion) {}
|
||||
HostPortal(::MongoExtensionAPIVersion apiVersion,
|
||||
int maxWireVersion,
|
||||
std::string extensionOptions)
|
||||
: ::MongoExtensionHostPortal{&VTABLE, apiVersion, maxWireVersion},
|
||||
_extensionOpts(std::move(extensionOptions)) {}
|
||||
|
||||
private:
|
||||
static ::MongoExtensionStatus* _extRegisterStageDescriptor(
|
||||
const MongoExtensionAggregationStageDescriptor* stageDesc) noexcept;
|
||||
|
||||
static constexpr ::MongoExtensionHostPortalVTable VTABLE{&_extRegisterStageDescriptor};
|
||||
static ::MongoExtensionByteView _extGetOptions(
|
||||
const ::MongoExtensionHostPortal* portal) noexcept;
|
||||
|
||||
static constexpr ::MongoExtensionHostPortalVTable VTABLE{&_extRegisterStageDescriptor,
|
||||
&_extGetOptions};
|
||||
|
||||
const std::string _extensionOpts;
|
||||
};
|
||||
|
||||
} // namespace mongo::extension::host
|
||||
|
||||
@ -183,7 +183,7 @@ ExtensionConfig ExtensionLoader::loadExtensionConfig(const std::string& extensio
|
||||
LOGV2(11042903,
|
||||
"Successfully loaded config file",
|
||||
"sharedLibraryPath"_attr = config.sharedLibraryPath,
|
||||
// TODO SERVER-109991: Remove 'extensionOptions' from log.
|
||||
// TODO SERVER-110474: Remove or modify 'extensionOptions' logging.
|
||||
"extensionOptions"_attr = YAML::Dump(config.extOptions));
|
||||
|
||||
return config;
|
||||
@ -220,8 +220,7 @@ void ExtensionLoader::load(const ExtensionConfig& config) {
|
||||
.getIncomingInternalClient()
|
||||
.maxWireVersion);
|
||||
|
||||
// TODO SERVER-109991: Pass 'config.extOptions' to HostPortal.
|
||||
HostPortal portal{extHandle.getVersion(), maxWireVersion};
|
||||
HostPortal portal{extHandle.getVersion(), maxWireVersion, YAML::Dump(config.extOptions)};
|
||||
extHandle.initialize(portal);
|
||||
}
|
||||
} // namespace mongo::extension::host
|
||||
|
||||
@ -50,10 +50,8 @@ static std::filesystem::path getExtensionPath(const std::string& extensionName)
|
||||
}
|
||||
|
||||
static ExtensionConfig makeEmptyExtensionConfig(const std::string& extensionName) {
|
||||
ExtensionConfig config;
|
||||
config.sharedLibraryPath = getExtensionPath(extensionName).string();
|
||||
config.extOptions = YAML::Node(YAML::NodeType::Map);
|
||||
return config;
|
||||
return ExtensionConfig{.sharedLibraryPath = getExtensionPath(extensionName).string(),
|
||||
.extOptions = YAML::Node(YAML::NodeType::Map)};
|
||||
}
|
||||
|
||||
class LoadExtensionsTest : public unittest::Test {
|
||||
@ -273,4 +271,54 @@ TEST(LoadExtensionTest, LoadHighestCompatibleVersionSucceeds) {
|
||||
pipeline = {BSON("$extensionV4" << BSONObj())};
|
||||
ASSERT_THROWS_CODE(Pipeline::parse(pipeline, expCtx), AssertionException, 16436);
|
||||
}
|
||||
|
||||
TEST_F(LoadExtensionsTest, LoadExtensionBothOptionsSucceed) {
|
||||
{
|
||||
const auto extOptions = YAML::Load("optionA: true\n");
|
||||
const ExtensionConfig config = {
|
||||
.sharedLibraryPath = getExtensionPath("libtest_options_mongo_extension.so").string(),
|
||||
.extOptions = extOptions};
|
||||
ASSERT_DOES_NOT_THROW(ExtensionLoader::load(config));
|
||||
auto expCtx = make_intrusive<ExpressionContextForTest>();
|
||||
|
||||
std::vector<BSONObj> pipeline = {BSON("$optionA" << BSONObj())};
|
||||
auto parsedPipeline = Pipeline::parse(pipeline, expCtx);
|
||||
ASSERT_TRUE(parsedPipeline != nullptr);
|
||||
ASSERT_EQUALS(parsedPipeline->getSources().size(), 1U);
|
||||
|
||||
auto stage =
|
||||
dynamic_cast<DocumentSourceExtension*>(parsedPipeline->getSources().front().get());
|
||||
ASSERT_TRUE(stage != nullptr);
|
||||
ASSERT_EQUALS(std::string(stage->getSourceName()), "$optionA");
|
||||
|
||||
// Assert that $optionB is unavailable.
|
||||
pipeline = {BSON("$optionB" << BSONObj())};
|
||||
ASSERT_THROWS_CODE(Pipeline::parse(pipeline, expCtx), AssertionException, 16436);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LoadExtensionsTest, LoadExtensionParseWithExtensionOptions) {
|
||||
{
|
||||
const auto extOptions = YAML::Load("checkMax: true\nmax: 10");
|
||||
const ExtensionConfig config = {
|
||||
.sharedLibraryPath = getExtensionPath("libparse_options_mongo_extension.so").string(),
|
||||
.extOptions = extOptions};
|
||||
ASSERT_DOES_NOT_THROW(ExtensionLoader::load(config));
|
||||
auto expCtx = make_intrusive<ExpressionContextForTest>();
|
||||
|
||||
std::vector<BSONObj> pipeline = {BSON("$checkNum" << BSON("num" << 9))};
|
||||
auto parsedPipeline = Pipeline::parse(pipeline, expCtx);
|
||||
ASSERT_TRUE(parsedPipeline != nullptr);
|
||||
ASSERT_EQUALS(parsedPipeline->getSources().size(), 1U);
|
||||
|
||||
auto stage =
|
||||
dynamic_cast<DocumentSourceExtension*>(parsedPipeline->getSources().front().get());
|
||||
ASSERT_TRUE(stage != nullptr);
|
||||
ASSERT_EQUALS(std::string(stage->getSourceName()), "$checkNum");
|
||||
|
||||
// Assert that parsing fails when the provided num is greater than max 10.
|
||||
pipeline = {BSON("$checkNum" << BSON("num" << 11))};
|
||||
ASSERT_THROWS_CODE(Pipeline::parse(pipeline, expCtx), AssertionException, 10999106);
|
||||
}
|
||||
}
|
||||
} // namespace mongo::extension::host
|
||||
|
||||
@ -238,6 +238,9 @@ typedef struct MongoExtensionHostPortal {
|
||||
typedef struct MongoExtensionHostPortalVTable {
|
||||
MongoExtensionStatus* (*registerStageDescriptor)(
|
||||
const MongoExtensionAggregationStageDescriptor* descriptor);
|
||||
// Returns a MongoExtensionByteView containing the raw extension options associated with this
|
||||
// extension.
|
||||
MongoExtensionByteView (*getExtensionOptions)(const MongoExtensionHostPortal* portal);
|
||||
} MongoExtensionHostPortalVTable;
|
||||
|
||||
/**
|
||||
|
||||
@ -24,5 +24,6 @@ mongo_cc_library(
|
||||
deps = [
|
||||
"//src/mongo:base",
|
||||
"//src/mongo/db/extension/public:api",
|
||||
"//src/third_party/yaml-cpp:yaml",
|
||||
],
|
||||
)
|
||||
|
||||
@ -32,6 +32,8 @@
|
||||
#include "mongo/db/extension/sdk/aggregation_stage.h"
|
||||
#include "mongo/db/extension/sdk/extension_status.h"
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace mongo::extension::sdk {
|
||||
|
||||
/**
|
||||
@ -62,11 +64,19 @@ public:
|
||||
return get()->hostMongoDBMaxWireVersion;
|
||||
}
|
||||
|
||||
YAML::Node getExtensionOptions() const {
|
||||
assertValid();
|
||||
return YAML::Load(std::string(byteViewAsStringView(vtable().getExtensionOptions(get()))));
|
||||
}
|
||||
|
||||
private:
|
||||
void _assertVTableConstraints(const VTable_t& vtable) const override {
|
||||
tassert(10926401,
|
||||
"Extension 'registerStageDescriptor' is null",
|
||||
vtable.registerStageDescriptor != nullptr);
|
||||
tassert(10999108,
|
||||
"Extension 'getExtensionOptions' is null",
|
||||
vtable.getExtensionOptions != nullptr);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -33,6 +33,18 @@ mongo_cc_extension_shared_library(
|
||||
srcs = ["vector_search.cpp"],
|
||||
)
|
||||
|
||||
# Extensions under test_examples/extension_options/
|
||||
[
|
||||
mongo_cc_extension_shared_library(
|
||||
name = extension_name + "_mongo_extension",
|
||||
srcs = ["extension_options/" + extension_name + ".cpp"],
|
||||
)
|
||||
for extension_name in [
|
||||
"test_options",
|
||||
"parse_options",
|
||||
]
|
||||
]
|
||||
|
||||
# Extensions under test_examples/loading/
|
||||
[
|
||||
mongo_cc_extension_shared_library(
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (C) 2025-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/bson/bsonobj.h"
|
||||
#include "mongo/db/extension/sdk/aggregation_stage.h"
|
||||
#include "mongo/db/extension/sdk/extension_factory.h"
|
||||
|
||||
namespace sdk = mongo::extension::sdk;
|
||||
|
||||
struct ExtensionOptions {
|
||||
inline static bool checkMax = false;
|
||||
inline static double max = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* $checkNum is a no-op stage.
|
||||
*
|
||||
* The stage definition must include a "num" field, like {$checkNum: {num: <double>}}, or it will
|
||||
* fail to parse. If 'checkMax' is true and the supplied num is greater than 'max', it will fail to
|
||||
* parse.
|
||||
*/
|
||||
class CheckNumLogicalStage : public sdk::LogicalAggregationStage {};
|
||||
class CheckNumStageDescriptor : public sdk::AggregationStageDescriptor {
|
||||
public:
|
||||
static inline const std::string kStageName = "$checkNum";
|
||||
CheckNumStageDescriptor()
|
||||
: sdk::AggregationStageDescriptor(kStageName, MongoExtensionAggregationStageType::kNoOp) {}
|
||||
|
||||
std::unique_ptr<sdk::LogicalAggregationStage> parse(mongo::BSONObj stageBson) const override {
|
||||
uassert(10999104,
|
||||
"Failed to parse " + kStageName + ", expected an object for $checkNum",
|
||||
stageBson.hasField(kStageName) && stageBson.getField(kStageName).isABSONObj());
|
||||
|
||||
const auto obj = stageBson.getField(kStageName).Obj();
|
||||
uassert(10999105,
|
||||
"Failed to parse " + kStageName + ", expected {" + kStageName +
|
||||
": {num: <double>}}",
|
||||
obj.hasField("num") && obj.getField("num").isNumber());
|
||||
|
||||
if (ExtensionOptions::checkMax) {
|
||||
uassert(10999106,
|
||||
"Failed to parse " + kStageName + ", provided num is higher than max " +
|
||||
std::to_string(ExtensionOptions::max),
|
||||
obj.getField("num").numberDouble() <= ExtensionOptions::max);
|
||||
}
|
||||
|
||||
return std::make_unique<CheckNumLogicalStage>();
|
||||
}
|
||||
};
|
||||
|
||||
class MyExtension : public sdk::Extension {
|
||||
public:
|
||||
void initialize(const sdk::HostPortalHandle& portal) override {
|
||||
YAML::Node node = portal.getExtensionOptions();
|
||||
uassert(10999107, "Extension options must include 'checkMax'", node["checkMax"]);
|
||||
ExtensionOptions::checkMax = node["checkMax"].as<bool>();
|
||||
if (ExtensionOptions::checkMax) {
|
||||
uassert(10999103, "Extension options must include 'max'", node["max"]);
|
||||
ExtensionOptions::max = node["max"].as<double>();
|
||||
}
|
||||
_registerStage<CheckNumStageDescriptor>(portal);
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_EXTENSION(MyExtension)
|
||||
DEFINE_GET_EXTENSION()
|
||||
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (C) 2025-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/bson/bsonobj.h"
|
||||
#include "mongo/db/extension/sdk/aggregation_stage.h"
|
||||
#include "mongo/db/extension/sdk/extension_factory.h"
|
||||
|
||||
namespace sdk = mongo::extension::sdk;
|
||||
|
||||
struct ExtensionOptions {
|
||||
inline static bool optionA = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* $optionA is a no-op stage.
|
||||
*
|
||||
* The stage definition must be empty, like {$optionA: {}}, or it will fail to parse.
|
||||
*/
|
||||
class OptionALogicalStage : public sdk::LogicalAggregationStage {};
|
||||
|
||||
class OptionAStageDescriptor : public sdk::AggregationStageDescriptor {
|
||||
public:
|
||||
static inline const std::string kStageName = "$optionA";
|
||||
|
||||
OptionAStageDescriptor()
|
||||
: sdk::AggregationStageDescriptor(kStageName, MongoExtensionAggregationStageType::kNoOp) {}
|
||||
|
||||
std::unique_ptr<sdk::LogicalAggregationStage> parse(mongo::BSONObj stageBson) const override {
|
||||
uassert(10999101,
|
||||
"Failed to parse " + kStageName + ", expected object",
|
||||
stageBson.hasField(kStageName) && stageBson.getField(kStageName).isABSONObj() &&
|
||||
stageBson.getField(kStageName).Obj().isEmpty());
|
||||
|
||||
return std::make_unique<OptionALogicalStage>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* $optionB is a no-op stage.
|
||||
*
|
||||
* The stage definition must be empty, like {$optionB: {}}, or it will fail to parse.
|
||||
*/
|
||||
class OptionBLogicalStage : public sdk::LogicalAggregationStage {};
|
||||
|
||||
class OptionBStageDescriptor : public sdk::AggregationStageDescriptor {
|
||||
public:
|
||||
static inline const std::string kStageName = "$optionB";
|
||||
|
||||
OptionBStageDescriptor()
|
||||
: sdk::AggregationStageDescriptor(kStageName, MongoExtensionAggregationStageType::kNoOp) {}
|
||||
|
||||
std::unique_ptr<sdk::LogicalAggregationStage> parse(mongo::BSONObj stageBson) const override {
|
||||
uassert(10999102,
|
||||
"Failed to parse " + kStageName + ", expected object",
|
||||
stageBson.hasField(kStageName) && stageBson.getField(kStageName).isABSONObj() &&
|
||||
stageBson.getField(kStageName).Obj().isEmpty());
|
||||
|
||||
return std::make_unique<OptionBLogicalStage>();
|
||||
}
|
||||
};
|
||||
|
||||
class MyExtension : public sdk::Extension {
|
||||
public:
|
||||
void initialize(const sdk::HostPortalHandle& portal) override {
|
||||
YAML::Node node = portal.getExtensionOptions();
|
||||
uassert(10999100, "Extension options must include 'optionA'", node["optionA"]);
|
||||
ExtensionOptions::optionA = node["optionA"].as<bool>();
|
||||
|
||||
if (ExtensionOptions::optionA) {
|
||||
_registerStage<OptionAStageDescriptor>(portal);
|
||||
} else {
|
||||
_registerStage<OptionBStageDescriptor>(portal);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_EXTENSION(MyExtension)
|
||||
DEFINE_GET_EXTENSION()
|
||||
Loading…
Reference in New Issue
Block a user