Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions include/envoy/server/bootstrap_extension_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace Server {
class BootstrapExtension {
public:
virtual ~BootstrapExtension() = default;

/**
* Called when server is done initializing and we have the ServerFactoryContext available.
* @param context general filter context through which persistent resources can be accessed.
*/
virtual void serverInitialized(Configuration::ServerFactoryContext& context) PURE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name this onServerInitialized. The context can be still passed by constructor, because they are pointing to the same instance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the Context is not fully initialized in the ctor, and I think it is bug prone (i.e. this bug is a good example) to do that. specifically, clusterManager() method in the context returns a reference variable is nullptr; i.e. the user wouldn't even think to test it for nullptr as it is a reference.
To make it harder to write a bug, i think it is worth passing the bare minimum in the ctor (hence my change).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that limits too much in createBootstrapExtension's access, there are more things should be made available for extension to be used, such as dispatcher/singletonManager/timeSource/Api etc. We can't just limit it to ValidationVisitor.

};

using BootstrapExtensionPtr = std::unique_ptr<BootstrapExtension>;
Expand All @@ -34,10 +40,11 @@ class BootstrapExtensionFactory : public Config::TypedFactory {
* implementation is unable to produce a factory with the provided parameters, it should throw an
* EnvoyException. The returned pointer should never be nullptr.
* @param config the custom configuration for this bootstrap extension type.
* @param context general filter context through which persistent resources can be accessed.
* @param validation_visitor message validation visitor instance.
*/
virtual BootstrapExtensionPtr createBootstrapExtension(const Protobuf::Message& config,
ServerFactoryContext& context) PURE;
virtual BootstrapExtensionPtr
createBootstrapExtension(const Protobuf::Message& config,
ProtobufMessage::ValidationVisitor& validation_visitor) PURE;

std::string category() const override { return "envoy.bootstrap"; }
};
Expand Down
2 changes: 2 additions & 0 deletions source/common/network/socket_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ namespace Network {
class SocketInterfaceExtension : public Server::BootstrapExtension {
public:
SocketInterfaceExtension(SocketInterface& sock_interface) : sock_interface_(sock_interface) {}
// Server::BootstrapExtension
void serverInitialized(Server::Configuration::ServerFactoryContext&) override {}

protected:
SocketInterface& sock_interface_;
Expand Down
2 changes: 1 addition & 1 deletion source/common/network/socket_interface_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ bool SocketInterfaceImpl::ipFamilySupported(int domain) {

Server::BootstrapExtensionPtr
SocketInterfaceImpl::createBootstrapExtension(const Protobuf::Message&,
Server::Configuration::ServerFactoryContext&) {
ProtobufMessage::ValidationVisitor&) {
return std::make_unique<SocketInterfaceExtension>(*this);
}

Expand Down
3 changes: 2 additions & 1 deletion source/common/network/socket_interface_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class SocketInterfaceImpl : public SocketInterfaceBase {
// Server::Configuration::BootstrapExtensionFactory
Server::BootstrapExtensionPtr
createBootstrapExtension(const Protobuf::Message& config,
Server::Configuration::ServerFactoryContext& context) override;
ProtobufMessage::ValidationVisitor& validation_visitor) override;

ProtobufTypes::MessagePtr createEmptyConfigProto() override;
std::string name() const override {
return "envoy.extensions.network.socket_interface.default_socket_interface";
Expand Down
39 changes: 17 additions & 22 deletions source/extensions/bootstrap/wasm/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,18 @@ namespace Extensions {
namespace Bootstrap {
namespace Wasm {

static const std::string INLINE_STRING = "<inline>";
void WasmServiceExtension::serverInitialized(Server::Configuration::ServerFactoryContext& context) {
createWasm(context);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should delay the whole bootstrap extension creation to server initialization. The VM can be still created early, and the bootstrap extension could get this event in later stage and it should make http call there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the advantage of splitting the VM initialization?
I'm asking as i think this will make the implementation significantly more complex - How would a wasm extension know when is it safe to perform an http call? that might need changes in the host sdk, and potentially in the ABI.

}

void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& config,
Server::Configuration::ServerFactoryContext& context,
CreateWasmServiceCallback&& cb) {
void WasmServiceExtension::createWasm(Server::Configuration::ServerFactoryContext& context) {
auto plugin = std::make_shared<Common::Wasm::Plugin>(
config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(),
config.config().vm_config().runtime(),
Common::Wasm::anyToBytes(config.config().configuration()), config.config().fail_open(),
config_.config().name(), config_.config().root_id(), config_.config().vm_config().vm_id(),
config_.config().vm_config().runtime(),
Common::Wasm::anyToBytes(config_.config().configuration()), config_.config().fail_open(),
envoy::config::core::v3::TrafficDirection::UNSPECIFIED, context.localInfo(), nullptr);

bool singleton = config.singleton();
auto callback = [&context, singleton, plugin, cb](Common::Wasm::WasmHandleSharedPtr base_wasm) {
auto callback = [this, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) {
if (!base_wasm) {
if (plugin->fail_open_) {
ENVOY_LOG(error, "Unable to create Wasm service {}", plugin->name_);
Expand All @@ -35,10 +34,11 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con
}
return;
}
if (singleton) {
if (config_.singleton()) {
// Return a Wasm VM which will be stored as a singleton by the Server.
cb(std::make_unique<WasmService>(plugin, Common::Wasm::getOrCreateThreadLocalPlugin(
base_wasm, plugin, context.dispatcher())));
wasm_service_ = std::make_unique<WasmService>(
plugin,
Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, context.dispatcher()));
return;
}
// Per-thread WASM VM.
Expand All @@ -48,11 +48,11 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con
tls_slot->set([base_wasm, plugin](Event::Dispatcher& dispatcher) {
return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher);
});
cb(std::make_unique<WasmService>(plugin, std::move(tls_slot)));
wasm_service_ = std::make_unique<WasmService>(plugin, std::move(tls_slot));
};

if (!Common::Wasm::createWasm(
config.config().vm_config(), plugin, context.scope().createScope(""),
config_.config().vm_config(), plugin, context.scope().createScope(""),
context.clusterManager(), context.initManager(), context.dispatcher(), context.api(),
context.lifecycleNotifier(), remote_data_provider_, std::move(callback))) {
// NB: throw if we get a synchronous configuration failures as this is how such failures are
Expand All @@ -64,17 +64,12 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con

Server::BootstrapExtensionPtr
WasmFactory::createBootstrapExtension(const Protobuf::Message& config,
Server::Configuration::ServerFactoryContext& context) {
ProtobufMessage::ValidationVisitor& validation_visitor) {
auto typed_config =
MessageUtil::downcastAndValidate<const envoy::extensions::wasm::v3::WasmService&>(
config, context.messageValidationContext().staticValidationVisitor());
config, validation_visitor);

auto wasm_service_extension = std::make_unique<WasmServiceExtension>();
createWasm(typed_config, context,
[extension = wasm_service_extension.get()](WasmServicePtr wasm) {
extension->wasm_service_ = std::move(wasm);
});
return wasm_service_extension;
return std::make_unique<WasmServiceExtension>(typed_config);
}

// /**
Expand Down
21 changes: 9 additions & 12 deletions source/extensions/bootstrap/wasm/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,34 @@ class WasmService {
};

using WasmServicePtr = std::unique_ptr<WasmService>;
using CreateWasmServiceCallback = std::function<void(WasmServicePtr)>;

class WasmFactory : public Server::Configuration::BootstrapExtensionFactory,
Logger::Loggable<Logger::Id::wasm> {
class WasmFactory : public Server::Configuration::BootstrapExtensionFactory {
public:
~WasmFactory() override = default;
std::string name() const override { return "envoy.bootstrap.wasm"; }
void createWasm(const envoy::extensions::wasm::v3::WasmService& config,
Server::Configuration::ServerFactoryContext& context,
CreateWasmServiceCallback&& cb);
Server::BootstrapExtensionPtr
createBootstrapExtension(const Protobuf::Message& config,
Server::Configuration::ServerFactoryContext& context) override;
ProtobufMessage::ValidationVisitor& validation_visitor) override;
ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<envoy::extensions::wasm::v3::WasmService>();
}

private:
Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_;
};

class WasmServiceExtension : public Server::BootstrapExtension {
class WasmServiceExtension : public Server::BootstrapExtension, Logger::Loggable<Logger::Id::wasm> {
public:
WasmServiceExtension(const envoy::extensions::wasm::v3::WasmService& config) : config_(config) {}
WasmService& wasmService() {
ASSERT(wasm_service_ != nullptr);
return *wasm_service_;
}
void serverInitialized(Server::Configuration::ServerFactoryContext& context) override;

private:
void createWasm(Server::Configuration::ServerFactoryContext& context);

envoy::extensions::wasm::v3::WasmService config_;
WasmServicePtr wasm_service_;
friend class WasmFactory;
Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_;
};

} // namespace Wasm
Expand Down
13 changes: 10 additions & 3 deletions source/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ InstanceImpl::~InstanceImpl() {
ENVOY_LOG(debug, "destroyed listener manager");
}

Upstream::ClusterManager& InstanceImpl::clusterManager() { return *config_.clusterManager(); }
Upstream::ClusterManager& InstanceImpl::clusterManager() {
ASSERT(config_.clusterManager() != nullptr);
return *config_.clusterManager();
}

void InstanceImpl::drainListeners() {
ENVOY_LOG(info, "closing and draining listeners");
Expand Down Expand Up @@ -428,8 +431,8 @@ void InstanceImpl::initialize(const Options& options,
auto config = Config::Utility::translateAnyToFactoryConfig(
bootstrap_extension.typed_config(), messageValidationContext().staticValidationVisitor(),
factory);
bootstrap_extensions_.push_back(
factory.createBootstrapExtension(*config, serverFactoryContext()));
bootstrap_extensions_.push_back(factory.createBootstrapExtension(
*config, messageValidationContext().staticValidationVisitor()));
}

// Register the fatal actions.
Expand Down Expand Up @@ -534,6 +537,10 @@ void InstanceImpl::initialize(const Options& options,
// cluster_manager_factory_ is available.
config_.initialize(bootstrap_, *this, *cluster_manager_factory_);

for (auto&& bootstrap_extension : bootstrap_extensions_) {
bootstrap_extension->serverInitialized(serverFactoryContext());
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still too early because cluster manager isn't fully initialized at this moment either. move this right before the guard dog initialization?

// Instruct the listener manager to create the LDS provider if needed. This must be done later
// because various items do not yet exist when the listener manager is created.
if (bootstrap_.dynamic_resources().has_lds_config() ||
Expand Down
4 changes: 3 additions & 1 deletion test/extensions/bootstrap/wasm/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class WasmFactoryTest : public testing::TestWithParam<std::string> {
EXPECT_CALL(context_, initManager()).WillRepeatedly(testing::ReturnRef(init_manager_));
EXPECT_CALL(context_, lifecycleNotifier())
.WillRepeatedly(testing::ReturnRef(lifecycle_notifier_));
extension_ = factory->createBootstrapExtension(config, context_);
extension_ = factory->createBootstrapExtension(
config, context_.messageValidationContext().staticValidationVisitor());
extension_->serverInitialized(context_);
static_cast<Bootstrap::Wasm::WasmServiceExtension*>(extension_.get())->wasmService();
EXPECT_CALL(init_watcher_, ready());
init_manager_.initialize(init_watcher_);
Expand Down
5 changes: 5 additions & 0 deletions test/mocks/server/bootstrap_extension_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace Envoy {
namespace Server {

MockBootstrapExtension::MockBootstrapExtension() = default;

MockBootstrapExtension::~MockBootstrapExtension() = default;

namespace Configuration {
MockBootstrapExtensionFactory::MockBootstrapExtensionFactory() = default;

Expand Down
13 changes: 12 additions & 1 deletion test/mocks/server/bootstrap_extension_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@

namespace Envoy {
namespace Server {

class MockBootstrapExtension : public BootstrapExtension {
public:
MockBootstrapExtension();
~MockBootstrapExtension() override;

MOCK_METHOD(void, serverInitialized, (Configuration::ServerFactoryContext & context), (override));
};

namespace Configuration {
class MockBootstrapExtensionFactory : public BootstrapExtensionFactory {
public:
MockBootstrapExtensionFactory();
~MockBootstrapExtensionFactory() override;

MOCK_METHOD(BootstrapExtensionPtr, createBootstrapExtension,
(const Protobuf::Message&, Configuration::ServerFactoryContext&), (override));
(const Protobuf::Message&, ProtobufMessage::ValidationVisitor& validation_visitor),
(override));
MOCK_METHOD(ProtobufTypes::MessagePtr, createEmptyConfigProto, (), (override));
MOCK_METHOD(std::string, name, (), (const, override));
};

} // namespace Configuration
} // namespace Server
} // namespace Envoy
13 changes: 9 additions & 4 deletions test/server/server_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1248,11 +1248,17 @@ TEST_P(ServerInstanceImplTest, WithBootstrapExtensions) {
}));
EXPECT_CALL(mock_factory, name()).WillRepeatedly(Return("envoy_test.bootstrap.foo"));
EXPECT_CALL(mock_factory, createBootstrapExtension(_, _))
.WillOnce(Invoke([](const Protobuf::Message& config, Configuration::ServerFactoryContext&) {
.WillOnce(Invoke([](const Protobuf::Message& config, ProtobufMessage::ValidationVisitor&) {
const auto* proto = dynamic_cast<const test::common::config::DummyConfig*>(&config);
EXPECT_NE(nullptr, proto);
EXPECT_EQ(proto->a(), "foo");
return std::make_unique<FooBootstrapExtension>();
auto mock_extension = std::make_unique<MockBootstrapExtension>();
EXPECT_CALL(*mock_extension, serverInitialized(_))
.WillOnce(Invoke([](Configuration::ServerFactoryContext& ctx) {
// call to cluster manager, to make sure it is not nullptr.
ctx.clusterManager().clusters();
}));
return mock_extension;
}));

Registry::InjectFactory<Configuration::BootstrapExtensionFactory> registered_factory(
Expand All @@ -1268,8 +1274,7 @@ TEST_P(ServerInstanceImplTest, WithBootstrapExtensionsThrowingError) {
}));
EXPECT_CALL(mock_factory, name()).WillRepeatedly(Return("envoy_test.bootstrap.foo"));
EXPECT_CALL(mock_factory, createBootstrapExtension(_, _))
.WillOnce(Invoke([](const Protobuf::Message&,
Configuration::ServerFactoryContext&) -> BootstrapExtensionPtr {
.WillOnce(Invoke([]() -> BootstrapExtensionPtr {
throw EnvoyException("Unable to initiate mock_bootstrap_extension.");
}));

Expand Down