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