|
| 1 | +#include "envoy/http/filter.h" |
| 2 | +#include "envoy/http/filter_factory.h" |
1 | 3 | #include "envoy/server/lifecycle_notifier.h"
|
2 | 4 |
|
3 | 5 | #include "source/common/common/base64.h"
|
4 | 6 | #include "source/common/common/hex.h"
|
5 | 7 | #include "source/common/event/dispatcher_impl.h"
|
| 8 | +#include "source/common/http/filter_manager.h" |
6 | 9 | #include "source/common/stats/isolated_store_impl.h"
|
7 | 10 | #include "source/extensions/common/wasm/wasm.h"
|
8 | 11 |
|
9 | 12 | #include "test/extensions/common/wasm/wasm_runtime.h"
|
| 13 | +#include "test/mocks/local_reply/mocks.h" |
10 | 14 | #include "test/mocks/server/mocks.h"
|
11 | 15 | #include "test/mocks/stats/mocks.h"
|
12 | 16 | #include "test/mocks/upstream/mocks.h"
|
@@ -1313,7 +1317,6 @@ class WasmCommonContextTest : public Common::Wasm::WasmHttpFilterTestBase<
|
1313 | 1317 | return new TestContext(wasm, plugin);
|
1314 | 1318 | });
|
1315 | 1319 | }
|
1316 |
| - |
1317 | 1320 | void setupContext() { setupFilterBase<TestContext>(); }
|
1318 | 1321 |
|
1319 | 1322 | TestContext& rootContext() { return *static_cast<TestContext*>(root_context_); }
|
@@ -1395,43 +1398,19 @@ TEST_P(WasmCommonContextTest, EmptyContext) {
|
1395 | 1398 | root_context_->validateConfiguration("", plugin_);
|
1396 | 1399 | }
|
1397 | 1400 |
|
1398 |
| -// test that we don't send the local reply twice, even though it's specified in the wasm code |
1399 |
| -TEST_P(WasmCommonContextTest, DuplicateLocalReply) { |
1400 |
| - std::string code; |
1401 |
| - if (std::get<0>(GetParam()) != "null") { |
1402 |
| - code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat( |
1403 |
| - "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm"))); |
1404 |
| - } else { |
1405 |
| - // The name of the Null VM plugin. |
1406 |
| - code = "CommonWasmTestContextCpp"; |
1407 |
| - } |
1408 |
| - EXPECT_FALSE(code.empty()); |
1409 |
| - |
1410 |
| - setup(code, "context", "send local reply twice"); |
1411 |
| - setupContext(); |
1412 |
| - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) |
1413 |
| - .WillOnce([this](Http::ResponseHeaderMap&, bool) { context().onResponseHeaders(0, false); }); |
1414 |
| - EXPECT_CALL(decoder_callbacks_, |
1415 |
| - sendLocalReply(Envoy::Http::Code::OK, testing::Eq("body"), _, _, testing::Eq("ok"))); |
1416 |
| - |
1417 |
| - // Create in-VM context. |
1418 |
| - context().onCreate(); |
1419 |
| - EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, context().onRequestBody(0, false)); |
1420 |
| -} |
1421 |
| - |
1422 | 1401 | // test that we don't send the local reply twice when the wasm code panics
|
1423 | 1402 | TEST_P(WasmCommonContextTest, LocalReplyWhenPanic) {
|
1424 | 1403 | std::string code;
|
1425 | 1404 | if (std::get<0>(GetParam()) != "null") {
|
1426 | 1405 | code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat(
|
1427 | 1406 | "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm")));
|
1428 | 1407 | } else {
|
1429 |
| - // no need test the Null VM plugin. |
| 1408 | + // Let's not cause crashes in Null VM |
1430 | 1409 | return;
|
1431 | 1410 | }
|
1432 | 1411 | EXPECT_FALSE(code.empty());
|
1433 | 1412 |
|
1434 |
| - setup(code, "context", "panic after sending local reply"); |
| 1413 | + setup(code, "context", "panic during request processing"); |
1435 | 1414 | setupContext();
|
1436 | 1415 | // In the case of VM failure, failStream is called, so we need to make sure that we don't send the
|
1437 | 1416 | // local reply twice.
|
@@ -1495,6 +1474,125 @@ TEST_P(WasmCommonContextTest, ProcessValidGRPCStatusCodeAsEmptyInLocalReply) {
|
1495 | 1474 | EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, context().onRequestBody(1, false));
|
1496 | 1475 | }
|
1497 | 1476 |
|
| 1477 | +class WasmLocalReplyTest : public WasmCommonContextTest { |
| 1478 | +public: |
| 1479 | + WasmLocalReplyTest() = default; |
| 1480 | + |
| 1481 | + void setup(const std::string& code, std::string vm_configuration, std::string root_id = "") { |
| 1482 | + WasmCommonContextTest::setup(code, vm_configuration, root_id); |
| 1483 | + filter_manager_ = std::make_unique<Http::DownstreamFilterManager>( |
| 1484 | + filter_manager_callbacks_, dispatcher_, connection_, 0, nullptr, true, 10000, |
| 1485 | + filter_factory_, local_reply_, protocol_, time_source_, filter_state_, overload_manager_); |
| 1486 | + request_headers_ = Http::RequestHeaderMapPtr{ |
| 1487 | + new Http::TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}}; |
| 1488 | + request_data_ = Envoy::Buffer::OwnedImpl("body"); |
| 1489 | + } |
| 1490 | + |
| 1491 | + Http::StreamFilterSharedPtr filter() { return context_; } |
| 1492 | + |
| 1493 | + Http::FilterFactoryCb createWasmFilter() { |
| 1494 | + return [this](Http::FilterChainFactoryCallbacks& callbacks) { |
| 1495 | + callbacks.addStreamFilter(filter()); |
| 1496 | + }; |
| 1497 | + } |
| 1498 | + |
| 1499 | + void setupContext() { |
| 1500 | + WasmCommonContextTest::setupContext(); |
| 1501 | + ON_CALL(filter_factory_, createFilterChain(_)) |
| 1502 | + .WillByDefault(Invoke([this](Http::FilterChainManager& manager) -> bool { |
| 1503 | + auto factory = createWasmFilter(); |
| 1504 | + manager.applyFilterFactoryCb({}, factory); |
| 1505 | + return true; |
| 1506 | + })); |
| 1507 | + ON_CALL(filter_manager_callbacks_, requestHeaders()) |
| 1508 | + .WillByDefault(Return(makeOptRef(*request_headers_))); |
| 1509 | + filter_manager_->createFilterChain(); |
| 1510 | + filter_manager_->requestHeadersInitialized(); |
| 1511 | + } |
| 1512 | + |
| 1513 | + std::unique_ptr<Http::FilterManager> filter_manager_; |
| 1514 | + NiceMock<Http::MockFilterManagerCallbacks> filter_manager_callbacks_; |
| 1515 | + NiceMock<Event::MockDispatcher> dispatcher_; |
| 1516 | + NiceMock<Network::MockConnection> connection_; |
| 1517 | + NiceMock<Envoy::Http::MockFilterChainFactory> filter_factory_; |
| 1518 | + NiceMock<LocalReply::MockLocalReply> local_reply_; |
| 1519 | + Http::Protocol protocol_{Http::Protocol::Http2}; |
| 1520 | + NiceMock<MockTimeSystem> time_source_; |
| 1521 | + StreamInfo::FilterStateSharedPtr filter_state_ = |
| 1522 | + std::make_shared<StreamInfo::FilterStateImpl>(StreamInfo::FilterState::LifeSpan::Connection); |
| 1523 | + NiceMock<Server::MockOverloadManager> overload_manager_; |
| 1524 | + Http::RequestHeaderMapPtr request_headers_; |
| 1525 | + Envoy::Buffer::OwnedImpl request_data_; |
| 1526 | +}; |
| 1527 | + |
| 1528 | +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmLocalReplyTest, |
| 1529 | + Envoy::Extensions::Common::Wasm::runtime_and_cpp_values); |
| 1530 | + |
| 1531 | +TEST_P(WasmLocalReplyTest, DuplicateLocalReply) { |
| 1532 | + std::string code; |
| 1533 | + if (std::get<0>(GetParam()) != "null") { |
| 1534 | + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat( |
| 1535 | + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm"))); |
| 1536 | + } else { |
| 1537 | + // The name of the Null VM plugin. |
| 1538 | + code = "CommonWasmTestContextCpp"; |
| 1539 | + } |
| 1540 | + EXPECT_FALSE(code.empty()); |
| 1541 | + |
| 1542 | + setup(code, "context", "send local reply twice"); |
| 1543 | + setupContext(); |
| 1544 | + |
| 1545 | + // Even if sendLocalReply is called multiple times it should only generate a single |
| 1546 | + // response to the client, so encodeHeaders should only be called once |
| 1547 | + EXPECT_CALL(filter_manager_callbacks_, encodeHeaders(_, _)); |
| 1548 | + EXPECT_CALL(filter_manager_callbacks_, endStream()); |
| 1549 | + filter_manager_->decodeHeaders(*request_headers_, false); |
| 1550 | + filter_manager_->decodeData(request_data_, true); |
| 1551 | + filter_manager_->destroyFilters(); |
| 1552 | +} |
| 1553 | + |
| 1554 | +TEST_P(WasmLocalReplyTest, LocalReplyInRequestAndResponse) { |
| 1555 | + std::string code; |
| 1556 | + if (std::get<0>(GetParam()) != "null") { |
| 1557 | + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat( |
| 1558 | + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm"))); |
| 1559 | + } else { |
| 1560 | + code = "CommonWasmTestContextCpp"; |
| 1561 | + } |
| 1562 | + EXPECT_FALSE(code.empty()); |
| 1563 | + |
| 1564 | + setup(code, "context", "local reply in request and response"); |
| 1565 | + setupContext(); |
| 1566 | + |
| 1567 | + EXPECT_CALL(filter_manager_callbacks_, encodeHeaders(_, _)); |
| 1568 | + EXPECT_CALL(filter_manager_callbacks_, endStream()); |
| 1569 | + filter_manager_->decodeHeaders(*request_headers_, false); |
| 1570 | + filter_manager_->decodeData(request_data_, true); |
| 1571 | + filter_manager_->destroyFilters(); |
| 1572 | +} |
| 1573 | + |
| 1574 | +TEST_P(WasmLocalReplyTest, PanicDuringResponse) { |
| 1575 | + std::string code; |
| 1576 | + if (std::get<0>(GetParam()) != "null") { |
| 1577 | + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(absl::StrCat( |
| 1578 | + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_context_cpp.wasm"))); |
| 1579 | + } else { |
| 1580 | + // Let's not cause crashes in Null VM |
| 1581 | + return; |
| 1582 | + } |
| 1583 | + EXPECT_FALSE(code.empty()); |
| 1584 | + |
| 1585 | + setup(code, "context", "panic during response processing"); |
| 1586 | + setupContext(); |
| 1587 | + |
| 1588 | + EXPECT_CALL(filter_manager_callbacks_, encodeHeaders(_, _)); |
| 1589 | + EXPECT_CALL(filter_manager_callbacks_, endStream()); |
| 1590 | + |
| 1591 | + filter_manager_->decodeHeaders(*request_headers_, false); |
| 1592 | + filter_manager_->decodeData(request_data_, true); |
| 1593 | + filter_manager_->destroyFilters(); |
| 1594 | +} |
| 1595 | + |
1498 | 1596 | class PluginConfigTest : public testing::TestWithParam<std::tuple<std::string, std::string>> {
|
1499 | 1597 | public:
|
1500 | 1598 | PluginConfigTest() = default;
|
@@ -1548,7 +1646,7 @@ TEST_P(PluginConfigTest, FailReloadPolicy) {
|
1548 | 1646 | const std::string plugin_config_yaml = fmt::format(
|
1549 | 1647 | R"EOF(
|
1550 | 1648 | name: "{}_test_wasm_reload"
|
1551 |
| -root_id: "panic after sending local reply" |
| 1649 | +root_id: "panic during request processing" |
1552 | 1650 | failure_policy: FAIL_RELOAD
|
1553 | 1651 | vm_config:
|
1554 | 1652 | runtime: "envoy.wasm.runtime.{}"
|
@@ -1667,7 +1765,7 @@ TEST_P(PluginConfigTest, FailClosedPolicy) {
|
1667 | 1765 | const std::string plugin_config_yaml = fmt::format(
|
1668 | 1766 | R"EOF(
|
1669 | 1767 | name: "{}_test_wasm_reload"
|
1670 |
| -root_id: "panic after sending local reply" |
| 1768 | +root_id: "panic during request processing" |
1671 | 1769 | failure_policy: FAIL_CLOSED
|
1672 | 1770 | vm_config:
|
1673 | 1771 | runtime: "envoy.wasm.runtime.{}"
|
@@ -1746,7 +1844,7 @@ TEST_P(PluginConfigTest, FailUnspecifiedPolicy) {
|
1746 | 1844 | const std::string plugin_config_yaml = fmt::format(
|
1747 | 1845 | R"EOF(
|
1748 | 1846 | name: "{}_test_wasm_reload"
|
1749 |
| -root_id: "panic after sending local reply" |
| 1847 | +root_id: "panic during request processing" |
1750 | 1848 | vm_config:
|
1751 | 1849 | runtime: "envoy.wasm.runtime.{}"
|
1752 | 1850 | configuration:
|
@@ -1824,7 +1922,7 @@ TEST_P(PluginConfigTest, FailOpenPolicy) {
|
1824 | 1922 | const std::string plugin_config_yaml = fmt::format(
|
1825 | 1923 | R"EOF(
|
1826 | 1924 | name: "{}_test_wasm_reload"
|
1827 |
| -root_id: "panic after sending local reply" |
| 1925 | +root_id: "panic during request processing" |
1828 | 1926 | failure_policy: FAIL_OPEN
|
1829 | 1927 | vm_config:
|
1830 | 1928 | runtime: "envoy.wasm.runtime.{}"
|
|
0 commit comments