-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bootstrap-extensions: fix a crash on http callout #14478
Changes from 10 commits
21c5e5f
598c890
02e594a
13e92c5
87620b8
dbe9055
919e67f
039041a
37e53ac
4491188
d6264ad
d181b02
2b39bbc
b95ecaf
2354ea2
b96f116
fddcd70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,19 +14,19 @@ namespace Extensions { | |
namespace Bootstrap { | ||
namespace Wasm { | ||
|
||
static const std::string INLINE_STRING = "<inline>"; | ||
void WasmServiceExtension::onServerInitialized( | ||
Server::Configuration::ServerFactoryContext& context) { | ||
createWasm(context); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the advantage of splitting the VM initialization? |
||
} | ||
|
||
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_); | ||
|
@@ -35,10 +35,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. | ||
|
@@ -48,11 +49,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 | ||
|
@@ -69,12 +70,7 @@ WasmFactory::createBootstrapExtension(const Protobuf::Message& config, | |
MessageUtil::downcastAndValidate<const envoy::extensions::wasm::v3::WasmService&>( | ||
config, context.messageValidationContext().staticValidationVisitor()); | ||
|
||
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); | ||
} | ||
|
||
// /** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// NOLINT(namespace-envoy) | ||
#include <string> | ||
|
||
#include "proxy_wasm_intrinsics.h" | ||
|
||
template <typename T> std::unique_ptr<T> wrap_unique(T* ptr) { return std::unique_ptr<T>(ptr); } | ||
|
||
START_WASM_PLUGIN(WasmHttpCpp) | ||
|
||
// Required Proxy-Wasm ABI version. | ||
WASM_EXPORT(void, proxy_abi_version_0_1_0, ()) {} | ||
|
||
WASM_EXPORT(uint32_t, proxy_on_configure, (uint32_t, uint32_t)) { | ||
proxy_set_tick_period_milliseconds(100); | ||
return 1; | ||
} | ||
|
||
WASM_EXPORT(void, proxy_on_tick, (uint32_t)) { | ||
HeaderStringPairs headers; | ||
headers.push_back(std::make_pair<std::string, std::string>(":method", "GET")); | ||
headers.push_back(std::make_pair<std::string, std::string>(":path", "/")); | ||
headers.push_back(std::make_pair<std::string, std::string>(":authority", "example.com")); | ||
HeaderStringPairs trailers; | ||
uint32_t token; | ||
WasmResult result = makeHttpCall("wasm_cluster", headers, "", trailers, 10000, &token); | ||
// We have sent successfully, stop timer - we only want to send one request. | ||
if (result == WasmResult::Ok) { | ||
proxy_set_tick_period_milliseconds(0); | ||
} | ||
} | ||
|
||
WASM_EXPORT(void, proxy_on_http_call_response, (uint32_t, uint32_t, uint32_t headers, uint32_t, uint32_t)) { | ||
if (headers != 0) { | ||
auto status = getHeaderMapValue(WasmHeaderMapType::HttpCallResponseHeaders, "status"); | ||
if ("200" == status->view()) { | ||
proxy_set_tick_period_milliseconds(0); | ||
return; | ||
} | ||
} | ||
// Request failed - very possibly because of the integration test not being ready. | ||
// Try again to prevent flakes. | ||
proxy_set_tick_period_milliseconds(100); | ||
} | ||
|
||
END_WASM_PLUGIN |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#include "extensions/common/wasm/wasm.h" | ||
|
||
#include "test/extensions/common/wasm/wasm_runtime.h" | ||
#include "test/integration/http_protocol_integration.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Wasm { | ||
namespace { | ||
|
||
class WasmIntegrationTest : public HttpIntegrationTest, public testing::TestWithParam<std::string> { | ||
public: | ||
WasmIntegrationTest() | ||
: HttpIntegrationTest(Http::CodecClient::Type::HTTP1, Network::Address::IpVersion::v4) {} | ||
|
||
void createUpstreams() override { | ||
HttpIntegrationTest::createUpstreams(); | ||
addFakeUpstream(FakeHttpConnection::Type::HTTP1); | ||
} | ||
|
||
void cleanup() { | ||
if (wasm_connection_ != nullptr) { | ||
ASSERT_TRUE(wasm_connection_->close()); | ||
ASSERT_TRUE(wasm_connection_->waitForDisconnect()); | ||
} | ||
cleanupUpstreamAndDownstream(); | ||
} | ||
void initialize() override { | ||
auto httpwasm = TestEnvironment::substitute( | ||
"{{ test_rundir }}/test/extensions/bootstrap/wasm/test_data/http_cpp.wasm"); | ||
config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { | ||
auto* wasm = bootstrap.mutable_static_resources()->add_clusters(); | ||
wasm->MergeFrom(bootstrap.static_resources().clusters()[0]); | ||
wasm->set_name("wasm_cluster"); | ||
}); | ||
|
||
config_helper_.addBootstrapExtension(fmt::format(R"EOF( | ||
name: envoy.filters.http.wasm | ||
typed_config: | ||
'@type': type.googleapis.com/envoy.extensions.wasm.v3.WasmService | ||
singleton: true | ||
config: | ||
name: "singleton" | ||
root_id: "singleton" | ||
configuration: | ||
'@type': type.googleapis.com/google.protobuf.StringValue | ||
value: "" | ||
vm_config: | ||
vm_id: "my_vm_id" | ||
runtime: "envoy.wasm.runtime.{}" | ||
code: | ||
local: | ||
filename: {} | ||
)EOF", | ||
GetParam(), httpwasm)); | ||
HttpIntegrationTest::initialize(); | ||
} | ||
|
||
FakeHttpConnectionPtr wasm_connection_; | ||
FakeStreamPtr wasm_request_; | ||
IntegrationStreamDecoderPtr response_; | ||
}; | ||
|
||
INSTANTIATE_TEST_SUITE_P(Runtimes, WasmIntegrationTest, | ||
Envoy::Extensions::Common::Wasm::sandbox_runtime_values, | ||
Envoy::Extensions::Common::Wasm::wasmTestParamsToString); | ||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmIntegrationTest); | ||
|
||
TEST_P(WasmIntegrationTest, FilterMakesCallInConfigureTime) { | ||
initialize(); | ||
ASSERT_TRUE(fake_upstreams_.back()->waitForHttpConnection(*dispatcher_, wasm_connection_)); | ||
|
||
// Expect the filter to send us an HTTP request | ||
ASSERT_TRUE(wasm_connection_->waitForNewStream(*dispatcher_, wasm_request_)); | ||
|
||
ASSERT_TRUE(wasm_request_->waitForEndStream(*dispatcher_)); | ||
|
||
// Respond back to the filter. | ||
Http::TestResponseHeaderMapImpl response_headers{ | ||
{":status", "200"}, | ||
}; | ||
wasm_request_->encodeHeaders(response_headers, true); | ||
cleanup(); | ||
} | ||
|
||
} // namespace | ||
} // namespace Wasm | ||
} // namespace Extensions | ||
} // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove the parameter from this method, as the context will be same reference passed into createBootstrapExtension.