diff --git a/bazel/wasm/wasm.bzl b/bazel/wasm/wasm.bzl index 1987a97d53a8d..6b3594c823a1a 100644 --- a/bazel/wasm/wasm.bzl +++ b/bazel/wasm/wasm.bzl @@ -61,10 +61,21 @@ wasi_rust_binary_rule = rule( attrs = _wasm_attrs(wasi_rust_transition), ) -def envoy_wasm_cc_binary(name, additional_linker_inputs = [], linkopts = [], tags = [], **kwargs): +wasm_cc_binary_rule = rule( + implementation = _wasm_binary_impl, + attrs = { + "binary": attr.label(mandatory = True), + "precompile": attr.bool(default = False), + # This is deliberately in target configuration to avoid compiling v8 twice. + "_compile_tool": attr.label(default = Label("//test/tools/wee8_compile:wee8_compile_tool"), executable = True, cfg = "target"), + }, +) + +def envoy_wasm_cc_binary(name, additional_linker_inputs = [], linkopts = [], tags = [], precompile = False, **kwargs): + wasm_name = "_wasm_" + name.replace(".", "_") api_js = Label("//source/extensions/common/wasm/ext:envoy_proxy_wasm_api_js") proxy_wasm_cc_binary( - name = name, + name = wasm_name, additional_linker_inputs = additional_linker_inputs + [ api_js, ], @@ -75,6 +86,16 @@ def envoy_wasm_cc_binary(name, additional_linker_inputs = [], linkopts = [], tag **kwargs ) + wasm_kwargs = { + "name": name, + "binary": ":" + wasm_name, + "precompile": precompile, + "tags": tags + ["manual"], + } + if "visibility" in kwargs: + wasm_kwargs["visibility"] = kwargs["visibility"] + wasm_cc_binary_rule(**wasm_kwargs) + def wasm_rust_binary(name, tags = [], wasi = False, precompile = False, **kwargs): wasm_name = "_wasm_" + name.replace(".", "_") kwargs.setdefault("visibility", ["//visibility:public"]) diff --git a/test/extensions/access_loggers/wasm/config_test.cc b/test/extensions/access_loggers/wasm/config_test.cc index 7c29cdae9d658..500beaa6faf03 100644 --- a/test/extensions/access_loggers/wasm/config_test.cc +++ b/test/extensions/access_loggers/wasm/config_test.cc @@ -72,7 +72,7 @@ class WasmAccessLogConfigTest }; INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, - Envoy::Extensions::Common::Wasm::runtime_and_cpp_values, + Envoy::Extensions::Common::Wasm::single_sandbox_runtime_and_cpp_values, Envoy::Extensions::Common::Wasm::wasmTestParamsToString); TEST_P(WasmAccessLogConfigTest, CreateWasmFromEmpty) { @@ -99,6 +99,7 @@ TEST_P(WasmAccessLogConfigTest, CreateWasmFromWASM) { envoy::extensions::access_loggers::wasm::v3::WasmAccessLog config; config.mutable_config()->mutable_vm_config()->set_runtime( absl::StrCat("envoy.wasm.runtime.", std::get<0>(GetParam()))); + config.mutable_config()->mutable_vm_config()->set_allow_precompiled(true); std::string code; if (std::get<0>(GetParam()) != "null") { code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -145,6 +146,7 @@ TEST_P(WasmAccessLogConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -167,6 +169,7 @@ TEST_P(WasmAccessLogConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -197,6 +200,7 @@ TEST_P(WasmAccessLogConfigTest, YamlLoadFromRemoteWasmCreateFilter) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -251,6 +255,7 @@ TEST_P(WasmAccessLogConfigTest, FailedToGetThreadLocalPlugin) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: diff --git a/test/extensions/access_loggers/wasm/test_data/BUILD b/test/extensions/access_loggers/wasm/test_data/BUILD index eaf2a849af807..97fbf441a84de 100644 --- a/test/extensions/access_loggers/wasm/test_data/BUILD +++ b/test/extensions/access_loggers/wasm/test_data/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test_library", "envoy_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_v8_bool") load("//bazel/wasm:wasm.bzl", "envoy_wasm_cc_binary") licenses(["notice"]) # Apache 2 @@ -28,4 +29,5 @@ envoy_cc_test_library( envoy_wasm_cc_binary( name = "test_cpp.wasm", srcs = ["test_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), ) diff --git a/test/extensions/bootstrap/wasm/config_test.cc b/test/extensions/bootstrap/wasm/config_test.cc index 2f809badb65ee..46faf9923a536 100644 --- a/test/extensions/bootstrap/wasm/config_test.cc +++ b/test/extensions/bootstrap/wasm/config_test.cc @@ -22,6 +22,7 @@ class WasmFactoryTest : public testing::TestWithParammutable_vm_config()->set_runtime( absl::StrCat("envoy.wasm.runtime.", std::get<0>(GetParam()))); + config_.mutable_config()->mutable_vm_config()->set_allow_precompiled(true); if (std::get<0>(GetParam()) != "null") { config_.mutable_config()->mutable_vm_config()->mutable_code()->mutable_local()->set_filename( TestEnvironment::substitute( @@ -66,7 +67,7 @@ class WasmFactoryTest : public testing::TestWithParammutable_runtime() = absl::StrCat("envoy.wasm.runtime.", runtime); *plugin_config.mutable_vm_config()->mutable_vm_id() = vm_id_; + plugin_config.mutable_vm_config()->set_allow_precompiled(true); plugin_config.mutable_vm_config()->mutable_configuration()->set_value(vm_configuration_); plugin_config.mutable_configuration()->set_value(plugin_configuration_); plugin_ = std::make_shared(plugin_config, local_info_); @@ -91,7 +92,7 @@ class WasmTest : public WasmTestBase, }; INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, - Envoy::Extensions::Common::Wasm::sandbox_runtime_and_cpp_values, + Envoy::Extensions::Common::Wasm::first_sandbox_runtime_and_cpp_values, Envoy::Extensions::Common::Wasm::wasmTestParamsToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTest); @@ -112,7 +113,7 @@ class WasmNullTest : public WasmTestBase, }; INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNullTest, - Envoy::Extensions::Common::Wasm::runtime_and_cpp_values); + Envoy::Extensions::Common::Wasm::single_sandbox_runtime_and_cpp_values); class WasmTestMatrix : public WasmTestBase, public testing::TestWithParam> { @@ -132,8 +133,9 @@ class WasmTestMatrix : public WasmTestBase, std::string code_; }; -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, - Envoy::Extensions::Common::Wasm::sandbox_runtime_and_language_values); +INSTANTIATE_TEST_SUITE_P( + RuntimesAndLanguages, WasmTestMatrix, + Envoy::Extensions::Common::Wasm::single_sandbox_runtime_and_language_values); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTestMatrix); TEST_P(WasmTestMatrix, LoggingWithEnvVars) { diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 292e22808ddd2..8531da56d112c 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -40,6 +40,7 @@ envoy_cc_test( "//test/extensions/common/wasm/test_data:bad_signature_cpp.wasm", "//test/extensions/common/wasm/test_data:test_context_cpp.wasm", "//test/extensions/common/wasm/test_data:test_cpp.wasm", + "//test/extensions/common/wasm/test_data:test_cpp_source.wasm", "//test/extensions/common/wasm/test_data:test_restriction_cpp.wasm", ]), rbe_pool = "linux_x64_large", @@ -65,6 +66,7 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "//test/test_common:wasm_lib", + "@proxy-wasm-cpp-host//:headers", ], ) @@ -122,6 +124,15 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "wasm_runtime_test", + srcs = ["wasm_runtime_test.cc"], + tags = ["skip_on_windows"], + deps = [ + ":wasm_runtime", + ], +) + envoy_cc_test( name = "context_test", srcs = ["context_test.cc"], diff --git a/test/extensions/common/wasm/test_data/BUILD b/test/extensions/common/wasm/test_data/BUILD index c0160b6229ed9..7cb579dc58082 100644 --- a/test/extensions/common/wasm/test_data/BUILD +++ b/test/extensions/common/wasm/test_data/BUILD @@ -70,11 +70,18 @@ envoy_cc_test_library( envoy_wasm_cc_binary( name = "test_cpp.wasm", srcs = ["test_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), +) + +envoy_wasm_cc_binary( + name = "test_cpp_source.wasm", + srcs = ["test_cpp.cc"], ) envoy_wasm_cc_binary( name = "test_context_cpp.wasm", srcs = ["test_context_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "//source/extensions/common/wasm/ext:envoy_proxy_wasm_api_lib", ], @@ -83,6 +90,7 @@ envoy_wasm_cc_binary( envoy_wasm_cc_binary( name = "test_restriction_cpp.wasm", srcs = ["test_restriction_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), ) envoy_wasm_cc_binary( diff --git a/test/extensions/common/wasm/wasm_runtime.cc b/test/extensions/common/wasm/wasm_runtime.cc index 3981eff89f751..af025f8ea65fb 100644 --- a/test/extensions/common/wasm/wasm_runtime.cc +++ b/test/extensions/common/wasm/wasm_runtime.cc @@ -31,13 +31,16 @@ std::vector languages(bool cpp_only) { return languages; } -std::vector> wasmTestMatrix(bool include_nullvm, - bool cpp_only) { +std::vector> wasmTestMatrix(bool include_nullvm, bool cpp_only, + bool single_runtime) { std::vector> values; for (const auto& runtime : sandboxRuntimes()) { for (const auto& language : languages(cpp_only)) { values.push_back(std::make_tuple(runtime, language)); } + if (single_runtime) { + break; + } } if (include_nullvm) { values.push_back(std::make_tuple("null", "cpp")); @@ -46,9 +49,10 @@ std::vector> wasmTestMatrix(bool include_nu } std::vector> -wasmDualFilterTestMatrix(bool include_nullvm, bool cpp_only) { +wasmDualFilterTestMatrix(bool include_nullvm, bool cpp_only, bool single_runtime) { std::vector> values; - for (const auto& p : Envoy::Extensions::Common::Wasm::wasmTestMatrix(include_nullvm, cpp_only)) { + for (const auto& p : + Envoy::Extensions::Common::Wasm::wasmTestMatrix(include_nullvm, cpp_only, single_runtime)) { values.push_back(std::make_tuple(std::get<0>(p), std::get<1>(p), true)); values.push_back(std::make_tuple(std::get<0>(p), std::get<1>(p), false)); } diff --git a/test/extensions/common/wasm/wasm_runtime.h b/test/extensions/common/wasm/wasm_runtime.h index c5d36e84d9e7f..719ac59326b75 100644 --- a/test/extensions/common/wasm/wasm_runtime.h +++ b/test/extensions/common/wasm/wasm_runtime.h @@ -9,25 +9,40 @@ namespace Extensions { namespace Common { namespace Wasm { +// Sandbox runtimes compiled into this build (e.g. v8, wamr, wasmtime). +// Ordering is load-bearing: single_sandbox_*, first_sandbox_*, and host_test_* suites +// exercise only the first sandbox runtime. +std::vector sandboxRuntimes(); + // Testable runtime and language combinations. -std::vector> wasmTestMatrix(bool include_nullvm, - bool cpp_only); +std::vector> wasmTestMatrix(bool include_nullvm, bool cpp_only, + bool single_runtime = false); std::vector> -wasmDualFilterTestMatrix(bool include_nullvm, bool cpp_only); +wasmDualFilterTestMatrix(bool include_nullvm, bool cpp_only, bool single_runtime = false); std::vector> wasmDualFilterWithCodecsTestMatrix(bool include_nullvm, bool cpp_only, std::vector codecs_type); inline auto runtime_and_cpp_values = testing::ValuesIn(wasmTestMatrix(true, true)); +inline auto single_sandbox_runtime_and_cpp_values = + testing::ValuesIn(wasmTestMatrix(true, true, true)); +inline auto first_sandbox_runtime_and_cpp_values = + testing::ValuesIn(wasmTestMatrix(false, true, true)); inline auto sandbox_runtime_and_cpp_values = testing::ValuesIn(wasmTestMatrix(false, true)); inline auto dual_filter_sandbox_runtime_and_cpp_values = testing::ValuesIn(wasmDualFilterTestMatrix(false, true)); +inline auto dual_filter_single_sandbox_runtime_and_cpp_values = + testing::ValuesIn(wasmDualFilterTestMatrix(true, true, true)); inline auto dual_filter_with_codecs_sandbox_runtime_and_cpp_values = testing::ValuesIn(wasmDualFilterWithCodecsTestMatrix( false, true, {Envoy::Http::CodecType::HTTP1, Envoy::Http::CodecType::HTTP2})); inline auto runtime_and_language_values = testing::ValuesIn(wasmTestMatrix(true, false)); +inline auto host_test_runtime_and_language_values = + testing::ValuesIn(wasmTestMatrix(true, false, true)); +inline auto single_sandbox_runtime_and_language_values = + testing::ValuesIn(wasmTestMatrix(false, false, true)); inline auto sandbox_runtime_and_language_values = testing::ValuesIn(wasmTestMatrix(false, false)); std::string diff --git a/test/extensions/common/wasm/wasm_runtime_test.cc b/test/extensions/common/wasm/wasm_runtime_test.cc new file mode 100644 index 0000000000000..8cb4095698051 --- /dev/null +++ b/test/extensions/common/wasm/wasm_runtime_test.cc @@ -0,0 +1,35 @@ +#include "test/extensions/common/wasm/wasm_runtime.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +#if defined(PROXY_WASM_HAS_RUNTIME_V8) +TEST(WasmRuntimeTest, V8IsFirstSandboxRuntime) { + const auto runtimes = sandboxRuntimes(); + ASSERT_FALSE(runtimes.empty()); + EXPECT_EQ(runtimes.front(), "v8"); +} +#endif + +TEST(WasmRuntimeTest, HostTestMatrixUsesFirstAvailableSandbox) { + std::vector> expected; + const auto runtimes = sandboxRuntimes(); + if (!runtimes.empty()) { +#if defined(__x86_64__) + expected.emplace_back(runtimes.front(), "cpp"); +#endif + expected.emplace_back(runtimes.front(), "rust"); + } + expected.emplace_back("null", "cpp"); + + EXPECT_EQ(wasmTestMatrix(true, false, true), expected); +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index caf8c81050867..2ef368e429e9d 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -26,6 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "include/proxy-wasm/bytecode_util.h" #include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" @@ -83,6 +84,34 @@ class WasmCommonTest : public testing::TestWithParam allow_precompiled) { + envoy::extensions::wasm::v3::PluginConfig plugin_config; + auto* vm_config = plugin_config.mutable_vm_config(); + vm_config->set_runtime(absl::StrCat("envoy.wasm.runtime.", std::get<0>(GetParam()))); + vm_config->mutable_configuration()->set_value("source-canary"); + if (allow_precompiled.has_value()) { + vm_config->set_allow_precompiled(*allow_precompiled); + } + const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir }}/test/extensions/common/wasm/test_data/", module))); + ASSERT_FALSE(code.empty()); + auto plugin = std::make_shared(plugin_config, local_info_); + auto wasm = std::make_shared(plugin->wasmConfig(), "", scope_, *api_, cluster_manager_, + *dispatcher_); + if (allow_precompiled.value_or(false)) { + std::string_view precompiled; + ASSERT_TRUE(proxy_wasm::BytecodeUtil::getCustomSection( + code, wasm->wasm_vm()->getPrecompiledSectionName(), precompiled)); + ASSERT_FALSE(precompiled.empty()); + // V8::load exclusively deserializes when given a nonempty precompiled section; + // it returns false on rejection rather than falling back to compiling the source. + } + ASSERT_TRUE(wasm->load(code, vm_config->allow_precompiled())); + ASSERT_TRUE(wasm->initialize()); + EXPECT_LOG_CONTAINS("info", "on_vm_start source-canary", + ASSERT_NE(wasm->start(plugin), nullptr)); + } + Upstream::MockClusterManager cluster_manager_; Stats::IsolatedStoreImpl stats_store_; Api::ApiPtr api_; @@ -93,10 +122,33 @@ class WasmCommonTest : public testing::TestWithParam(GetParam()) == "null") { + GTEST_SKIP() << "Requires a sandbox runtime"; + } + loadTestModule("test_cpp_source.wasm", std::nullopt); +} + +TEST_P(WasmCommonTest, PrecompiledSectionStrippedWhenNotAllowed) { + if (std::get<0>(GetParam()) == "null") { + GTEST_SKIP() << "Requires a sandbox runtime"; + } + loadTestModule("test_cpp.wasm", false); +} + +TEST_P(WasmCommonTest, PrecompiledModuleIsUsedWhenAllowed) { + if (std::get<0>(GetParam()) != "v8") { + GTEST_SKIP() << "Requires V8 precompiled modules"; + } + loadTestModule("test_cpp.wasm", true); +} + TEST_P(WasmCommonTest, WasmFailState) { envoy::extensions::wasm::v3::PluginConfig plugin_config; auto plugin = std::make_shared(plugin_config, local_info_); @@ -570,6 +622,7 @@ TEST_P(WasmCommonTest, MemorySizeGaugeThreadLocalPlugin) { envoy::extensions::wasm::v3::PluginConfig plugin_config; auto vm_config = plugin_config.mutable_vm_config(); vm_config->set_runtime(runtime); + vm_config->set_allow_precompiled(true); Protobuf::StringValue vm_configuration_string; vm_configuration_string.set_value(vm_configuration); std::ignore = vm_config->mutable_configuration()->PackFrom(vm_configuration_string); @@ -1967,6 +2020,7 @@ root_id: "panic during request processing" failure_policy: FAIL_RELOAD vm_config: runtime: "envoy.wasm.runtime.{}" + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "some configuration" @@ -2086,6 +2140,7 @@ root_id: "panic during request processing" failure_policy: FAIL_CLOSED vm_config: runtime: "envoy.wasm.runtime.{}" + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "some configuration" @@ -2164,6 +2219,7 @@ name: "{}_test_wasm_reload" root_id: "panic during request processing" vm_config: runtime: "envoy.wasm.runtime.{}" + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "some configuration" @@ -2243,6 +2299,7 @@ root_id: "panic during request processing" failure_policy: FAIL_OPEN vm_config: runtime: "envoy.wasm.runtime.{}" + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "some configuration" @@ -2312,6 +2369,7 @@ name: "{}" root_id: "panic during request processing" vm_config: runtime: "envoy.wasm.runtime.{}" + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "some configuration" diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 9917f4d02e060..65611aef11367 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -23,31 +23,120 @@ envoy_proto_library( ) envoy_extension_cc_test( - name = "wasm_filter_test", - size = "enormous", - srcs = ["wasm_filter_test.cc"], + name = "wasm_filter_headers_test", + size = "large", + srcs = [ + "wasm_filter_headers_test.cc", + "wasm_filter_test_base.h", + ], data = envoy_select_wasm_cpp_tests([ - "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:headers_cpp.wasm", + ]) + envoy_select_wasm_rust_tests([ + "//test/extensions/filters/http/wasm/test_data:headers_rust.wasm", + ]), + extension_names = ["envoy.filters.http.wasm"], + rbe_pool = "linux_x64_small", + shard_count = 2, + tags = ["skip_on_windows"], + deps = [ + "//source/common/http:message_lib", + "//source/extensions/filters/http/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", + "//test/extensions/filters/http/wasm/test_data:test_cpp_plugin", + "//test/mocks/network:connection_mocks", + "//test/mocks/router:router_mocks", + "//test/test_common:test_runtime_lib", + "//test/test_common:wasm_lib", + ], +) + +envoy_extension_cc_test( + name = "wasm_filter_body_test", + size = "large", + srcs = [ + "wasm_filter_body_test.cc", + "wasm_filter_test_base.h", + ], + data = envoy_select_wasm_cpp_tests([ + "//test/extensions/filters/http/wasm/test_data:body_cpp.wasm", ]) + envoy_select_wasm_rust_tests([ - "//test/extensions/filters/http/wasm/test_data:async_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:body_rust.wasm", - "//test/extensions/filters/http/wasm/test_data:close_stream_rust.wasm", + ]), + extension_names = ["envoy.filters.http.wasm"], + rbe_pool = "linux_x64_small", + shard_count = 2, + tags = ["skip_on_windows"], + deps = [ + "//source/common/http:message_lib", + "//source/extensions/filters/http/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", + "//test/extensions/filters/http/wasm/test_data:test_cpp_plugin", + "//test/mocks/network:connection_mocks", + "//test/mocks/router:router_mocks", + "//test/test_common:test_runtime_lib", + "//test/test_common:wasm_lib", + ], +) + +envoy_extension_cc_test( + name = "wasm_filter_async_test", + size = "large", + srcs = [ + "wasm_filter_async_test.cc", + "wasm_filter_test_base.h", + ], + data = envoy_select_wasm_cpp_tests([ + "//test/extensions/filters/http/wasm/test_data:async_call_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:grpc_call_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:grpc_stream_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:resume_call_cpp.wasm", + ]) + envoy_select_wasm_rust_tests([ + "//test/extensions/filters/http/wasm/test_data:async_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:grpc_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:grpc_stream_rust.wasm", - "//test/extensions/filters/http/wasm/test_data:headers_rust.wasm", + "//test/extensions/filters/http/wasm/test_data:resume_call_rust.wasm", + ]), + extension_names = ["envoy.filters.http.wasm"], + rbe_pool = "linux_x64_small", + shard_count = 3, + tags = ["skip_on_windows"], + deps = [ + "//source/common/http:message_lib", + "//source/extensions/filters/http/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", + "//test/extensions/filters/http/wasm/test_data:test_cpp_plugin", + "//test/mocks/network:connection_mocks", + "//test/mocks/router:router_mocks", + "//test/test_common:test_runtime_lib", + "//test/test_common:wasm_lib", + ], +) + +envoy_extension_cc_test( + name = "wasm_filter_misc_test", + size = "large", + srcs = [ + "wasm_filter_misc_test.cc", + "wasm_filter_test_base.h", + ], + data = envoy_select_wasm_cpp_tests([ + "//test/extensions/filters/http/wasm/test_data:advanced_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:close_stream_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:headers_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:panic_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:shared_data_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:shared_queue_cpp.wasm", + ]) + envoy_select_wasm_rust_tests([ + "//test/extensions/filters/http/wasm/test_data:close_stream_rust.wasm", "//test/extensions/filters/http/wasm/test_data:metadata_rust.wasm", "//test/extensions/filters/http/wasm/test_data:panic_rust.wasm", - "//test/extensions/filters/http/wasm/test_data:resume_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_data_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_queue_rust.wasm", ]), extension_names = ["envoy.filters.http.wasm"], - rbe_pool = "linux_x64_large", - shard_count = 50, - tags = [ - "cpu:4", - "skip_on_windows", - ], + rbe_pool = "linux_x64_small", + shard_count = 3, + tags = ["skip_on_windows"], deps = [ "//source/common/http:message_lib", "//source/extensions/filters/http/wasm:wasm_filter_lib", @@ -62,14 +151,14 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "config_test", - size = "enormous", + size = "large", srcs = ["config_test.cc"], data = envoy_select_wasm_cpp_tests([ - "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:headers_cpp.wasm", ]), extension_names = ["envoy.filters.http.wasm"], - rbe_pool = "linux_x64_large", - shard_count = 16, + rbe_pool = "linux_x64_small", + shard_count = 4, tags = ["skip_on_windows"], deps = [ "//source/common/common:base64_lib", @@ -92,7 +181,10 @@ envoy_extension_cc_test( size = "enormous", srcs = ["wasm_filter_integration_test.cc"], data = envoy_select_wasm_cpp_tests([ - "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", + "//test/extensions/common/wasm/test_data:test_cpp_source.wasm", + "//test/extensions/filters/http/wasm/test_data:body_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:headers_cpp.wasm", + "//test/extensions/filters/http/wasm/test_data:panic_cpp.wasm", ]) + envoy_select_wasm_rust_tests([ "//test/extensions/filters/http/wasm/test_data:deferred_data_rust.wasm", ]), diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index ec8d26971c28c..31d204ecb774d 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -60,7 +60,12 @@ class WasmFilterConfigTest } } - void SetUp() override { Envoy::Extensions::Common::Wasm::clearCodeCacheForTesting(); } + void SetUp() override { + if (std::get<0>(GetParam()) == "null") { + GTEST_SKIP() << "Configuration tests require a sandbox Wasm module"; + } + Envoy::Extensions::Common::Wasm::clearCodeCacheForTesting(); + } void initializeForRemote() { retry_timer_ = new Event::MockTimer(); @@ -134,6 +139,7 @@ class WasmFilterConfigTest name: ")EOF", name, R"EOF(" vm_config: + allow_precompiled: true vm_id: ")EOF", name, R"EOF(" runtime: "envoy.wasm.runtime.)EOF", @@ -143,7 +149,7 @@ class WasmFilterConfigTest value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; TestUtility::loadFromYaml(yaml, proto_config); @@ -170,7 +176,7 @@ class WasmFilterConfigTest INSTANTIATE_TEST_SUITE_P( Runtimes, WasmFilterConfigTest, - Envoy::Extensions::Common::Wasm::dual_filter_sandbox_runtime_and_cpp_values, + Envoy::Extensions::Common::Wasm::dual_filter_single_sandbox_runtime_and_cpp_values, Envoy::Extensions::Common::Wasm::wasmDualFilterTestParamsToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmFilterConfigTest); @@ -178,6 +184,7 @@ TEST_P(WasmFilterConfigTest, CreateFilterFactoryFromProtoWithServerContext) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -185,7 +192,7 @@ TEST_P(WasmFilterConfigTest, CreateFilterFactoryFromProtoWithServerContext) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; @@ -232,7 +239,7 @@ TEST_P(WasmFilterConfigTest, JsonLoadFromFileWasm) { }, "code": { "local": { - "filename": "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + "filename": "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" } }, }}} @@ -255,6 +262,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasm) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -262,7 +270,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasm) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; @@ -341,6 +349,7 @@ TEST_P(WasmFilterConfigTest, DEPRECATED_FEATURE_TEST(YamlLoadFromFileWasmFailOpe config: fail_open: true vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -348,7 +357,7 @@ TEST_P(WasmFilterConfigTest, DEPRECATED_FEATURE_TEST(YamlLoadFromFileWasmFailOpe value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; @@ -369,6 +378,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmFailOpenPolicyOk) { config: failure_policy: FAIL_OPEN vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -376,7 +386,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmFailOpenPolicyOk) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; @@ -397,6 +407,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -404,7 +415,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "invalid" @@ -419,6 +430,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -426,7 +438,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: "valid" @@ -444,11 +456,12 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { TEST_P(WasmFilterConfigTest, YamlLoadInlineWasm) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); EXPECT_FALSE(code.empty()); const std::string yaml = absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -472,6 +485,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadInlineBadCode) { const std::string yaml = absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -488,12 +502,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadInlineBadCode) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasm) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -537,7 +552,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasm) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasmFailOnUncachedThenSucceed) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( @@ -603,7 +618,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasmFailOnUncachedThenSucceed) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasmFailCachedThenSucceed) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( @@ -794,12 +809,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasmFailCachedThenSucceed) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteConnectionReset) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -836,12 +852,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteConnectionReset) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessWith503) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -881,12 +898,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessWith503) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessIncorrectSha256) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -927,12 +945,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessIncorrectSha256) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteMultipleRetries) { initializeForRemote(); const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -1004,6 +1023,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcode) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -1074,6 +1094,7 @@ TEST_P(WasmFilterConfigTest, DEPRECATED_FEATURE_TEST(YamlLoadFromRemoteSuccessBa config: fail_open: true vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -1121,6 +1142,7 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcodeFailOpenPolicy) { config: failure_policy: FAIL_OPEN vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -1162,12 +1184,13 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcodeFailOpenPolicy) { TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteWasmcreateContext) { const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( - "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); + "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm")); const std::string sha256 = Hex::encode(Envoy::Common::Crypto::UtilitySingleton::get().getSha256Digest(code)); const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -1219,6 +1242,7 @@ TEST_P(WasmFilterConfigTest, DEPRECATED_FEATURE_TEST(FailedToGetThreadLocalPlugi config: fail_open: true vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -1226,7 +1250,7 @@ TEST_P(WasmFilterConfigTest, DEPRECATED_FEATURE_TEST(FailedToGetThreadLocalPlugi value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; @@ -1249,6 +1273,7 @@ TEST_P(WasmFilterConfigTest, FailedToGetThreadLocalPluginOpenPolicy) { config: failure_policy: FAIL_OPEN vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -1256,7 +1281,7 @@ TEST_P(WasmFilterConfigTest, FailedToGetThreadLocalPluginOpenPolicy) { value: "some configuration" code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/headers_cpp.wasm" )EOF")); envoy::extensions::filters::http::wasm::v3::Wasm proto_config; diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 693146697c4ab..49725e7e8e72a 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -5,6 +5,7 @@ load( "envoy_cc_test_library", "envoy_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_v8_bool") load("//bazel/wasm:wasm.bzl", "envoy_wasm_cc_binary", "wasm_rust_binary") licenses(["notice"]) # Apache 2 @@ -14,6 +15,7 @@ envoy_package() wasm_rust_binary( name = "async_call_rust.wasm", srcs = ["async_call_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -23,6 +25,7 @@ wasm_rust_binary( wasm_rust_binary( name = "body_rust.wasm", srcs = ["body_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -32,6 +35,7 @@ wasm_rust_binary( wasm_rust_binary( name = "close_stream_rust.wasm", srcs = ["close_stream_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -41,6 +45,7 @@ wasm_rust_binary( wasm_rust_binary( name = "deferred_data_rust.wasm", srcs = ["deferred_data_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", ], @@ -49,6 +54,7 @@ wasm_rust_binary( wasm_rust_binary( name = "grpc_call_rust.wasm", srcs = ["grpc_call_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@cu//:protobuf", "@proxy-wasm-rust-sdk//:proxy_wasm", @@ -59,6 +65,7 @@ wasm_rust_binary( wasm_rust_binary( name = "grpc_stream_rust.wasm", srcs = ["grpc_stream_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@cu//:protobuf", "@proxy-wasm-rust-sdk//:proxy_wasm", @@ -70,6 +77,7 @@ wasm_rust_binary( name = "headers_rust.wasm", srcs = ["headers_rust.rs"], wasi = True, + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -79,6 +87,7 @@ wasm_rust_binary( wasm_rust_binary( name = "metadata_rust.wasm", srcs = ["metadata_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -88,6 +97,7 @@ wasm_rust_binary( wasm_rust_binary( name = "panic_rust.wasm", srcs = ["panic_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -97,6 +107,7 @@ wasm_rust_binary( wasm_rust_binary( name = "resume_call_rust.wasm", srcs = ["resume_call_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -106,6 +117,7 @@ wasm_rust_binary( wasm_rust_binary( name = "shared_data_rust.wasm", srcs = ["shared_data_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -115,6 +127,7 @@ wasm_rust_binary( wasm_rust_binary( name = "shared_queue_rust.wasm", srcs = ["shared_queue_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -150,19 +163,17 @@ envoy_cc_test_library( ) envoy_wasm_cc_binary( - name = "test_cpp.wasm", - srcs = [ - "test_async_call_cpp.cc", - "test_body_cpp.cc", - "test_close_stream_cpp.cc", - "test_cpp.cc", - "test_grpc_call_cpp.cc", - "test_grpc_stream_cpp.cc", - "test_panic_cpp.cc", - "test_resume_call_cpp.cc", - "test_shared_data_cpp.cc", - "test_shared_queue_cpp.cc", - ], + name = "headers_cpp.wasm", + srcs = ["test_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + copts = ["-DWASM_TEST_CPP_HEADERS_ONLY=1"], + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "advanced_cpp.wasm", + srcs = ["test_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), protobuf = "lite", deps = [ ":test_cc_proto", @@ -174,6 +185,77 @@ envoy_wasm_cc_binary( ], ) +envoy_wasm_cc_binary( + name = "body_cpp.wasm", + srcs = ["test_body_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "async_call_cpp.wasm", + srcs = ["test_async_call_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "resume_call_cpp.wasm", + srcs = ["test_resume_call_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "close_stream_cpp.wasm", + srcs = ["test_close_stream_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "grpc_call_cpp.wasm", + srcs = ["test_grpc_call_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + protobuf = "lite", + deps = [ + ":test_cc_proto", + "@proxy-wasm-cpp-sdk//contrib:contrib_lib", + ], +) + +envoy_wasm_cc_binary( + name = "grpc_stream_cpp.wasm", + srcs = ["test_grpc_stream_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + protobuf = "lite", + deps = [ + ":test_cc_proto", + "@proxy-wasm-cpp-sdk//contrib:contrib_lib", + ], +) + +envoy_wasm_cc_binary( + name = "shared_data_cpp.wasm", + srcs = ["test_shared_data_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "shared_queue_cpp.wasm", + srcs = ["test_shared_queue_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + +envoy_wasm_cc_binary( + name = "panic_cpp.wasm", + srcs = ["test_panic_cpp.cc"], + precompile = envoy_select_wasm_v8_bool(), + deps = ["@proxy-wasm-cpp-sdk//contrib:contrib_lib"], +) + # NB: this target is compiled both to native code and to Wasm. Hence the generic rule. proto_library( name = "test_proto", diff --git a/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc index 9d16ab30a81e3..d04a9bbd890cd 100644 --- a/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_async_call_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/test_data/test_body_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_body_cpp.cc index 6bd4e04163ba1..9bb0e83f73bbf 100644 --- a/test/extensions/filters/http/wasm/test_data/test_body_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_body_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif @@ -41,7 +41,7 @@ void BodyContext::logBody(WasmBufferType type) { uint32_t flags; getBufferStatus(type, &buffered_size, &flags); auto body = getBufferBytes(type, 0, buffered_size); - absl::string_view body_view = body->view(); + std::string_view body_view = body->view(); // To avoid log too large body. if (body_view.size() > 2048) { diff --git a/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc index b9fec2ef05d03..987648f27f4e6 100644 --- a/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 044d8ed67dce5..9516a56ebd94f 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -3,9 +3,12 @@ #include #include +#ifndef WASM_TEST_CPP_HEADERS_ONLY #include "test/extensions/filters/http/wasm/test_data/test.pb.h" +#endif #ifndef NULL_PLUGIN +#ifndef WASM_TEST_CPP_HEADERS_ONLY #include "proxy_wasm_intrinsics_lite.h" #include "source/extensions/common/wasm/ext/declare_property.pb.h" @@ -13,6 +16,9 @@ #include "source/extensions/common/wasm/ext/sign.pb.h" #include "source/extensions/common/wasm/ext/verify_signature.pb.h" #else +#include "proxy_wasm_intrinsics.h" +#endif +#else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #include "absl/base/casts.h" @@ -186,6 +192,7 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { } } +#ifndef WASM_TEST_CPP_HEADERS_ONLY { // Validate a valid CEL expression const std::string expr = R"( @@ -209,6 +216,7 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { } } } +#endif { // Create a syntactically wrong CEL expression @@ -388,6 +396,7 @@ void TestContext::onLog() { &cluster_metadata)) { logWarn("cluster metadata: " + cluster_metadata); } +#ifndef WASM_TEST_CPP_HEADERS_ONLY } else if (test == "property") { setFilterState("wasm_state", "wasm_value"); auto path = getRequestHeader(":path"); @@ -613,6 +622,7 @@ void TestContext::onLog() { } } } +#endif } } @@ -652,6 +662,7 @@ void TestRootContext::onTick() { if (getValue({"xds", "node", "metadata", "wasm_node_list_key", "1"}, &list_value)) { logDebug("unexpected list value outside the range"); } +#ifndef WASM_TEST_CPP_HEADERS_ONLY } else if (test_ == "property") { uint64_t t; if (WasmResult::Ok != proxy_get_current_time_nanoseconds(&t)) { @@ -1004,7 +1015,8 @@ void TestRootContext::onTick() { if (WasmResult::Ok == proxy_call_foreign_function(function.data(), function.size(), in.data(), in.size(), &out, &out_size)) { envoy::source::extensions::common::wasm::SignResult result; - if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && result.result()) { + if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && + result.result()) { logInfo("signature created successfully"); } else { logError(result.error()); @@ -1027,7 +1039,8 @@ void TestRootContext::onTick() { if (WasmResult::Ok == proxy_call_foreign_function(function.data(), function.size(), in.data(), in.size(), &out, &out_size)) { envoy::source::extensions::common::wasm::SignResult result; - if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && result.result()) { + if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && + result.result()) { logCritical("signature should not be ok"); } else { logError(result.error()); @@ -1050,7 +1063,8 @@ void TestRootContext::onTick() { if (WasmResult::Ok == proxy_call_foreign_function(function.data(), function.size(), in.data(), in.size(), &out, &out_size)) { envoy::source::extensions::common::wasm::SignResult result; - if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && result.result()) { + if (result.ParseFromString(absl::string_view(out, static_cast(out_size))) && + result.result()) { logCritical("signature should not be ok"); } else { logError(result.error()); @@ -1172,7 +1186,8 @@ void TestRootContext::onTick() { sign_in.data(), sign_in.size(), &sign_out, &sign_out_size)) { envoy::source::extensions::common::wasm::SignResult sign_result; - if (sign_result.ParseFromString(absl::string_view(sign_out, static_cast(sign_out_size))) && + if (sign_result.ParseFromString( + absl::string_view(sign_out, static_cast(sign_out_size))) && sign_result.result()) { logInfo("signature created successfully, length: " + std::to_string(sign_result.signature().size())); @@ -1195,7 +1210,8 @@ void TestRootContext::onTick() { verify_in.data(), verify_in.size(), &verify_out, &verify_out_size)) { envoy::source::extensions::common::wasm::VerifySignatureResult verify_result; - if (verify_result.ParseFromString(absl::string_view(verify_out, static_cast(verify_out_size))) && + if (verify_result.ParseFromString( + absl::string_view(verify_out, static_cast(verify_out_size))) && verify_result.result()) { logInfo("end-to-end test passed: signature created and verified successfully"); } else { @@ -1239,6 +1255,7 @@ void TestRootContext::onTick() { } ::free(out); } +#endif } } diff --git a/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc index b6b86531dcff1..0ff36a93130de 100644 --- a/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc index 2ce27d3398404..7ec3eb71eef5c 100644 --- a/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/test_data/test_shared_data_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_shared_data_cpp.cc index d3b9e51fe8d31..1e086ef9a7dac 100644 --- a/test/extensions/filters/http/wasm/test_data/test_shared_data_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_shared_data_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/test_data/test_shared_queue_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_shared_queue_cpp.cc index f6235a820dd38..8cc5853b8fd54 100644 --- a/test/extensions/filters/http/wasm/test_data/test_shared_queue_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_shared_queue_cpp.cc @@ -4,7 +4,7 @@ #include #ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics_lite.h" +#include "proxy_wasm_intrinsics.h" #else #include "source/extensions/common/wasm/ext/envoy_null_plugin.h" #endif diff --git a/test/extensions/filters/http/wasm/wasm_filter_async_test.cc b/test/extensions/filters/http/wasm/wasm_filter_async_test.cc new file mode 100644 index 0000000000000..883fbb102f2a6 --- /dev/null +++ b/test/extensions/filters/http/wasm/wasm_filter_async_test.cc @@ -0,0 +1,995 @@ +#include "test/extensions/filters/http/wasm/wasm_filter_test_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Wasm { + +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::host_test_runtime_and_language_values, + Envoy::Extensions::Common::Wasm::wasmTestParamsToString); + +TEST_P(WasmHttpFilterTest, AsyncCall) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce(Invoke( + [&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "11"}}), + message->headers()); + EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); + EXPECT_EQ(options.send_xff, false); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response"))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(":status -> 200"))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response_message->body().add("response"); + NiceMock span; + Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; + callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); + callbacks->onSuccess(request, std::move(response_message)); + return proxy_wasm::WasmResult::Ok; + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, StopAndResumeViaAsyncCall) { + setupTest("resume_call"); + setupFilter(); + + InSequence s; + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "6"}}), + message->headers()); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + NiceMock span; + Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; + callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); + callbacks->onSuccess(request, std::move(response_message)); + return proxy_wasm::WasmResult::Ok; + })); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("continueRequest"))); + + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, continueDecoding()).WillOnce(Invoke([&]() { + // Verify that we're not resuming processing from within Wasm callback. + EXPECT_EQ(proxy_wasm::current_context_, nullptr); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + // Just fail the send. + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + return nullptr; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); +} + +TEST_P(WasmHttpFilterTest, AsyncCallBadCluster) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response( + new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "503"}}})); + // Simulate code path for "no healthy host for HTTP connection pool" inline callback. + cb.onSuccess(request, std::move(response)); + return nullptr; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); +} + +TEST_P(WasmHttpFilterTest, AsyncCallFailure) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "11"}}), + message->headers()); + EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + callbacks->onFailure(request, Http::AsyncClient::FailureReason::Reset); + return proxy_wasm::WasmResult::Ok; + })); + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call failed"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq("async_call failed"))); + } + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { + setupTest("async_call"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "11"}}), + message->headers()); + EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); + + // Destroy the Context, Plugin and VM. + context_.reset(); + plugin_.reset(); + plugin_handle_.reset(); + wasm_.reset(); + + Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response_message->body().add("response"); + + // (Don't) Make the callback on the destroyed VM. + EXPECT_EQ(callbacks, nullptr); + if (callbacks) { + callbacks->onSuccess(request, std::move(response_message)); + } +} + +TEST_P(WasmHttpFilterTest, GrpcCall) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_call"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_call_proto"); + } + for (const auto& id : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + NiceMock request; + Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + Tracing::Span* parent_span{}; + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce( + Invoke([&](absl::string_view service_full_name, absl::string_view method_name, + Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, + Tracing::Span& span, + const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(method_name, "method"); + Protobuf::Value value; + EXPECT_TRUE( + value.ParseFromArray(message->linearize(message->length()), message->length())); + EXPECT_EQ(value.string_value(), "request"); + callbacks = &cb; + parent_span = &span; + EXPECT_EQ(options.timeout->count(), 1000); + EXPECT_EQ(options.send_xff, false); + return &request; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response"))); + } + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + NiceMock span; + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onSuccessRaw(std::move(response), span); + } + } +} + +TEST_P(WasmHttpFilterTest, GrpcCallBadCall) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_call"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_call_proto"); + } + for (const auto& id : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("expected failure occurred")))); + } + + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce(Invoke([&](absl::string_view, absl::string_view, Buffer::InstancePtr&&, + Grpc::RawAsyncRequestCallbacks&, Tracing::Span&, + const Http::AsyncClient::RequestOptions&) -> Grpc::AsyncRequest* { + return nullptr; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + } +} + +TEST_P(WasmHttpFilterTest, GrpcCallFailure) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_call"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_call_proto"); + } + for (const auto& id : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + NiceMock request; + Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + Tracing::Span* parent_span{}; + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce( + Invoke([&](absl::string_view service_full_name, absl::string_view method_name, + Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, + Tracing::Span& span, + const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(method_name, "method"); + Protobuf::Value value; + EXPECT_TRUE( + value.ParseFromArray(message->linearize(message->length()), message->length())); + EXPECT_EQ(value.string_value(), "request"); + callbacks = &cb; + parent_span = &span; + EXPECT_EQ(options.timeout->count(), 1000); + return &request; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("failure bad"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("failure bad"))); + } + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + // Test some additional error paths. + // 0xFF00 (HTTP call). + EXPECT_EQ(filter().grpcSend(0xFF00, "", false), proxy_wasm::WasmResult::BadArgument); + EXPECT_EQ(filter().grpcCancel(0xFF00), proxy_wasm::WasmResult::BadArgument); + EXPECT_EQ(filter().grpcClose(0xFF00), proxy_wasm::WasmResult::BadArgument); + + // 0xFF01 (gRPC call). + EXPECT_EQ(filter().grpcSend(0xFF01, "", false), proxy_wasm::WasmResult::BadArgument); + EXPECT_EQ(filter().grpcCancel(0xFF01), proxy_wasm::WasmResult::NotFound); + EXPECT_EQ(filter().grpcClose(0xFF01), proxy_wasm::WasmResult::NotFound); + + // 0xFF02 (gRPC stream). + EXPECT_EQ(filter().grpcSend(0xFF02, "", false), proxy_wasm::WasmResult::NotFound); + EXPECT_EQ(filter().grpcCancel(0xFF02), proxy_wasm::WasmResult::NotFound); + EXPECT_EQ(filter().grpcClose(0xFF02), proxy_wasm::WasmResult::NotFound); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + NiceMock span; + if (callbacks) { + callbacks->onFailure(Grpc::Status::WellKnownGrpcStatus::Canceled, "bad", span); + } + } +} + +TEST_P(WasmHttpFilterTest, GrpcCallCancel) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_call"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_call_proto"); + } + for (const auto& id : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + NiceMock request; + Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + Tracing::Span* parent_span{}; + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce( + Invoke([&](absl::string_view service_full_name, absl::string_view method_name, + Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, + Tracing::Span& span, + const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(method_name, "method"); + Protobuf::Value value; + EXPECT_TRUE( + value.ParseFromArray(message->linearize(message->length()), message->length())); + EXPECT_EQ(value.string_value(), "request"); + callbacks = &cb; + parent_span = &span; + EXPECT_EQ(options.timeout->count(), 1000); + return &request; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + rootContext().onQueueReady(0); + } +} + +TEST_P(WasmHttpFilterTest, GrpcCallClose) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_call"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_call_proto"); + } + for (const auto& id : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + NiceMock request; + Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + Tracing::Span* parent_span{}; + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce( + Invoke([&](absl::string_view service_full_name, absl::string_view method_name, + Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, + Tracing::Span& span, + const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(method_name, "method"); + Protobuf::Value value; + EXPECT_TRUE( + value.ParseFromArray(message->linearize(message->length()), message->length())); + EXPECT_EQ(value.string_value(), "request"); + callbacks = &cb; + parent_span = &span; + EXPECT_EQ(options.timeout->count(), 1000); + return &request; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + rootContext().onQueueReady(1); + } +} + +TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { + std::vector> proto_or_cluster; + proto_or_cluster.emplace_back("grpc_call", true); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.emplace_back("grpc_call_proto", false); + } + for (const auto& [id, success] : proto_or_cluster) { + setupTest(id); + setupFilter(); + + if (id == "grpc_call_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("bogus grpc_service accepted error")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + Grpc::MockAsyncRequest request; + Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; + Grpc::MockAsyncClientManager client_manager; + auto client_factory = std::make_unique(); + auto async_client = std::make_unique(); + Tracing::Span* parent_span{}; + EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) + .WillOnce( + Invoke([&](absl::string_view service_full_name, absl::string_view method_name, + Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, + Tracing::Span& span, + const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(method_name, "method"); + Protobuf::Value value; + EXPECT_TRUE( + value.ParseFromArray(message->linearize(message->length()), message->length())); + EXPECT_EQ(value.string_value(), "request"); + callbacks = &cb; + parent_span = &span; + EXPECT_EQ(options.timeout->count(), 1000); + return &request; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); + EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + return std::move(async_client); + })); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); + + // Destroy the Context, Plugin and VM. + context_.reset(); + plugin_.reset(); + plugin_handle_.reset(); + wasm_.reset(); + } else { + rootContext().onQueueReady(2); + + // Start shutdown sequence. + wasm_->wasm()->startShutdown(); + plugin_.reset(); + plugin_handle_.reset(); + wasm_.reset(); + } + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + if (std::get<1>(GetParam()) == "rust") { + EXPECT_EQ(callbacks, nullptr); + } else { + EXPECT_NE(callbacks, nullptr); + } + NiceMock span; + if (callbacks) { + if (success) { + callbacks->onSuccessRaw(std::move(response), span); + } else { + callbacks->onFailure(Grpc::Status::WellKnownGrpcStatus::Canceled, "bad", span); + } + } + } +} + +void WasmHttpFilterTest::setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks, + std::string id) { + setupTest(id); + setupFilter(); + + EXPECT_CALL(async_client_manager_, getOrCreateRawAsyncClient(_, _, _)) + .WillRepeatedly( + Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { + auto async_client = std::make_unique(); + EXPECT_CALL(*async_client, startRaw(_, _, _, _)) + .WillRepeatedly(Invoke( + [&](absl::string_view service_full_name, absl::string_view method_name, + Grpc::RawAsyncStreamCallbacks& cb, + const Http::AsyncClient::StreamOptions& options) -> Grpc::RawAsyncStream* { + EXPECT_EQ(service_full_name, "service"); + EXPECT_EQ(options.send_xff, false); + if (method_name != "method") { + return nullptr; + } + callbacks = &cb; + return &async_stream_; + })); + return async_client; + })); + EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) + .WillRepeatedly(Invoke([&]() -> Grpc::AsyncClientManager& { return async_client_manager_; })); +} + +TEST_P(WasmHttpFilterTest, GrpcStream) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_stream"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_stream_proto"); + } + for (const auto& id : proto_or_cluster) { + Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; + setupGrpcStreamTest(callbacks, id); + + if (id == "grpc_stream_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close done"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close done"))); + } + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onReceiveInitialMetadata(std::make_unique()); + callbacks->onReceiveMessageRaw(std::move(response)); + callbacks->onReceiveTrailingMetadata(std::make_unique()); + callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "done"); + } + } +} + +// Local close followed by remote close. + +TEST_P(WasmHttpFilterTest, GrpcStreamCloseLocal) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_stream"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_stream_proto"); + } + for (const auto& id : proto_or_cluster) { + Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; + setupGrpcStreamTest(callbacks, id); + + if (id == "grpc_stream_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response close"))); + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close ok"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response close"))); + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close ok"))); + } + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("close"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onReceiveInitialMetadata(std::make_unique()); + callbacks->onReceiveMessageRaw(std::move(response)); + callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "ok"); + } + } +} + +// Remote close followed by local close. + +TEST_P(WasmHttpFilterTest, GrpcStreamCloseRemote) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_stream"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_stream_proto"); + } + for (const auto& id : proto_or_cluster) { + Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; + setupGrpcStreamTest(callbacks, id); + + if (id == "grpc_stream_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close close"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close close"))); + } + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onReceiveInitialMetadata(std::make_unique()); + callbacks->onReceiveMessageRaw(std::move(response)); + callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "close"); + } + } +} + +TEST_P(WasmHttpFilterTest, GrpcStreamCancel) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_stream"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_stream_proto"); + } + for (const auto& id : proto_or_cluster) { + Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; + setupGrpcStreamTest(callbacks, id); + + if (id == "grpc_stream_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + NiceMock span; + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onReceiveInitialMetadata(std::make_unique( + Http::TestResponseHeaderMapImpl{{"test", "reset"}})); + } + } +} + +TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { + std::vector proto_or_cluster; + proto_or_cluster.push_back("grpc_stream"); + if (std::get<1>(GetParam()) == "cpp") { + // cluster definition passed as a protobuf is only supported in C++ SDK. + proto_or_cluster.push_back("grpc_stream_proto"); + } + for (const auto& id : proto_or_cluster) { + Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; + setupGrpcStreamTest(callbacks, id); + + if (id == "grpc_stream_proto") { + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } else { + cluster_manager_.initializeThreadLocalClusters({"cluster"}); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus service parse failure")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("expected bogus method call failure")))); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); + } + + // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); + } else { + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); + } + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + Protobuf::Value value; + value.set_string_value("response"); + std::string response_string; + EXPECT_TRUE(value.SerializeToString(&response_string)); + auto response = std::make_unique(response_string); + EXPECT_NE(callbacks, nullptr); + NiceMock span; + if (callbacks) { + callbacks->onCreateInitialMetadata(request_headers); + const auto source = request_headers.get(Http::LowerCaseString{"source"}); + EXPECT_EQ(source.size(), 1); + EXPECT_EQ(source[0]->value().getStringView(), id); + callbacks->onReceiveInitialMetadata(std::make_unique()); + callbacks->onReceiveMessageRaw(std::move(response)); + callbacks->onReceiveTrailingMetadata(std::make_unique()); + } + + // Destroy the Context, Plugin and VM. + context_.reset(); + plugin_.reset(); + plugin_handle_.reset(); + wasm_.reset(); + } +} + +// Test metadata access including CEL expressions. + +} // namespace Wasm +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/wasm/wasm_filter_body_test.cc b/test/extensions/filters/http/wasm/wasm_filter_body_test.cc new file mode 100644 index 0000000000000..0ca8ad52f2d1c --- /dev/null +++ b/test/extensions/filters/http/wasm/wasm_filter_body_test.cc @@ -0,0 +1,387 @@ +#include "test/extensions/filters/http/wasm/wasm_filter_test_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Wasm { + +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::host_test_runtime_and_language_values, + Envoy::Extensions::Common::Wasm::wasmTestParamsToString); + +TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReadBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that prepends and appends to the body. + +TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("onBody prepend.prepend.hello.append.append")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "PrependAndAppendToBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + if (std::get<1>(GetParam()) == "rust") { + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); + } else { + // This status is not available in the rust SDK. + // TODO: update all SDKs to the new revision of the spec and update the tests accordingly. + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter().decodeData(data, true)); + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter().encodeData(data, true)); + } + filter().onDestroy(); +} + +// Script that replaces the body. + +TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))).Times(2); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "ReplaceBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + if (std::get<1>(GetParam()) == "rust") { + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); + } else { + // This status is not available in the rust SDK. + // TODO: update all SDKs to the new revision of the spec and update the tests accordingly. + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter().decodeData(data, true)); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter().encodeData(data, true)); + } + filter().onDestroy(); +} + +// Script that replaces the first character in the body. + +TEST_P(WasmHttpFilterTest, BodyRequestPartialReplaceBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody partial.replace.ello")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, + Eq(absl::string_view("onBody partial.replace.artial.replace.ello")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "PartialReplaceBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); + filter().onDestroy(); +} + +// Script that removes the body. + +TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "RemoveBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that removes the first character from the body. + +TEST_P(WasmHttpFilterTest, BodyRequestPartialRemoveBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ello")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "PartialRemoveBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that buffers the body. + +TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { + setupTest("body"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "BufferBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl bufferedBody; + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); + EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) + .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); + + Buffer::OwnedImpl data1("hello"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); + // The first call to decodeData() will not result in the addDecodedData() callback being called + // because the filter doesn't yet know if the VM's onRequestBody() will return + // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. + bufferedBody.add(data1); + + // The second call to decodeData() will result in the addDecodedData() callback being called + // because the VM's onRequestBody() returned StopIterationAndBuffer. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) + .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); + + Buffer::OwnedImpl data2(" again "); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello again ")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); + + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) + .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); + + Buffer::OwnedImpl data3("hello"); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody hello again hello")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data3, true)); + + // Verify that the response still works even though we buffered the request. + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"x-test-operation", "ReadBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + // Should not buffer this time + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))).Times(2); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data1, false)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data1, true)); + + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, BodyRequestBufferBodyAndDestroyFilterInTheAddDecodedDataCallback) { + setupTest("body"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "BufferBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl bufferedBody; + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); + EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) + .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); + + Buffer::OwnedImpl data1("hello"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); + // The first call to decodeData() will not result in the addDecodedData() callback being called + // because the filter doesn't yet know if the VM's onRequestBody() will return + // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. + bufferedBody.add(data1); + + // The second call to decodeData() will result in the addDecodedData() callback being called + // because the VM's onRequestBody() returned StopIterationAndBuffer. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) + .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); + + Buffer::OwnedImpl data2(" again "); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello again ")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); + + // The addDecodedData() may result in buffer overflow and the local reply will be sent. It may + // destroy the filter. We mock the onDestroy() to ensure the filter will not call into the VM + // after it is destroyed. + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) + .WillOnce(Invoke([this](Buffer::Instance&, bool) { filter().onDestroy(); })); + + Buffer::OwnedImpl data3("hello"); + EXPECT_CALL(filter(), log_(_, _)).Times(0); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data3, true)); +} + +// Script that prepends and appends to the buffered body. + +TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {"x-test-operation", "PrependAndAppendToBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that replaces the buffered body. + +TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "ReplaceBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that replaces the first character in the buffered body. + +TEST_P(WasmHttpFilterTest, BodyRequestPartialReplaceBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody partial.replace.ello")))); + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {"x-test-operation", "PartialReplaceBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that removes the buffered body. + +TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "RemoveBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that removes the first character from the buffered body. + +TEST_P(WasmHttpFilterTest, BodyRequestPartialRemoveBufferedBody) { + setupTest("body"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ello")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"x-test-operation", "PartialRemoveBufferedBody"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + filter().onDestroy(); +} + +// Script that buffers the first part of the body and streams the rest + +TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { + setupTest("body"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl bufferedBody; + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); + EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) + .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"x-test-operation", "BufferTwoBodies"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + + Buffer::OwnedImpl data1("hello"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); + bufferedBody.add(data1); + + Buffer::OwnedImpl data2(", there, "); + bufferedBody.add(data2); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, ")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); + + // Previous callbacks returned "Buffer" so we have buffered so far + Buffer::OwnedImpl data3("world!"); + bufferedBody.add(data3); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, world!")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data3, false)); + + // Last callback returned "continue" so we just see individual chunks. + Buffer::OwnedImpl data4("So it's "); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody So it's ")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data4, false)); + + Buffer::OwnedImpl data5("goodbye, then!"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody goodbye, then!")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data5, true)); + + filter().onDestroy(); +} + +// Script that buffers the first part of the body and streams the rest + +TEST_P(WasmHttpFilterTest, BodyResponseBufferThenStreamBody) { + setupTest("body"); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl bufferedBody; + EXPECT_CALL(encoder_callbacks_, modifyEncodingBuffer(_)) + .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"x-test-operation", "BufferTwoBodies"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + + Buffer::OwnedImpl data1("hello"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().encodeData(data1, false)); + // The first call to encodeData() will not result in the addEncodedData() callback being called + // because the filter doesn't yet know if the VM's onResponseBody() will return + // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. + bufferedBody.add(data1); + + // The second call to encodeData() will result in the addEncodedData() callback being called + // because the VM's onResponseBody() returned StopIterationAndBuffer. + EXPECT_CALL(encoder_callbacks_, addEncodedData(_, _)) + .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); + + Buffer::OwnedImpl data2(", there, "); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, ")))); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().encodeData(data2, false)); + + EXPECT_CALL(encoder_callbacks_, addEncodedData(_, _)) + .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); + + Buffer::OwnedImpl data3("world!"); + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, world!")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data3, false)); + + // Last callback returned "continue" so we just see individual chunks. + Buffer::OwnedImpl data4("So it's "); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody So it's ")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data4, false)); + + Buffer::OwnedImpl data5("goodbye, then!"); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody goodbye, then!")))); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data5, true)); + + filter().onDestroy(); +} + +// Script testing AccessLog::Instance::log. + +} // namespace Wasm +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/wasm/wasm_filter_headers_test.cc b/test/extensions/filters/http/wasm/wasm_filter_headers_test.cc new file mode 100644 index 0000000000000..d261fc93af3bb --- /dev/null +++ b/test/extensions/filters/http/wasm/wasm_filter_headers_test.cc @@ -0,0 +1,374 @@ +#include "source/common/common/cleanup.h" + +#include "test/extensions/filters/http/wasm/wasm_filter_test_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Wasm { + +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::host_test_runtime_and_language_values, + Envoy::Extensions::Common::Wasm::wasmTestParamsToString); + +TEST_P(WasmHttpFilterTest, BadCode) { + setupBase(std::get<0>(GetParam()), "bad code", createContextFn()); + EXPECT_EQ(wasm_, nullptr); +} + +// Script touching headers only, request that is headers only. + +TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnlyWithEnvVars) { + envoy::extensions::wasm::v3::EnvironmentVariables envs; + const std::string host_env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV"; + if (std::get<0>(GetParam()) != "null") { + // Setup env vars. + const std::string host_env_value = "foo"; + const std::string env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV"; + const std::string env_value = "bar"; + TestEnvironment::setEnvVar(host_env_key, host_env_value, 0); + envs.mutable_host_env_keys()->Add(host_env_key.c_str()); + (*envs.mutable_key_values())[env_key] = env_value; + } + Cleanup unset_env([&host_env_key]() { TestEnvironment::unsetEnvVar(host_env_key); }); + setupTest("", "headers", envs); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + if (std::get<0>(GetParam()) != "null") { + EXPECT_CALL(filter(), + log_(spdlog::level::trace, Eq("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV: foo\n" + "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV: bar"))); + } + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + // Verify that route cache is cleared when modifying HTTP request headers. + NiceMock decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(2); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); + // Test some errors. + EXPECT_EQ(filter().continueStream(static_cast(9999)), + proxy_wasm::WasmResult::BadArgument); + EXPECT_EQ(filter().closeStream(static_cast(9999)), + proxy_wasm::WasmResult::BadArgument); + Http::TestResponseHeaderMapImpl response_headers; + EXPECT_EQ(filter().encode1xxHeaders(response_headers), Http::Filter1xxHeadersStatus::Continue); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AllHeadersAndTrailers) { + setupTest("", "headers"); + setupFilter(); + + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + // Verify that route cache is cleared when modifying HTTP request headers. + NiceMock decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(2); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().decodeTrailers(request_trailers)); + Http::MetadataMap request_metadata{}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().decodeMetadata(request_metadata)); + + // Verify that route cache is NOT cleared when modifying HTTP response headers. + EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(0); + + Http::TestResponseHeaderMapImpl response_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + EXPECT_THAT(response_headers.get_("test-status"), Eq("OK")); + Http::TestResponseTrailerMapImpl response_trailers{}; + EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter().encodeTrailers(response_trailers)); + Http::MetadataMap response_metadata{}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().encodeMetadata(response_metadata)); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AddTrailers) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody data")))).Times(2); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); + + Buffer::OwnedImpl data("data"); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).Times(0); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, false)); + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); + + Http::TestResponseHeaderMapImpl response_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + EXPECT_THAT(response_headers.get_("test-status"), Eq("OK")); + + Http::TestResponseTrailerMapImpl response_trailers{}; + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).Times(0); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, false)); + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).WillOnce(ReturnRef(response_trailers)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); + EXPECT_THAT(response_trailers.get_("newtrailer"), Eq("response")); + + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AllHeadersAndTrailersNotStarted) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().decodeTrailers(request_trailers)); + Http::MetadataMap request_metadata{}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().decodeMetadata(request_metadata)); + Http::TestResponseHeaderMapImpl response_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + Http::TestResponseTrailerMapImpl response_trailers{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().encodeTrailers(response_trailers)); + Http::MetadataMap response_metadata{}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().encodeMetadata(response_metadata)); + Buffer::OwnedImpl data("data"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, false)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, false)); + filter().onDestroy(); +} + +// Script touching headers only, request that is headers only. + +TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_FALSE(filter().endOfStream(proxy_wasm::WasmStreamType::Request)); + Buffer::OwnedImpl data("hello"); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, HeadersStopAndContinue) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. + return; + } + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, true)); + root_context_->onTick(0); + filter().clearRouteCache(); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, HeadersStopAndContinueAllowStopIteration) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. + return; + } + setAllowOnHeadersStopIteration(true); + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter().decodeHeaders(request_headers, true)); + root_context_->onTick(0); + filter().clearRouteCache(); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); + filter().onDestroy(); +} + +#if 0 + +TEST_P(WasmHttpFilterTest, HeadersStopAndEndStream) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. + return; + } + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"server", "envoy-wasm-end-stream"}}; + EXPECT_EQ(Http::FilterHeadersStatus::ContinueAndEndStream, + filter().decodeHeaders(request_headers, true)); + root_context_->onTick(0); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); + filter().onDestroy(); +} +#endif + +TEST_P(WasmHttpFilterTest, HeadersStopAndBuffer) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. + return; + } + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"server", "envoy-wasm-stop-buffer"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, + filter().decodeHeaders(request_headers, true)); + root_context_->onTick(0); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, HeadersStopAndWatermark) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. + return; + } + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, + {"server", "envoy-wasm-stop-watermark"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, true)); + root_context_->onTick(0); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); + filter().onDestroy(); +} + +// Script that reads the body. + +TEST_P(WasmHttpFilterTest, AccessLog) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + Http::TestResponseTrailerMapImpl response_trailers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + filter().continueStream(proxy_wasm::WasmStreamType::Response); + filter().closeStream(proxy_wasm::WasmStreamType::Response); + StreamInfo::MockStreamInfo log_stream_info; + EXPECT_CALL(log_stream_info, requestComplete()) + .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); + filter().log({&request_headers, &response_headers, &response_trailers}, log_stream_info); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AccessLogClientDisconnected) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / ")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + StreamInfo::MockStreamInfo log_stream_info; + EXPECT_CALL(log_stream_info, requestComplete()) + .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); + filter().log({&request_headers}, log_stream_info); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AccessLogDisabledForIncompleteStream) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / ")))).Times(0); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + + StreamInfo::MockStreamInfo log_stream_info; + EXPECT_CALL(log_stream_info, requestComplete()).WillRepeatedly(testing::Return(std::nullopt)); + filter().log({&request_headers}, log_stream_info); + filter().onDestroy(); +} + +TEST_P(WasmHttpFilterTest, AccessLogCreate) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + StreamInfo::MockStreamInfo log_stream_info; + EXPECT_CALL(log_stream_info, requestComplete()) + .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + Http::TestResponseTrailerMapImpl response_trailers{}; + filter().log({&request_headers, &response_headers, &response_trailers}, log_stream_info); + filter().onDestroy(); +} + +} // namespace Wasm +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc index 198d700502485..518274d92bcd4 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc @@ -52,6 +52,7 @@ class WasmFilterIntegrationTest void setupWasmFilter(const std::string& config, const std::string& root_id = "") { bool downstream = std::get<2>(GetParam()); + const std::string module = root_id.empty() ? "headers" : root_id; const std::string yaml = TestEnvironment::substitute(absl::StrCat( R"EOF( name: envoy.filters.http.wasm @@ -65,13 +66,15 @@ class WasmFilterIntegrationTest vm_id: "vm_id" runtime: envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF( + allow_precompiled: true configuration: "@type": type.googleapis.com/google.protobuf.StringValue value: )EOF", config, R"EOF( code: local: - filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/test_cpp.wasm" + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/)EOF", + module, R"EOF(_cpp.wasm" )EOF")); config_helper_.prependFilter(yaml, downstream); @@ -155,6 +158,34 @@ INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterIntegrationTest, Common::Wasm::wasmDualFilterWithCodecsTestParamsToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmFilterIntegrationTest); +TEST_P(WasmFilterIntegrationTest, LoadFromSourceWithoutPrecompiled) { + if (std::get<0>(GetParam()) != "v8" || !std::get<2>(GetParam()) || + std::get<3>(GetParam()) != Http::CodecType::HTTP1) { + GTEST_SKIP() << "A single V8 downstream HTTP1 source-loading canary"; + } + config_helper_.prependFilter(TestEnvironment::substitute(R"EOF( +name: envoy.filters.http.wasm +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + name: source_canary + vm_config: + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp_source.wasm" +)EOF")); + HttpIntegrationTest::initialize(); + + const Http::TestRequestHeaderMapImpl request_headers{ + {":scheme", "http"}, {":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + const Http::TestRequestHeaderMapImpl expected_request_headers{ + {":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + const Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + runTest(request_headers, {}, expected_request_headers, "", response_headers, {}, response_headers, + ""); +} + TEST_P(WasmFilterIntegrationTest, HeadersManipulation) { setupWasmFilter("headers"); HttpIntegrationTest::initialize(); @@ -441,6 +472,7 @@ name: envoy.filters.http.wasm base_interval: 0.01s vm_config: runtime: envoy.wasm.runtime.v8 + allow_precompiled: true code: local: filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/deferred_data_rust.wasm" diff --git a/test/extensions/filters/http/wasm/wasm_filter_misc_test.cc b/test/extensions/filters/http/wasm/wasm_filter_misc_test.cc new file mode 100644 index 0000000000000..6d3b91fc746a4 --- /dev/null +++ b/test/extensions/filters/http/wasm/wasm_filter_misc_test.cc @@ -0,0 +1,469 @@ +#include "test/extensions/filters/http/wasm/wasm_filter_test_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Wasm { + +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::host_test_runtime_and_language_values, + Envoy::Extensions::Common::Wasm::wasmTestParamsToString); + +TEST_P(WasmHttpFilterTest, Metadata) { +#ifdef WIN32 + // TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. + GTEST_SKIP() << "Skipping on Windows"; +#endif + setupTest("", "metadata"); + setupFilter(); + envoy::config::core::v3::Node node_data; + Protobuf::Value node_val; + node_val.set_string_value("wasm_node_get_value"); + (*node_data.mutable_metadata()->mutable_fields())["wasm_node_get_key"] = node_val; + (*node_data.mutable_metadata()->mutable_fields())["wasm_node_list_key"] = + ValueUtil::listValue({node_val}); + EXPECT_CALL(local_info_, node()).WillRepeatedly(ReturnRef(node_data)); + EXPECT_CALL(rootContext(), + log_(spdlog::level::debug, Eq(absl::string_view("onTick wasm_node_get_value")))); + + EXPECT_CALL(filter(), + log_(spdlog::level::err, Eq(absl::string_view("onBody wasm_node_get_value")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), + log_(spdlog::level::trace, + Eq(absl::string_view("Struct wasm_request_get_value wasm_request_get_value")))); + if (std::get<1>(GetParam()) != "rust") { + // TODO(PiotrSikora): not yet supported in the Rust SDK. + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("server is envoy-wasm")))); + } + + request_stream_info_.metadata_.mutable_filter_metadata()->insert( + Protobuf::MapPair( + "envoy.filters.http.wasm", + MessageUtil::keyValueStruct("wasm_request_get_key", "wasm_request_get_value"))); + + rootContext().onTick(0); + + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + std::optional dur = std::chrono::nanoseconds(15000000); + EXPECT_CALL(request_stream_info_, requestComplete()).WillRepeatedly(Return(dur)); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("duration is 15000000")))); + if (std::get<1>(GetParam()) != "rust") { + // TODO(PiotrSikora): not yet supported in the Rust SDK. + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("grpc service: test")))); + } + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"biz", "baz"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + + filter().log({&request_headers}, request_stream_info_); + + const auto* result = + request_stream_info_.filterState()->getDataReadOnly( + "wasm.wasm_request_set_key"); + EXPECT_EQ("wasm_request_set_value", result->value()); + + filter().onDestroy(); + filter().onDestroy(); // Does nothing. +} + +TEST_P(WasmHttpFilterTest, Property) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): test not yet implemented using Rust SDK. + return; + } + envoy::config::core::v3::Node node_data; + Protobuf::Value node_val; + node_val.set_string_value("sample_data"); + (*node_data.mutable_metadata()->mutable_fields())["istio.io/metadata"] = node_val; + EXPECT_CALL(local_info_, node()).WillRepeatedly(ReturnRef(node_data)); + setupTest("", "property"); + setupFilter(); + request_stream_info_.metadata_.mutable_filter_metadata()->insert( + Protobuf::MapPair( + "envoy.filters.http.wasm", + MessageUtil::keyValueStruct("wasm_request_get_key", "wasm_request_get_value"))); + EXPECT_CALL(request_stream_info_, responseCode()).WillRepeatedly(Return(403)); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + + // test outputs should match inputs + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("request.path: /test_context")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("node.metadata: sample_data")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("metadata: wasm_request_get_value")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("response.code: 403")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("state: wasm_value")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("upstream host metadata: endpoint")))); + + root_context_->onTick(0); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/test_context"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + request_stream_info_.route_name_ = "route12"; + request_stream_info_.downstream_connection_info_provider_->setRequestedServerName("w3.org"); + NiceMock connection; + EXPECT_CALL(connection, id()).WillRepeatedly(Return(4)); + EXPECT_CALL(encoder_callbacks_, connection()) + .WillRepeatedly(Return(OptRef{connection})); + std::shared_ptr route{new NiceMock()}; + request_stream_info_.route_ = route; + std::shared_ptr> host_description( + new NiceMock()); + auto metadata = std::make_shared( + TestUtility::parseYaml( + R"EOF( + filter_metadata: + namespace: + key: endpoint + )EOF")); + EXPECT_CALL(*host_description, metadata()).WillRepeatedly(Return(metadata)); + request_stream_info_.upstreamInfo()->setUpstreamHost(host_description); + EXPECT_CALL(request_stream_info_, requestComplete()) + .WillRepeatedly(Return(std::chrono::milliseconds(30))); + filter().log({&request_headers}, request_stream_info_); +} + +TEST_P(WasmHttpFilterTest, ClusterMetadata) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): test not yet implemented using Rust SDK. + return; + } + setupTest("", "cluster_metadata"); + setupFilter(); + auto cluster = std::make_shared>(); + auto cluster_metadata = std::make_shared( + TestUtility::parseYaml( + R"EOF( + filter_metadata: + namespace: + key: cluster + )EOF")); + + std::shared_ptr> host_description( + new NiceMock()); + Http::TestRequestHeaderMapImpl request_headers{{}}; + + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(*cluster, metadata()).WillRepeatedly(ReturnRef(*cluster_metadata)); + EXPECT_CALL(request_stream_info_, requestComplete) + .WillRepeatedly(Return(std::chrono::milliseconds(30))); + request_stream_info_.upstream_cluster_info_ = cluster; + EXPECT_CALL(request_stream_info_, upstreamClusterInfo()).Times(testing::AnyNumber()); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("cluster metadata: cluster")))); + filter().log({&request_headers}, request_stream_info_); +} + +TEST_P(WasmHttpFilterTest, SharedData) { + setupTest("shared_data"); + EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq(absl::string_view("set CasMismatch")))); + EXPECT_CALL(rootContext(), + log_(spdlog::level::debug, Eq(absl::string_view("get 1 shared_data_value1")))); + if (std::get<1>(GetParam()) == "rust") { + EXPECT_CALL(rootContext(), + log_(spdlog::level::warn, Eq(absl::string_view("get 2 shared_data_value2")))); + } else { + EXPECT_CALL(rootContext(), + log_(spdlog::level::critical, Eq(absl::string_view("get 2 shared_data_value2")))); + } + EXPECT_CALL(rootContext(), + log_(spdlog::level::debug, Eq(absl::string_view("get of bad key not found")))); + EXPECT_CALL(rootContext(), + log_(spdlog::level::debug, Eq(absl::string_view("second get of bad key not found")))); + rootContext().onTick(0); + rootContext().onQueueReady(0); +} + +TEST_P(WasmHttpFilterTest, SharedQueue) { + setupTest("shared_queue"); + setupFilter(); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, + Eq(absl::string_view("onRequestHeaders not found self/bad_shared_queue")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, + Eq(absl::string_view("onRequestHeaders not found vm_id/bad_shared_queue")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, + Eq(absl::string_view("onRequestHeaders not found bad_vm_id/bad_shared_queue")))); + EXPECT_CALL(filter(), log_(spdlog::level::warn, + Eq(absl::string_view("onRequestHeaders found self/my_shared_queue")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, + Eq(absl::string_view("onRequestHeaders found vm_id/my_shared_queue")))); + EXPECT_CALL(filter(), + log_(spdlog::level::warn, Eq(absl::string_view("onRequestHeaders enqueue Ok")))); + EXPECT_CALL(rootContext(), + log_(spdlog::level::warn, Eq(absl::string_view("onQueueReady bad token not found")))) + .Times(2); + EXPECT_CALL(rootContext(), + log_(spdlog::level::warn, Eq(absl::string_view("onQueueReady extra data not found")))) + .Times(2); + EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq(absl::string_view("onQueueReady")))) + .Times(2); + EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq(absl::string_view("data data1 Ok")))); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + auto token = proxy_wasm::resolveQueueForTest("vm_id", "my_shared_queue"); + root_context_->onQueueReady(token); +} + +// Script using a root_id which is not registered. + +TEST_P(WasmHttpFilterTest, RootIdNotRegistered) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. + return; + } + setupTest(); + setupFilter(); + Http::TestRequestHeaderMapImpl request_headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); +} + +// Script using an explicit root_id which is registered. + +TEST_P(WasmHttpFilterTest, RootId1) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. + return; + } + setupTest("context1"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders1 2")))); + Http::TestRequestHeaderMapImpl request_headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); +} + +// Script using an explicit root_id which is registered. + +TEST_P(WasmHttpFilterTest, RootId2) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. + return; + } + setupTest("context2"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders2 2")))); + Http::TestRequestHeaderMapImpl request_headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, true)); +} + +TEST_P(WasmHttpFilterTest, PanicOnRequestHeaders) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().onRequestHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnRequestBody) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onRequestBody(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnRequestTrailers) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onRequestTrailers(0)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseHeaders) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().onResponseHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseBody) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onResponseBody(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseTrailers) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + + // In the case of VM failure, failStream is called for both request and response stream types, + // so we need to make sure that we don't send the local reply twice. + EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, + Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + Eq("wasm_fail_stream"))); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onResponseTrailers(0)); +} + +TEST_P(WasmHttpFilterTest, CloseRequest) { + setupTest("close_stream"); + setupFilter(); + NiceMock stream_info; + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); + EXPECT_CALL(decoder_callbacks, resetStream(_, _)); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onRequestHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, CloseResponse) { + setupTest("close_stream"); + setupFilter(); + NiceMock stream_info; + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); + EXPECT_CALL(encoder_callbacks, resetStream(_, _)); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onResponseHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, VerifySignature) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(patricio78): test not yet implemented using Rust SDK. + return; + } + setupTest("", "verify_signature"); + setupFilter(); + EXPECT_CALL(rootContext(), + log_(spdlog::level::info, Eq(absl::string_view("signature is valid")))); + + EXPECT_CALL(rootContext(), + log_(spdlog::level::err, Eq(absl::string_view("unknown is not supported.")))); + EXPECT_CALL(rootContext(), log_(spdlog::level::err, + Eq(absl::string_view("Failed to initialize digest verify.")))) + .Times(2); + rootContext().onTick(0); +} + +TEST_P(WasmHttpFilterTest, Sign) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(patricio78): test not yet implemented using Rust SDK. + return; + } + setupTest("", "sign"); + setupFilter(); + EXPECT_CALL(rootContext(), + log_(spdlog::level::info, Eq(absl::string_view("signature created successfully")))); + + EXPECT_CALL(rootContext(), + log_(spdlog::level::err, Eq(absl::string_view("unknown is not supported.")))); + EXPECT_CALL( + rootContext(), + log_(spdlog::level::err, + Eq(absl::string_view("Invalid key type: private key required for signing operation.")))); + rootContext().onTick(0); +} + +TEST_P(WasmHttpFilterTest, SignAndVerifySignature) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(patricio78): test not yet implemented using Rust SDK. + return; + } + setupTest("", "sign_and_verify_signature"); + setupFilter(); + EXPECT_CALL(rootContext(), + log_(spdlog::level::info, + Eq(absl::string_view("signature created successfully, length: 256")))); + EXPECT_CALL(rootContext(), + log_(spdlog::level::info, + Eq(absl::string_view( + "end-to-end test passed: signature created and verified successfully")))); + EXPECT_CALL( + rootContext(), + log_(spdlog::level::info, + Eq(absl::string_view("mutual exclusion test passed: both PEM and DER keys rejected")))); + rootContext().onTick(0); +} + +} // namespace Wasm +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc deleted file mode 100644 index a3c1efc3704f6..0000000000000 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ /dev/null @@ -1,2254 +0,0 @@ -#include "envoy/grpc/async_client.h" - -#include "source/common/http/message_impl.h" -#include "source/extensions/filters/http/wasm/wasm_filter.h" - -#include "test/extensions/common/wasm/wasm_runtime.h" -#include "test/mocks/network/connection.h" -#include "test/mocks/router/mocks.h" -#include "test/test_common/wasm_base.h" - -#include "gmock/gmock.h" - -using testing::_; -using testing::Contains; -using testing::Eq; -using testing::InSequence; -using testing::Invoke; -using testing::Return; -using testing::ReturnRef; - -MATCHER_P(MapEq, rhs, "") { - const Envoy::Protobuf::Struct& obj = arg; - EXPECT_TRUE(rhs.size() > 0); - for (auto const& entry : rhs) { - EXPECT_THAT(obj.fields(), Contains(IsStructString(entry.first, entry.second))); - } - return true; -} - -using BufferFunction = std::function; - -#include "test/test_common/struct_matchers.h" - -namespace Envoy { -namespace Extensions { -namespace HttpFilters { -namespace Wasm { - -using Envoy::Extensions::Common::Wasm::CreateContextFn; -using Envoy::Extensions::Common::Wasm::Plugin; -using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; -using Envoy::Extensions::Common::Wasm::Wasm; -using Envoy::Extensions::Common::Wasm::WasmHandleSharedPtr; -using proxy_wasm::ContextBase; -using GrpcService = envoy::config::core::v3::GrpcService; -using WasmFilterConfig = envoy::extensions::filters::http::wasm::v3::Wasm; - -class TestFilter : public Envoy::Extensions::Common::Wasm::Context { -public: - TestFilter(Wasm* wasm, uint32_t root_context_id, PluginHandleSharedPtr plugin_handle) - : Envoy::Extensions::Common::Wasm::Context(wasm, root_context_id, plugin_handle) {} - MOCK_CONTEXT_LOG_; -}; - -class TestRoot : public Envoy::Extensions::Common::Wasm::Context { -public: - TestRoot(Wasm* wasm, const std::shared_ptr& plugin) : Context(wasm, plugin) {} - MOCK_CONTEXT_LOG_; -}; - -class WasmHttpFilterTest : public Envoy::Extensions::Common::Wasm::WasmHttpFilterTestBase< - testing::TestWithParam>> { -public: - WasmHttpFilterTest() = default; - ~WasmHttpFilterTest() override = default; - - CreateContextFn createContextFn() { - return [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { - return new TestRoot(wasm, plugin); - }; - } - - void setupTest(std::string root_id = "", std::string vm_configuration = "", - envoy::extensions::wasm::v3::EnvironmentVariables envs = {}) { - std::string code; - if (std::get<0>(GetParam()) == "null") { - code = "HttpWasmTestCpp"; - } else { - if (std::get<1>(GetParam()) == "cpp") { - code = TestEnvironment::readFileToStringForTest(TestEnvironment::runfilesPath( - "test/extensions/filters/http/wasm/test_data/test_cpp.wasm")); - } else { - auto filename = !root_id.empty() ? root_id : vm_configuration; - const auto basic_path = TestEnvironment::runfilesPath( - absl::StrCat("test/extensions/filters/http/wasm/test_data/", filename)); - code = TestEnvironment::readFileToStringForTest(basic_path + "_rust.wasm"); - } - } - - setRootId(root_id); - setEnvs(envs); - setVmConfiguration(vm_configuration); - setupBase(std::get<0>(GetParam()), code, createContextFn()); - } - - void setupFilter() { setupFilterBase(); } - - void setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks, std::string id); - - TestRoot& rootContext() { return *static_cast(root_context_); } - TestFilter& filter() { return *static_cast(context_.get()); } - -protected: - NiceMock async_stream_; - Grpc::MockAsyncClientManager async_client_manager_; -}; - -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, - Envoy::Extensions::Common::Wasm::runtime_and_language_values, - Envoy::Extensions::Common::Wasm::wasmTestParamsToString); - -// Bad code in initial config. -TEST_P(WasmHttpFilterTest, BadCode) { - setupBase(std::get<0>(GetParam()), "bad code", createContextFn()); - EXPECT_EQ(wasm_, nullptr); -} - -// Script touching headers only, request that is headers only. -TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersOnlyWithEnvVars) { - envoy::extensions::wasm::v3::EnvironmentVariables envs; - if (std::get<0>(GetParam()) != "null") { - // Setup env vars. - const std::string host_env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV"; - const std::string host_env_value = "foo"; - const std::string env_key = "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV"; - const std::string env_value = "bar"; - TestEnvironment::setEnvVar(host_env_key, host_env_value, 0); - envs.mutable_host_env_keys()->Add(host_env_key.c_str()); - (*envs.mutable_key_values())[env_key] = env_value; - } - setupTest("", "headers", envs); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - if (std::get<0>(GetParam()) != "null") { - EXPECT_CALL(filter(), - log_(spdlog::level::trace, Eq("ENVOY_HTTP_WASM_TEST_HEADERS_HOST_ENV: foo\n" - "ENVOY_HTTP_WASM_TEST_HEADERS_KEY_VALUE_ENV: bar"))); - } - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - // Verify that route cache is cleared when modifying HTTP request headers. - NiceMock decoder_callbacks; - filter().setDecoderFilterCallbacks(decoder_callbacks); - EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(2); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); - // Test some errors. - EXPECT_EQ(filter().continueStream(static_cast(9999)), - proxy_wasm::WasmResult::BadArgument); - EXPECT_EQ(filter().closeStream(static_cast(9999)), - proxy_wasm::WasmResult::BadArgument); - Http::TestResponseHeaderMapImpl response_headers; - EXPECT_EQ(filter().encode1xxHeaders(response_headers), Http::Filter1xxHeadersStatus::Continue); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AllHeadersAndTrailers) { - setupTest("", "headers"); - setupFilter(); - - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - // Verify that route cache is cleared when modifying HTTP request headers. - NiceMock decoder_callbacks; - filter().setDecoderFilterCallbacks(decoder_callbacks); - EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(2); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); - Http::TestRequestTrailerMapImpl request_trailers{}; - EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().decodeTrailers(request_trailers)); - Http::MetadataMap request_metadata{}; - EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().decodeMetadata(request_metadata)); - - // Verify that route cache is NOT cleared when modifying HTTP response headers. - EXPECT_CALL(decoder_callbacks.downstream_callbacks_, clearRouteCache()).Times(0); - - Http::TestResponseHeaderMapImpl response_headers{}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - EXPECT_THAT(response_headers.get_("test-status"), Eq("OK")); - Http::TestResponseTrailerMapImpl response_trailers{}; - EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter().encodeTrailers(response_trailers)); - Http::MetadataMap response_metadata{}; - EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().encodeMetadata(response_metadata)); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AddTrailers) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody data")))).Times(2); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); - - Buffer::OwnedImpl data("data"); - Http::TestRequestTrailerMapImpl request_trailers{}; - EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).Times(0); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, false)); - EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); - - Http::TestResponseHeaderMapImpl response_headers{}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - EXPECT_THAT(response_headers.get_("test-status"), Eq("OK")); - - Http::TestResponseTrailerMapImpl response_trailers{}; - EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).Times(0); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, false)); - EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).WillOnce(ReturnRef(response_trailers)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); - EXPECT_THAT(response_trailers.get_("newtrailer"), Eq("response")); - - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AllHeadersAndTrailersNotStarted) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - Http::TestRequestTrailerMapImpl request_trailers{}; - EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().decodeTrailers(request_trailers)); - Http::MetadataMap request_metadata{}; - EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().decodeMetadata(request_metadata)); - Http::TestResponseHeaderMapImpl response_headers{}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - Http::TestResponseTrailerMapImpl response_trailers{}; - EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter().encodeTrailers(response_trailers)); - Http::MetadataMap response_metadata{}; - EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter().encodeMetadata(response_metadata)); - Buffer::OwnedImpl data("data"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, false)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, false)); - filter().onDestroy(); -} - -// Script touching headers only, request that is headers only. -TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - EXPECT_FALSE(filter().endOfStream(proxy_wasm::WasmStreamType::Request)); - Buffer::OwnedImpl data("hello"); - Http::TestRequestTrailerMapImpl request_trailers{}; - EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, HeadersStopAndContinue) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. - return; - } - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, true)); - root_context_->onTick(0); - filter().clearRouteCache(); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, HeadersStopAndContinueAllowStopIteration) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. - return; - } - setAllowOnHeadersStopIteration(true); - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, - filter().decodeHeaders(request_headers, true)); - root_context_->onTick(0); - filter().clearRouteCache(); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); - filter().onDestroy(); -} - -#if 0 -TEST_P(WasmHttpFilterTest, HeadersStopAndEndStream) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. - return; - } - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"server", "envoy-wasm-end-stream"}}; - EXPECT_EQ(Http::FilterHeadersStatus::ContinueAndEndStream, - filter().decodeHeaders(request_headers, true)); - root_context_->onTick(0); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); - filter().onDestroy(); -} -#endif - -TEST_P(WasmHttpFilterTest, HeadersStopAndBuffer) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. - return; - } - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"server", "envoy-wasm-stop-buffer"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, - filter().decodeHeaders(request_headers, true)); - root_context_->onTick(0); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, HeadersStopAndWatermark) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK. - return; - } - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"server", "envoy-wasm-stop-watermark"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, true)); - root_context_->onTick(0); - EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); - EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue")); - filter().onDestroy(); -} - -// Script that reads the body. -TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReadBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that prepends and appends to the body. -TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("onBody prepend.prepend.hello.append.append")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "PrependAndAppendToBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - if (std::get<1>(GetParam()) == "rust") { - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); - } else { - // This status is not available in the rust SDK. - // TODO: update all SDKs to the new revision of the spec and update the tests accordingly. - EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter().decodeData(data, true)); - EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter().encodeData(data, true)); - } - filter().onDestroy(); -} - -// Script that replaces the body. -TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))).Times(2); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "ReplaceBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - if (std::get<1>(GetParam()) == "rust") { - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); - } else { - // This status is not available in the rust SDK. - // TODO: update all SDKs to the new revision of the spec and update the tests accordingly. - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter().decodeData(data, true)); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter().encodeData(data, true)); - } - filter().onDestroy(); -} - -// Script that replaces the first character in the body. -TEST_P(WasmHttpFilterTest, BodyRequestPartialReplaceBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody partial.replace.ello")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("onBody partial.replace.artial.replace.ello")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "PartialReplaceBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); - filter().onDestroy(); -} - -// Script that removes the body. -TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "RemoveBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that removes the first character from the body. -TEST_P(WasmHttpFilterTest, BodyRequestPartialRemoveBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ello")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "PartialRemoveBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that buffers the body. -TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { - setupTest("body"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "BufferBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - Buffer::OwnedImpl bufferedBody; - EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); - EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) - .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); - - Buffer::OwnedImpl data1("hello"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); - // The first call to decodeData() will not result in the addDecodedData() callback being called - // because the filter doesn't yet know if the VM's onRequestBody() will return - // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. - bufferedBody.add(data1); - - // The second call to decodeData() will result in the addDecodedData() callback being called - // because the VM's onRequestBody() returned StopIterationAndBuffer. - EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) - .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); - - Buffer::OwnedImpl data2(" again "); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello again ")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); - - EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) - .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); - - Buffer::OwnedImpl data3("hello"); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody hello again hello")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data3, true)); - - // Verify that the response still works even though we buffered the request. - Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, - {"x-test-operation", "ReadBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - // Should not buffer this time - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))).Times(2); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data1, false)); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data1, true)); - - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, BodyRequestBufferBodyAndDestroyFilterInTheAddDecodedDataCallback) { - setupTest("body"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "BufferBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - Buffer::OwnedImpl bufferedBody; - EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); - EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) - .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); - - Buffer::OwnedImpl data1("hello"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); - // The first call to decodeData() will not result in the addDecodedData() callback being called - // because the filter doesn't yet know if the VM's onRequestBody() will return - // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. - bufferedBody.add(data1); - - // The second call to decodeData() will result in the addDecodedData() callback being called - // because the VM's onRequestBody() returned StopIterationAndBuffer. - EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) - .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); - - Buffer::OwnedImpl data2(" again "); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello again ")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); - - // The addDecodedData() may result in buffer overflow and the local reply will be sent. It may - // destroy the filter. We mock the onDestroy() to ensure the filter will not call into the VM - // after it is destroyed. - EXPECT_CALL(decoder_callbacks_, addDecodedData(_, _)) - .WillOnce(Invoke([this](Buffer::Instance&, bool) { filter().onDestroy(); })); - - Buffer::OwnedImpl data3("hello"); - EXPECT_CALL(filter(), log_(_, _)).Times(0); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data3, true)); -} - -// Script that prepends and appends to the buffered body. -TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); - Http::TestRequestHeaderMapImpl request_headers{ - {":path", "/"}, {"x-test-operation", "PrependAndAppendToBufferedBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that replaces the buffered body. -TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "ReplaceBufferedBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that replaces the first character in the buffered body. -TEST_P(WasmHttpFilterTest, BodyRequestPartialReplaceBufferedBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody partial.replace.ello")))); - Http::TestRequestHeaderMapImpl request_headers{ - {":path", "/"}, {"x-test-operation", "PartialReplaceBufferedBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that removes the buffered body. -TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "RemoveBufferedBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that removes the first character from the buffered body. -TEST_P(WasmHttpFilterTest, BodyRequestPartialRemoveBufferedBody) { - setupTest("body"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ello")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, - {"x-test-operation", "PartialRemoveBufferedBody"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - filter().onDestroy(); -} - -// Script that buffers the first part of the body and streams the rest -TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { - setupTest("body"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - Buffer::OwnedImpl bufferedBody; - EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(&bufferedBody)); - EXPECT_CALL(decoder_callbacks_, modifyDecodingBuffer(_)) - .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); - - Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, - {"x-test-operation", "BufferTwoBodies"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - - Buffer::OwnedImpl data1("hello"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data1, false)); - bufferedBody.add(data1); - - Buffer::OwnedImpl data2(", there, "); - bufferedBody.add(data2); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, ")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().decodeData(data2, false)); - - // Previous callbacks returned "Buffer" so we have buffered so far - Buffer::OwnedImpl data3("world!"); - bufferedBody.add(data3); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, world!")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data3, false)); - - // Last callback returned "continue" so we just see individual chunks. - Buffer::OwnedImpl data4("So it's "); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody So it's ")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data4, false)); - - Buffer::OwnedImpl data5("goodbye, then!"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody goodbye, then!")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data5, true)); - - filter().onDestroy(); -} - -// Script that buffers the first part of the body and streams the rest -TEST_P(WasmHttpFilterTest, BodyResponseBufferThenStreamBody) { - setupTest("body"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - Buffer::OwnedImpl bufferedBody; - EXPECT_CALL(encoder_callbacks_, modifyEncodingBuffer(_)) - .WillRepeatedly(Invoke([&bufferedBody](BufferFunction f) { f(bufferedBody); })); - - Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, - {"x-test-operation", "BufferTwoBodies"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); - - Buffer::OwnedImpl data1("hello"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().encodeData(data1, false)); - // The first call to encodeData() will not result in the addEncodedData() callback being called - // because the filter doesn't yet know if the VM's onResponseBody() will return - // StopIterationAndBuffer or Continue. Add the data to the buffer manually for the test. - bufferedBody.add(data1); - - // The second call to encodeData() will result in the addEncodedData() callback being called - // because the VM's onResponseBody() returned StopIterationAndBuffer. - EXPECT_CALL(encoder_callbacks_, addEncodedData(_, _)) - .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); - - Buffer::OwnedImpl data2(", there, "); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, ")))); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter().encodeData(data2, false)); - - EXPECT_CALL(encoder_callbacks_, addEncodedData(_, _)) - .WillOnce(Invoke([&bufferedBody](Buffer::Instance& data, bool) { bufferedBody.move(data); })); - - Buffer::OwnedImpl data3("world!"); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody hello, there, world!")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data3, false)); - - // Last callback returned "continue" so we just see individual chunks. - Buffer::OwnedImpl data4("So it's "); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody So it's ")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data4, false)); - - Buffer::OwnedImpl data5("goodbye, then!"); - EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody goodbye, then!")))); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data5, true)); - - filter().onDestroy(); -} - -// Script testing AccessLog::Instance::log. -TEST_P(WasmHttpFilterTest, AccessLog) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - Http::TestResponseTrailerMapImpl response_trailers{}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - filter().continueStream(proxy_wasm::WasmStreamType::Response); - filter().closeStream(proxy_wasm::WasmStreamType::Response); - StreamInfo::MockStreamInfo log_stream_info; - EXPECT_CALL(log_stream_info, requestComplete()) - .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); - filter().log({&request_headers, &response_headers, &response_trailers}, log_stream_info); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AccessLogClientDisconnected) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / ")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - StreamInfo::MockStreamInfo log_stream_info; - EXPECT_CALL(log_stream_info, requestComplete()) - .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); - filter().log({&request_headers}, log_stream_info); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AccessLogDisabledForIncompleteStream) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / ")))).Times(0); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - - StreamInfo::MockStreamInfo log_stream_info; - EXPECT_CALL(log_stream_info, requestComplete()).WillRepeatedly(testing::Return(std::nullopt)); - filter().log({&request_headers}, log_stream_info); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AccessLogCreate) { - setupTest("", "headers"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onLog 2 / 200")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); - - StreamInfo::MockStreamInfo log_stream_info; - EXPECT_CALL(log_stream_info, requestComplete()) - .WillRepeatedly(testing::Return(std::chrono::milliseconds(30))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - Http::TestResponseTrailerMapImpl response_trailers{}; - filter().log({&request_headers, &response_headers, &response_trailers}, log_stream_info); - filter().onDestroy(); -} - -TEST_P(WasmHttpFilterTest, AsyncCall) { - setupTest("async_call"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - Http::AsyncClient::Callbacks* callbacks = nullptr; - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce(Invoke( - [&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { - EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, - {":path", "/"}, - {":authority", "foo"}, - {"content-length", "11"}}), - message->headers()); - EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); - EXPECT_EQ(options.send_xff, false); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response"))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(":status -> 200"))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) - .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { - Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response_message->body().add("response"); - NiceMock span; - Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; - callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); - callbacks->onSuccess(request, std::move(response_message)); - return proxy_wasm::WasmResult::Ok; - })); - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - EXPECT_NE(callbacks, nullptr); -} - -TEST_P(WasmHttpFilterTest, StopAndResumeViaAsyncCall) { - setupTest("resume_call"); - setupFilter(); - - InSequence s; - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - Http::AsyncClient::Callbacks* callbacks = nullptr; - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce( - Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, - {":path", "/"}, - {":authority", "foo"}, - {"content-length", "6"}}), - message->headers()); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) - .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { - Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - NiceMock span; - Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; - callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); - callbacks->onSuccess(request, std::move(response_message)); - return proxy_wasm::WasmResult::Ok; - })); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("continueRequest"))); - - Http::MockStreamDecoderFilterCallbacks decoder_callbacks; - filter().setDecoderFilterCallbacks(decoder_callbacks); - EXPECT_CALL(decoder_callbacks, continueDecoding()).WillOnce(Invoke([&]() { - // Verify that we're not resuming processing from within Wasm callback. - EXPECT_EQ(proxy_wasm::current_context_, nullptr); - })); - - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - EXPECT_NE(callbacks, nullptr); -} - -TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { - setupTest("async_call"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - // Just fail the send. - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce( - Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - return nullptr; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); -} - -TEST_P(WasmHttpFilterTest, AsyncCallBadCluster) { - setupTest("async_call"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/bad"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce( - Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response( - new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "503"}}})); - // Simulate code path for "no healthy host for HTTP connection pool" inline callback. - cb.onSuccess(request, std::move(response)); - return nullptr; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call rejected"))); - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); -} - -TEST_P(WasmHttpFilterTest, AsyncCallFailure) { - setupTest("async_call"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - Http::AsyncClient::Callbacks* callbacks = nullptr; - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce( - Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, - {":path", "/"}, - {":authority", "foo"}, - {"content-length", "11"}}), - message->headers()); - EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) - .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { - callbacks->onFailure(request, Http::AsyncClient::FailureReason::Reset); - return proxy_wasm::WasmResult::Ok; - })); - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("async_call failed"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq("async_call failed"))); - } - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - EXPECT_NE(callbacks, nullptr); -} - -TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { - setupTest("async_call"); - setupFilter(); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - Http::MockAsyncClientRequest request(&cluster_manager_.thread_local_cluster_.async_client_); - Http::AsyncClient::Callbacks* callbacks = nullptr; - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(cluster_manager_.thread_local_cluster_, httpAsyncClient()); - EXPECT_CALL(cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce( - Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, - {":path", "/"}, - {":authority", "foo"}, - {"content-length", "11"}}), - message->headers()); - EXPECT_EQ((Http::TestRequestTrailerMapImpl{{"trail", "cow"}}), *message->trailers()); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))); - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); - - // Destroy the Context, Plugin and VM. - context_.reset(); - plugin_.reset(); - plugin_handle_.reset(); - wasm_.reset(); - - Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response_message->body().add("response"); - - // (Don't) Make the callback on the destroyed VM. - EXPECT_EQ(callbacks, nullptr); - if (callbacks) { - callbacks->onSuccess(request, std::move(response_message)); - } -} - -TEST_P(WasmHttpFilterTest, GrpcCall) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_call"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_call_proto"); - } - for (const auto& id : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - NiceMock request; - Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - Tracing::Span* parent_span{}; - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([&](absl::string_view service_full_name, absl::string_view method_name, - Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, - Tracing::Span& span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(method_name, "method"); - Protobuf::Value value; - EXPECT_TRUE( - value.ParseFromArray(message->linearize(message->length()), message->length())); - EXPECT_EQ(value.string_value(), "request"); - callbacks = &cb; - parent_span = &span; - EXPECT_EQ(options.timeout->count(), 1000); - EXPECT_EQ(options.send_xff, false); - return &request; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response"))); - } - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - NiceMock span; - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onSuccessRaw(std::move(response), span); - } - } -} - -TEST_P(WasmHttpFilterTest, GrpcCallBadCall) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_call"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_call_proto"); - } - for (const auto& id : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("expected failure occurred")))); - } - - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce(Invoke([&](absl::string_view, absl::string_view, Buffer::InstancePtr&&, - Grpc::RawAsyncRequestCallbacks&, Tracing::Span&, - const Http::AsyncClient::RequestOptions&) -> Grpc::AsyncRequest* { - return nullptr; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); - } -} - -TEST_P(WasmHttpFilterTest, GrpcCallFailure) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_call"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_call_proto"); - } - for (const auto& id : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - NiceMock request; - Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - Tracing::Span* parent_span{}; - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([&](absl::string_view service_full_name, absl::string_view method_name, - Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, - Tracing::Span& span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(method_name, "method"); - Protobuf::Value value; - EXPECT_TRUE( - value.ParseFromArray(message->linearize(message->length()), message->length())); - EXPECT_EQ(value.string_value(), "request"); - callbacks = &cb; - parent_span = &span; - EXPECT_EQ(options.timeout->count(), 1000); - return &request; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("failure bad"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("failure bad"))); - } - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - // Test some additional error paths. - // 0xFF00 (HTTP call). - EXPECT_EQ(filter().grpcSend(0xFF00, "", false), proxy_wasm::WasmResult::BadArgument); - EXPECT_EQ(filter().grpcCancel(0xFF00), proxy_wasm::WasmResult::BadArgument); - EXPECT_EQ(filter().grpcClose(0xFF00), proxy_wasm::WasmResult::BadArgument); - - // 0xFF01 (gRPC call). - EXPECT_EQ(filter().grpcSend(0xFF01, "", false), proxy_wasm::WasmResult::BadArgument); - EXPECT_EQ(filter().grpcCancel(0xFF01), proxy_wasm::WasmResult::NotFound); - EXPECT_EQ(filter().grpcClose(0xFF01), proxy_wasm::WasmResult::NotFound); - - // 0xFF02 (gRPC stream). - EXPECT_EQ(filter().grpcSend(0xFF02, "", false), proxy_wasm::WasmResult::NotFound); - EXPECT_EQ(filter().grpcCancel(0xFF02), proxy_wasm::WasmResult::NotFound); - EXPECT_EQ(filter().grpcClose(0xFF02), proxy_wasm::WasmResult::NotFound); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - NiceMock span; - if (callbacks) { - callbacks->onFailure(Grpc::Status::WellKnownGrpcStatus::Canceled, "bad", span); - } - } -} - -TEST_P(WasmHttpFilterTest, GrpcCallCancel) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_call"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_call_proto"); - } - for (const auto& id : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - NiceMock request; - Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - Tracing::Span* parent_span{}; - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([&](absl::string_view service_full_name, absl::string_view method_name, - Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, - Tracing::Span& span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(method_name, "method"); - Protobuf::Value value; - EXPECT_TRUE( - value.ParseFromArray(message->linearize(message->length()), message->length())); - EXPECT_EQ(value.string_value(), "request"); - callbacks = &cb; - parent_span = &span; - EXPECT_EQ(options.timeout->count(), 1000); - return &request; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - rootContext().onQueueReady(0); - } -} - -TEST_P(WasmHttpFilterTest, GrpcCallClose) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_call"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_call_proto"); - } - for (const auto& id : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - NiceMock request; - Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - Tracing::Span* parent_span{}; - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([&](absl::string_view service_full_name, absl::string_view method_name, - Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, - Tracing::Span& span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(method_name, "method"); - Protobuf::Value value; - EXPECT_TRUE( - value.ParseFromArray(message->linearize(message->length()), message->length())); - EXPECT_EQ(value.string_value(), "request"); - callbacks = &cb; - parent_span = &span; - EXPECT_EQ(options.timeout->count(), 1000); - return &request; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - rootContext().onQueueReady(1); - } -} - -TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { - std::vector> proto_or_cluster; - proto_or_cluster.emplace_back("grpc_call", true); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.emplace_back("grpc_call_proto", false); - } - for (const auto& [id, success] : proto_or_cluster) { - setupTest(id); - setupFilter(); - - if (id == "grpc_call_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("bogus grpc_service accepted error")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("bogus grpc_service rejected")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - Grpc::MockAsyncRequest request; - Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; - Grpc::MockAsyncClientManager client_manager; - auto client_factory = std::make_unique(); - auto async_client = std::make_unique(); - Tracing::Span* parent_span{}; - EXPECT_CALL(*async_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([&](absl::string_view service_full_name, absl::string_view method_name, - Buffer::InstancePtr&& message, Grpc::RawAsyncRequestCallbacks& cb, - Tracing::Span& span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(method_name, "method"); - Protobuf::Value value; - EXPECT_TRUE( - value.ParseFromArray(message->linearize(message->length()), message->length())); - EXPECT_EQ(value.string_value(), "request"); - callbacks = &cb; - parent_span = &span; - EXPECT_EQ(options.timeout->count(), 1000); - return &request; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillOnce(Invoke([&]() -> Grpc::AsyncClientManager& { return client_manager; })); - EXPECT_CALL(client_manager, getOrCreateRawAsyncClient(_, _, _)) - .WillOnce( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - return std::move(async_client); - })); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); - - // Destroy the Context, Plugin and VM. - context_.reset(); - plugin_.reset(); - plugin_handle_.reset(); - wasm_.reset(); - } else { - rootContext().onQueueReady(2); - - // Start shutdown sequence. - wasm_->wasm()->startShutdown(); - plugin_.reset(); - plugin_handle_.reset(); - wasm_.reset(); - } - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - if (std::get<1>(GetParam()) == "rust") { - EXPECT_EQ(callbacks, nullptr); - } else { - EXPECT_NE(callbacks, nullptr); - } - NiceMock span; - if (callbacks) { - if (success) { - callbacks->onSuccessRaw(std::move(response), span); - } else { - callbacks->onFailure(Grpc::Status::WellKnownGrpcStatus::Canceled, "bad", span); - } - } - } -} - -void WasmHttpFilterTest::setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks, - std::string id) { - setupTest(id); - setupFilter(); - - EXPECT_CALL(async_client_manager_, getOrCreateRawAsyncClient(_, _, _)) - .WillRepeatedly( - Invoke([&](const GrpcService&, Stats::Scope&, bool) -> Grpc::RawAsyncClientSharedPtr { - auto async_client = std::make_unique(); - EXPECT_CALL(*async_client, startRaw(_, _, _, _)) - .WillRepeatedly(Invoke( - [&](absl::string_view service_full_name, absl::string_view method_name, - Grpc::RawAsyncStreamCallbacks& cb, - const Http::AsyncClient::StreamOptions& options) -> Grpc::RawAsyncStream* { - EXPECT_EQ(service_full_name, "service"); - EXPECT_EQ(options.send_xff, false); - if (method_name != "method") { - return nullptr; - } - callbacks = &cb; - return &async_stream_; - })); - return async_client; - })); - EXPECT_CALL(cluster_manager_, grpcAsyncClientManager()) - .WillRepeatedly(Invoke([&]() -> Grpc::AsyncClientManager& { return async_client_manager_; })); -} - -TEST_P(WasmHttpFilterTest, GrpcStream) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_stream"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_stream_proto"); - } - for (const auto& id : proto_or_cluster) { - Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; - setupGrpcStreamTest(callbacks, id); - - if (id == "grpc_stream_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close done"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close done"))); - } - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onReceiveInitialMetadata(std::make_unique()); - callbacks->onReceiveMessageRaw(std::move(response)); - callbacks->onReceiveTrailingMetadata(std::make_unique()); - callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "done"); - } - } -} - -// Local close followed by remote close. -TEST_P(WasmHttpFilterTest, GrpcStreamCloseLocal) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_stream"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_stream_proto"); - } - for (const auto& id : proto_or_cluster) { - Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; - setupGrpcStreamTest(callbacks, id); - - if (id == "grpc_stream_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response close"))); - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close ok"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response close"))); - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close ok"))); - } - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("close"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onReceiveInitialMetadata(std::make_unique()); - callbacks->onReceiveMessageRaw(std::move(response)); - callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "ok"); - } - } -} - -// Remote close followed by local close. -TEST_P(WasmHttpFilterTest, GrpcStreamCloseRemote) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_stream"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_stream_proto"); - } - for (const auto& id : proto_or_cluster) { - Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; - setupGrpcStreamTest(callbacks, id); - - if (id == "grpc_stream_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("close close"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close close"))); - } - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onReceiveInitialMetadata(std::make_unique()); - callbacks->onReceiveMessageRaw(std::move(response)); - callbacks->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "close"); - } - } -} - -TEST_P(WasmHttpFilterTest, GrpcStreamCancel) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_stream"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_stream_proto"); - } - for (const auto& id : proto_or_cluster) { - Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; - setupGrpcStreamTest(callbacks, id); - - if (id == "grpc_stream_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - NiceMock span; - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onReceiveInitialMetadata(std::make_unique( - Http::TestResponseHeaderMapImpl{{"test", "reset"}})); - } - } -} - -TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { - std::vector proto_or_cluster; - proto_or_cluster.push_back("grpc_stream"); - if (std::get<1>(GetParam()) == "cpp") { - // cluster definition passed as a protobuf is only supported in C++ SDK. - proto_or_cluster.push_back("grpc_stream_proto"); - } - for (const auto& id : proto_or_cluster) { - Grpc::RawAsyncStreamCallbacks* callbacks = nullptr; - setupGrpcStreamTest(callbacks, id); - - if (id == "grpc_stream_proto") { - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } else { - cluster_manager_.initializeThreadLocalClusters({"cluster"}); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus service parse failure")))); - EXPECT_CALL(filter(), log_(spdlog::level::err, - Eq(absl::string_view("expected bogus method call failure")))); - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("cluster call succeeded")))); - } - - // TODO(PiotrSikora): Switching back to the original context is inconsistent between SDKs. - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq("response response"))); - } else { - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); - } - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, false)); - - Protobuf::Value value; - value.set_string_value("response"); - std::string response_string; - EXPECT_TRUE(value.SerializeToString(&response_string)); - auto response = std::make_unique(response_string); - EXPECT_NE(callbacks, nullptr); - NiceMock span; - if (callbacks) { - callbacks->onCreateInitialMetadata(request_headers); - const auto source = request_headers.get(Http::LowerCaseString{"source"}); - EXPECT_EQ(source.size(), 1); - EXPECT_EQ(source[0]->value().getStringView(), id); - callbacks->onReceiveInitialMetadata(std::make_unique()); - callbacks->onReceiveMessageRaw(std::move(response)); - callbacks->onReceiveTrailingMetadata(std::make_unique()); - } - - // Destroy the Context, Plugin and VM. - context_.reset(); - plugin_.reset(); - plugin_handle_.reset(); - wasm_.reset(); - } -} - -// Test metadata access including CEL expressions. -TEST_P(WasmHttpFilterTest, Metadata) { -#ifdef WIN32 - // TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. - GTEST_SKIP() << "Skipping on Windows"; -#endif - setupTest("", "metadata"); - setupFilter(); - envoy::config::core::v3::Node node_data; - Protobuf::Value node_val; - node_val.set_string_value("wasm_node_get_value"); - (*node_data.mutable_metadata()->mutable_fields())["wasm_node_get_key"] = node_val; - (*node_data.mutable_metadata()->mutable_fields())["wasm_node_list_key"] = - ValueUtil::listValue({node_val}); - EXPECT_CALL(local_info_, node()).WillRepeatedly(ReturnRef(node_data)); - EXPECT_CALL(rootContext(), - log_(spdlog::level::debug, Eq(absl::string_view("onTick wasm_node_get_value")))); - - EXPECT_CALL(filter(), - log_(spdlog::level::err, Eq(absl::string_view("onBody wasm_node_get_value")))); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); - EXPECT_CALL(filter(), - log_(spdlog::level::trace, - Eq(absl::string_view("Struct wasm_request_get_value wasm_request_get_value")))); - if (std::get<1>(GetParam()) != "rust") { - // TODO(PiotrSikora): not yet supported in the Rust SDK. - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("server is envoy-wasm")))); - } - - request_stream_info_.metadata_.mutable_filter_metadata()->insert( - Protobuf::MapPair( - "envoy.filters.http.wasm", - MessageUtil::keyValueStruct("wasm_request_get_key", "wasm_request_get_value"))); - - rootContext().onTick(0); - - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - std::optional dur = std::chrono::nanoseconds(15000000); - EXPECT_CALL(request_stream_info_, requestComplete()).WillRepeatedly(Return(dur)); - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("duration is 15000000")))); - if (std::get<1>(GetParam()) != "rust") { - // TODO(PiotrSikora): not yet supported in the Rust SDK. - EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("grpc service: test")))); - } - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"biz", "baz"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - Buffer::OwnedImpl data("hello"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); - - filter().log({&request_headers}, request_stream_info_); - - const auto* result = - request_stream_info_.filterState()->getDataReadOnly( - "wasm.wasm_request_set_key"); - EXPECT_EQ("wasm_request_set_value", result->value()); - - filter().onDestroy(); - filter().onDestroy(); // Does nothing. -} - -TEST_P(WasmHttpFilterTest, Property) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): test not yet implemented using Rust SDK. - return; - } - envoy::config::core::v3::Node node_data; - Protobuf::Value node_val; - node_val.set_string_value("sample_data"); - (*node_data.mutable_metadata()->mutable_fields())["istio.io/metadata"] = node_val; - EXPECT_CALL(local_info_, node()).WillRepeatedly(ReturnRef(node_data)); - setupTest("", "property"); - setupFilter(); - request_stream_info_.metadata_.mutable_filter_metadata()->insert( - Protobuf::MapPair( - "envoy.filters.http.wasm", - MessageUtil::keyValueStruct("wasm_request_get_key", "wasm_request_get_value"))); - EXPECT_CALL(request_stream_info_, responseCode()).WillRepeatedly(Return(403)); - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - - // test outputs should match inputs - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("request.path: /test_context")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("node.metadata: sample_data")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("metadata: wasm_request_get_value")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("response.code: 403")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("state: wasm_value")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("upstream host metadata: endpoint")))); - - root_context_->onTick(0); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/test_context"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); - request_stream_info_.route_name_ = "route12"; - request_stream_info_.downstream_connection_info_provider_->setRequestedServerName("w3.org"); - NiceMock connection; - EXPECT_CALL(connection, id()).WillRepeatedly(Return(4)); - EXPECT_CALL(encoder_callbacks_, connection()) - .WillRepeatedly(Return(OptRef{connection})); - std::shared_ptr route{new NiceMock()}; - request_stream_info_.route_ = route; - std::shared_ptr> host_description( - new NiceMock()); - auto metadata = std::make_shared( - TestUtility::parseYaml( - R"EOF( - filter_metadata: - namespace: - key: endpoint - )EOF")); - EXPECT_CALL(*host_description, metadata()).WillRepeatedly(Return(metadata)); - request_stream_info_.upstreamInfo()->setUpstreamHost(host_description); - EXPECT_CALL(request_stream_info_, requestComplete()) - .WillRepeatedly(Return(std::chrono::milliseconds(30))); - filter().log({&request_headers}, request_stream_info_); -} - -TEST_P(WasmHttpFilterTest, ClusterMetadata) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): test not yet implemented using Rust SDK. - return; - } - setupTest("", "cluster_metadata"); - setupFilter(); - auto cluster = std::make_shared>(); - auto cluster_metadata = std::make_shared( - TestUtility::parseYaml( - R"EOF( - filter_metadata: - namespace: - key: cluster - )EOF")); - - std::shared_ptr> host_description( - new NiceMock()); - Http::TestRequestHeaderMapImpl request_headers{{}}; - - EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); - EXPECT_CALL(*cluster, metadata()).WillRepeatedly(ReturnRef(*cluster_metadata)); - EXPECT_CALL(request_stream_info_, requestComplete) - .WillRepeatedly(Return(std::chrono::milliseconds(30))); - request_stream_info_.upstream_cluster_info_ = cluster; - EXPECT_CALL(request_stream_info_, upstreamClusterInfo()).Times(testing::AnyNumber()); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("cluster metadata: cluster")))); - filter().log({&request_headers}, request_stream_info_); -} - -TEST_P(WasmHttpFilterTest, SharedData) { - setupTest("shared_data"); - EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq(absl::string_view("set CasMismatch")))); - EXPECT_CALL(rootContext(), - log_(spdlog::level::debug, Eq(absl::string_view("get 1 shared_data_value1")))); - if (std::get<1>(GetParam()) == "rust") { - EXPECT_CALL(rootContext(), - log_(spdlog::level::warn, Eq(absl::string_view("get 2 shared_data_value2")))); - } else { - EXPECT_CALL(rootContext(), - log_(spdlog::level::critical, Eq(absl::string_view("get 2 shared_data_value2")))); - } - EXPECT_CALL(rootContext(), - log_(spdlog::level::debug, Eq(absl::string_view("get of bad key not found")))); - EXPECT_CALL(rootContext(), - log_(spdlog::level::debug, Eq(absl::string_view("second get of bad key not found")))); - rootContext().onTick(0); - rootContext().onQueueReady(0); -} - -TEST_P(WasmHttpFilterTest, SharedQueue) { - setupTest("shared_queue"); - setupFilter(); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, - Eq(absl::string_view("onRequestHeaders not found self/bad_shared_queue")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, - Eq(absl::string_view("onRequestHeaders not found vm_id/bad_shared_queue")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, - Eq(absl::string_view("onRequestHeaders not found bad_vm_id/bad_shared_queue")))); - EXPECT_CALL(filter(), log_(spdlog::level::warn, - Eq(absl::string_view("onRequestHeaders found self/my_shared_queue")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, - Eq(absl::string_view("onRequestHeaders found vm_id/my_shared_queue")))); - EXPECT_CALL(filter(), - log_(spdlog::level::warn, Eq(absl::string_view("onRequestHeaders enqueue Ok")))); - EXPECT_CALL(rootContext(), - log_(spdlog::level::warn, Eq(absl::string_view("onQueueReady bad token not found")))) - .Times(2); - EXPECT_CALL(rootContext(), - log_(spdlog::level::warn, Eq(absl::string_view("onQueueReady extra data not found")))) - .Times(2); - EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq(absl::string_view("onQueueReady")))) - .Times(2); - EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq(absl::string_view("data data1 Ok")))); - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); - auto token = proxy_wasm::resolveQueueForTest("vm_id", "my_shared_queue"); - root_context_->onQueueReady(token); -} - -// Script using a root_id which is not registered. -TEST_P(WasmHttpFilterTest, RootIdNotRegistered) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. - return; - } - setupTest(); - setupFilter(); - Http::TestRequestHeaderMapImpl request_headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); -} - -// Script using an explicit root_id which is registered. -TEST_P(WasmHttpFilterTest, RootId1) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. - return; - } - setupTest("context1"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders1 2")))); - Http::TestRequestHeaderMapImpl request_headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); -} - -// Script using an explicit root_id which is registered. -TEST_P(WasmHttpFilterTest, RootId2) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(PiotrSikora): proxy_get_property("root_id") is not yet supported in the Rust SDK. - return; - } - setupTest("context2"); - setupFilter(); - EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders2 2")))); - Http::TestRequestHeaderMapImpl request_headers; - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().decodeHeaders(request_headers, true)); -} - -TEST_P(WasmHttpFilterTest, PanicOnRequestHeaders) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().onRequestHeaders(0, false)); -} - -TEST_P(WasmHttpFilterTest, PanicOnRequestBody) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onRequestBody(0, false)); -} - -TEST_P(WasmHttpFilterTest, PanicOnRequestTrailers) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&headers), true)); - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onRequestTrailers(0)); -} - -TEST_P(WasmHttpFilterTest, PanicOnResponseHeaders) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, - filter().onResponseHeaders(0, false)); -} - -TEST_P(WasmHttpFilterTest, PanicOnResponseBody) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onResponseBody(0, false)); -} - -TEST_P(WasmHttpFilterTest, PanicOnResponseTrailers) { - if (std::get<0>(GetParam()) == "null") { - return; - } - setupTest("panic"); - setupFilter(); - - // In the case of VM failure, failStream is called for both request and response stream types, - // so we need to make sure that we don't send the local reply twice. - EXPECT_CALL(decoder_callbacks_, sendLocalReply(Envoy::Http::Code::ServiceUnavailable, Eq(""), _, - Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), - Eq("wasm_fail_stream"))); - EXPECT_CALL(encoder_callbacks_, sendLocalReply(_, _, _, _, _)).Times(0); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onResponseTrailers(0)); -} - -TEST_P(WasmHttpFilterTest, CloseRequest) { - setupTest("close_stream"); - setupFilter(); - NiceMock stream_info; - Http::MockStreamDecoderFilterCallbacks decoder_callbacks; - filter().setDecoderFilterCallbacks(decoder_callbacks); - EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); - EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); - EXPECT_CALL(decoder_callbacks, resetStream(_, _)); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onRequestHeaders(0, false)); -} - -TEST_P(WasmHttpFilterTest, CloseResponse) { - setupTest("close_stream"); - setupFilter(); - NiceMock stream_info; - Http::MockStreamEncoderFilterCallbacks encoder_callbacks; - filter().setEncoderFilterCallbacks(encoder_callbacks); - EXPECT_CALL(encoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); - EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); - EXPECT_CALL(encoder_callbacks, resetStream(_, _)); - - // Create in-VM context. - filter().onCreate(); - EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onResponseHeaders(0, false)); -} - -TEST_P(WasmHttpFilterTest, VerifySignature) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(patricio78): test not yet implemented using Rust SDK. - return; - } - setupTest("", "verify_signature"); - setupFilter(); - EXPECT_CALL(rootContext(), - log_(spdlog::level::info, Eq(absl::string_view("signature is valid")))); - - EXPECT_CALL(rootContext(), - log_(spdlog::level::err, Eq(absl::string_view("unknown is not supported.")))); - EXPECT_CALL(rootContext(), log_(spdlog::level::err, - Eq(absl::string_view("Failed to initialize digest verify.")))) - .Times(2); - rootContext().onTick(0); -} - -TEST_P(WasmHttpFilterTest, Sign) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(patricio78): test not yet implemented using Rust SDK. - return; - } - setupTest("", "sign"); - setupFilter(); - EXPECT_CALL(rootContext(), - log_(spdlog::level::info, Eq(absl::string_view("signature created successfully")))); - - EXPECT_CALL(rootContext(), - log_(spdlog::level::err, Eq(absl::string_view("unknown is not supported.")))); - EXPECT_CALL( - rootContext(), - log_(spdlog::level::err, - Eq(absl::string_view("Invalid key type: private key required for signing operation.")))); - rootContext().onTick(0); -} - -TEST_P(WasmHttpFilterTest, SignAndVerifySignature) { - if (std::get<1>(GetParam()) == "rust") { - // TODO(patricio78): test not yet implemented using Rust SDK. - return; - } - setupTest("", "sign_and_verify_signature"); - setupFilter(); - EXPECT_CALL(rootContext(), - log_(spdlog::level::info, - Eq(absl::string_view("signature created successfully, length: 256")))); - EXPECT_CALL(rootContext(), - log_(spdlog::level::info, - Eq(absl::string_view( - "end-to-end test passed: signature created and verified successfully")))); - EXPECT_CALL( - rootContext(), - log_(spdlog::level::info, - Eq(absl::string_view("mutual exclusion test passed: both PEM and DER keys rejected")))); - rootContext().onTick(0); -} - -} // namespace Wasm -} // namespace HttpFilters -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/filters/http/wasm/wasm_filter_test_base.h b/test/extensions/filters/http/wasm/wasm_filter_test_base.h new file mode 100644 index 0000000000000..c35ce0a0b8023 --- /dev/null +++ b/test/extensions/filters/http/wasm/wasm_filter_test_base.h @@ -0,0 +1,109 @@ +#pragma once + +#include "envoy/grpc/async_client.h" + +#include "source/common/http/message_impl.h" +#include "source/extensions/filters/http/wasm/wasm_filter.h" + +#include "test/extensions/common/wasm/wasm_runtime.h" +#include "test/mocks/network/connection.h" +#include "test/mocks/router/mocks.h" +#include "test/test_common/wasm_base.h" + +#include "gmock/gmock.h" + +using testing::_; +using testing::Eq; +using testing::InSequence; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +using BufferFunction = std::function; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Wasm { + +using Envoy::Extensions::Common::Wasm::CreateContextFn; +using Envoy::Extensions::Common::Wasm::Plugin; +using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; +using Envoy::Extensions::Common::Wasm::Wasm; +using proxy_wasm::ContextBase; +using GrpcService = envoy::config::core::v3::GrpcService; + +class TestFilter : public Envoy::Extensions::Common::Wasm::Context { +public: + TestFilter(Wasm* wasm, uint32_t root_context_id, PluginHandleSharedPtr plugin_handle) + : Envoy::Extensions::Common::Wasm::Context(wasm, root_context_id, plugin_handle) {} + MOCK_CONTEXT_LOG_; +}; + +class TestRoot : public Envoy::Extensions::Common::Wasm::Context { +public: + TestRoot(Wasm* wasm, const std::shared_ptr& plugin) : Context(wasm, plugin) {} + MOCK_CONTEXT_LOG_; +}; + +class WasmHttpFilterTest : public Envoy::Extensions::Common::Wasm::WasmHttpFilterTestBase< + testing::TestWithParam>> { +public: + WasmHttpFilterTest() = default; + ~WasmHttpFilterTest() override = default; + + CreateContextFn createContextFn() { + return [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { + return new TestRoot(wasm, plugin); + }; + } + + void setupTest(std::string root_id = "", std::string vm_configuration = "", + envoy::extensions::wasm::v3::EnvironmentVariables envs = {}) { + std::string code; + if (std::get<0>(GetParam()) == "null") { + code = "HttpWasmTestCpp"; + } else if (std::get<1>(GetParam()) == "cpp") { + auto filename = !root_id.empty() ? root_id : vm_configuration; + if (filename.empty() || filename == "context1" || filename == "context2" || + filename == "cluster_metadata") { + filename = "headers"; + } else if (filename == "metadata" || filename == "property" || filename == "sign" || + filename == "verify_signature" || filename == "sign_and_verify_signature") { + filename = "advanced"; + } else if (filename == "grpc_call_proto") { + filename = "grpc_call"; + } else if (filename == "grpc_stream_proto") { + filename = "grpc_stream"; + } + code = TestEnvironment::readFileToStringForTest(TestEnvironment::runfilesPath( + absl::StrCat("test/extensions/filters/http/wasm/test_data/", filename, "_cpp.wasm"))); + } else { + auto filename = !root_id.empty() ? root_id : vm_configuration; + const auto basic_path = TestEnvironment::runfilesPath( + absl::StrCat("test/extensions/filters/http/wasm/test_data/", filename)); + code = TestEnvironment::readFileToStringForTest(basic_path + "_rust.wasm"); + } + + setRootId(root_id); + setEnvs(envs); + setVmConfiguration(vm_configuration); + setupBase(std::get<0>(GetParam()), code, createContextFn()); + } + + void setupFilter() { setupFilterBase(); } + + void setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks, std::string id); + + TestRoot& rootContext() { return *static_cast(root_context_); } + TestFilter& filter() { return *static_cast(context_.get()); } + +protected: + NiceMock async_stream_; + Grpc::MockAsyncClientManager async_client_manager_; +}; + +} // namespace Wasm +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index b5cc2ec492732..f229d2776b05a 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -66,7 +66,7 @@ class WasmNetworkFilterConfigTest }; INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, - Envoy::Extensions::Common::Wasm::runtime_and_cpp_values, + Envoy::Extensions::Common::Wasm::single_sandbox_runtime_and_cpp_values, Envoy::Extensions::Common::Wasm::wasmTestParamsToString); TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasm) { @@ -76,6 +76,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasm) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -120,6 +121,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadInlineWasm) { const std::string yaml = absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -145,6 +147,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadInlineBadCode) { config: name: "test" vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -186,6 +189,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadInlineBadCodeFailOpenPolicyNackConfi name: "test" failure_policy: FAIL_OPEN vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -207,6 +211,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailClosed) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -231,6 +236,7 @@ TEST_P(WasmNetworkFilterConfigTest, DEPRECATED_FEATURE_TEST(FilterConfigFailOpen config: fail_open: true vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -253,6 +259,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpenPolicy) { config: failure_policy: FAIL_OPEN vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -274,6 +281,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigCapabilitiesUnrestrictedByDefaul const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -301,6 +309,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigCapabilityRestriction) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -333,6 +342,7 @@ TEST_P(WasmNetworkFilterConfigTest, const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -362,6 +372,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigAllowOnVmStart) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -395,6 +406,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -418,6 +430,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasmInvalidConfig) { TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: @@ -452,6 +465,7 @@ TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromRemoteWasmCreateFilter) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" code: @@ -507,6 +521,7 @@ TEST_P(WasmNetworkFilterConfigTest, FailedToGetThreadLocalPlugin) { const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( config: vm_config: + allow_precompiled: true runtime: "envoy.wasm.runtime.)EOF", std::get<0>(GetParam()), R"EOF(" configuration: diff --git a/test/extensions/filters/network/wasm/test_data/BUILD b/test/extensions/filters/network/wasm/test_data/BUILD index b3987de1d8d18..d39cda48f1e7e 100644 --- a/test/extensions/filters/network/wasm/test_data/BUILD +++ b/test/extensions/filters/network/wasm/test_data/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test_library", "envoy_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_v8_bool") load("//bazel/wasm:wasm.bzl", "envoy_wasm_cc_binary", "wasm_rust_binary") licenses(["notice"]) # Apache 2 @@ -12,6 +13,7 @@ envoy_package() wasm_rust_binary( name = "close_stream_rust.wasm", srcs = ["close_stream_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -21,6 +23,7 @@ wasm_rust_binary( wasm_rust_binary( name = "logging_rust.wasm", srcs = ["logging_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -30,6 +33,7 @@ wasm_rust_binary( wasm_rust_binary( name = "panic_rust.wasm", srcs = ["panic_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -39,6 +43,7 @@ wasm_rust_binary( wasm_rust_binary( name = "resume_call_rust.wasm", srcs = ["resume_call_rust.rs"], + precompile = envoy_select_wasm_v8_bool(), deps = [ "@proxy-wasm-rust-sdk//:proxy_wasm", "@proxy-wasm-rust-sdk//bazel/cargo/remote:log", @@ -65,6 +70,7 @@ envoy_cc_test_library( envoy_wasm_cc_binary( name = "test_cpp.wasm", + precompile = envoy_select_wasm_v8_bool(), srcs = [ "test_close_stream_cpp.cc", "test_cpp.cc", diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 04b8702be0cec..dac797c8cf55e 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -91,7 +91,7 @@ class WasmNetworkFilterTest : public Extensions::Common::Wasm::WasmNetworkFilter }; INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, - Envoy::Extensions::Common::Wasm::runtime_and_language_values, + Envoy::Extensions::Common::Wasm::host_test_runtime_and_language_values, Envoy::Extensions::Common::Wasm::wasmTestParamsToString); // Bad code in initial config. diff --git a/test/extensions/stats_sinks/wasm/config_test.cc b/test/extensions/stats_sinks/wasm/config_test.cc index 511d523b6e95b..180a3cb297989 100644 --- a/test/extensions/stats_sinks/wasm/config_test.cc +++ b/test/extensions/stats_sinks/wasm/config_test.cc @@ -26,6 +26,7 @@ class WasmStatSinkConfigTest : public testing::TestWithParammutable_vm_config()->set_runtime( absl::StrCat("envoy.wasm.runtime.", std::get<0>(GetParam()))); + config_.mutable_config()->mutable_vm_config()->set_allow_precompiled(true); if (std::get<0>(GetParam()) != "null") { config_.mutable_config()->mutable_vm_config()->mutable_code()->mutable_local()->set_filename( TestEnvironment::substitute( @@ -66,7 +67,7 @@ class WasmStatSinkConfigTest : public testing::TestWithParam class WasmTestBase : public Base { auto vm_config = plugin_config.mutable_vm_config(); vm_config->set_vm_id("vm_id"); vm_config->set_runtime(absl::StrCat("envoy.wasm.runtime.", runtime)); + vm_config->set_allow_precompiled(true); Protobuf::StringValue vm_configuration_string; vm_configuration_string.set_value(vm_configuration_); std::ignore = vm_config->mutable_configuration()->PackFrom(vm_configuration_string); @@ -184,6 +185,7 @@ getWasmPluginConfigForTest(absl::string_view runtime, absl::string_view wasm_fil root_id: '{}' vm_config: runtime: 'envoy.wasm.runtime.{}' + allow_precompiled: true configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: '{}'