Skip to content

Commit

Permalink
working tests and integration test
Browse files Browse the repository at this point in the history
Signed-off-by: Greg Greenway <ggreenway@apple.com>
  • Loading branch information
ggreenway committed Feb 16, 2024
1 parent 9e49958 commit 5ba4161
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 18 deletions.
2 changes: 1 addition & 1 deletion api/envoy/extensions/matching/string/lua/v3/lua.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;

message Lua {
// The Lua code that Envoy will execute
string inline_code = 1;
string inline_code = 1 [(validate.rules).string = {min_len: 1}];
}
11 changes: 3 additions & 8 deletions source/common/common/matchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "source/common/common/macros.h"
#include "source/common/common/regex.h"
#include "source/common/config/metadata.h"
#include "source/common/config/utility.h"
#include "source/common/http/path_utility.h"

#include "absl/strings/match.h"
Expand Down Expand Up @@ -201,14 +202,8 @@ bool PathMatcher::match(const absl::string_view path) const {
}

StringMatcherPtr getExtensionStringMatcher(const ::xds::core::v3::TypedExtensionConfig& config) {
auto factory = Registry::FactoryRegistry<StringMatcherExtensionFactory>::getFactoryByType(
config.GetTypeName());
if (factory != nullptr) {
return factory->createStringMatcher(config);
}

throwEnvoyExceptionOrPanic(
absl::StrCat("Could not find extension of type ", config.GetTypeName()));
auto factory = Config::Utility::getAndCheckFactory<StringMatcherExtensionFactory>(config, false);
return factory->createStringMatcher(config.typed_config());
}

} // namespace Matchers
Expand Down
3 changes: 1 addition & 2 deletions source/common/common/matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ class StringMatcherImpl : public ValueMatcher, public StringMatcher {

class StringMatcherExtensionFactory : public Config::TypedFactory {
public:
virtual StringMatcherPtr
createStringMatcher(const ::xds::core::v3::TypedExtensionConfig& config) PURE;
virtual StringMatcherPtr createStringMatcher(const ProtobufWkt::Any& config) PURE;

std::string category() const override { return "envoy.matching.string"; }
};
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/matching/string/lua/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ envoy_extension_package()
envoy_cc_extension(
name = "config",
srcs = ["match.cc"],
hdrs = ["match.h"],
deps = [
"//source/common/common:matchers_lib",
"//source/extensions/filters/common/lua:lua_lib",
"@envoy_api//envoy/extensions/matching/string/lua/v3:pkg_cc_proto",
],
)
97 changes: 91 additions & 6 deletions source/extensions/matching/string/lua/match.cc
Original file line number Diff line number Diff line change
@@ -1,16 +1,101 @@
#include "source/common/common/matchers.h"
#include "source/extensions/matching/string/lua/match.h"

#include "envoy/extensions/matching/string/lua/v3/lua.pb.h"
#include "source/common/config/utility.h"
#include "source/common/protobuf/message_validator_impl.h"

namespace Envoy::Extensions::Matching::String::Lua {

LuaStringMatcher::LuaStringMatcher(const std::string& code) : state_(luaL_newstate()) {
RELEASE_ASSERT(state_.get() != nullptr, "unable to create new Lua state object");
luaL_openlibs(state_.get());
int rc = luaL_dostring(state_.get(), code.c_str());
if (rc != 0) {
absl::string_view error("unknown");
if (lua_isstring(state_.get(), -1)) {
size_t len = 0;
const char* err = lua_tolstring(state_.get(), -1, &len);
error = absl::string_view(err, len);
}
throw EnvoyException(absl::StrCat("Failed to load lua code in Lua StringMatcher:", error));
}

lua_getglobal(state_.get(), "match");
bool is_function = lua_isfunction(state_.get(), -1);
lua_pop(state_.get(), 1);
if (!is_function) {
throw EnvoyException("Lua code did not contain a global function named 'match'");
}
}

bool LuaStringMatcher::match(const absl::string_view value) const {
const int initial_depth = lua_gettop(state_.get());

bool ret = [&]() {
lua_getglobal(state_.get(), "match");
ASSERT(lua_isfunction(state_.get(), -1)); // Validated in constructor

lua_pushlstring(state_.get(), value.data(), value.size());
int rc = lua_pcall(state_.get(), 1, 1, 0);
if (rc != 0) {
// Runtime error
absl::string_view error("unknown");
if (lua_isstring(state_.get(), -1)) {
size_t len = 0;
const char* err = lua_tolstring(state_.get(), -1, &len);
error = absl::string_view(err, len);
}
ENVOY_LOG_PERIODIC_MISC(error, std::chrono::seconds(5),
"Lua StringMatcher error running script: {}", error);
lua_pop(state_.get(), 1);

return false;
}

bool ret = false;
if (lua_isboolean(state_.get(), -1)) {
ret = lua_toboolean(state_.get(), -1) != 0;
} else {
ENVOY_LOG_PERIODIC_MISC(error, std::chrono::seconds(5),
"Lua StringMatcher match function did not return a boolean");
}

lua_pop(state_.get(), 1);
return ret;
}();

// Validate that the stack is restored to it's original state; nothing added or removed.
ASSERT(lua_gettop(state_.get()) == initial_depth);
return ret;
}

// Lua state is not thread safe, so a state needs to be stored in thread local storage.
class LuaStringMatcherThreadWrapper : public Matchers::StringMatcher {
public:
LuaStringMatcherThreadWrapper(const std::string& code) {
// Validate that there are no errors while creating on the main thread.
LuaStringMatcher validator(code);

tls_slot_ = ThreadLocal::TypedSlot<LuaStringMatcher>::makeUnique(
*InjectableSingleton<ThreadLocal::SlotAllocator>::getExisting());
tls_slot_->set([code](Event::Dispatcher&) -> std::shared_ptr<LuaStringMatcher> {
return std::make_shared<LuaStringMatcher>(code);
});
}

bool match(const absl::string_view value) const override { return (*tls_slot_)->match(value); }

private:
ThreadLocal::TypedSlotPtr<LuaStringMatcher> tls_slot_;
};

class LuaStringMatcherFactory : public Matchers::StringMatcherExtensionFactory {
public:
Matchers::StringMatcherPtr
createStringMatcher(const ::xds::core::v3::TypedExtensionConfig& config) override {
auto typed_config =
dynamic_cast<::envoy::extensions::matching::string::lua::v3::Lua>(config.typed_config());
return nullptr;
Matchers::StringMatcherPtr createStringMatcher(const ProtobufWkt::Any& message) override {
::envoy::extensions::matching::string::lua::v3::Lua config;
Config::Utility::translateOpaqueConfig(message, ProtobufMessage::getStrictValidationVisitor(),
config);
return std::make_unique<LuaStringMatcherThreadWrapper>(config.inline_code());
}

std::string name() const override { return "envoy.matching.string.lua"; }
Expand Down
23 changes: 23 additions & 0 deletions source/extensions/matching/string/lua/match.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include "envoy/thread_local/thread_local.h"
#include "source/common/common/matchers.h"
#include "source/extensions/filters/common/lua/lua.h"

namespace Envoy::Extensions::Matching::String::Lua {

class LuaStringMatcher : public Matchers::StringMatcher, public ThreadLocal::ThreadLocalObject {
public:
LuaStringMatcher(const std::string& code);

// ThreadLocal::ThreadLocalObject
~LuaStringMatcher() override = default;

// Matchers::StringMatcher
bool match(const absl::string_view value) const override;

private:
CSmartPtr<lua_State, lua_close> state_;
};

} // namespace Envoy::Extensions::Matching::String::Lua
7 changes: 6 additions & 1 deletion source/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ InstanceBase::InstanceBase(Init::Manager& init_manager, const Options& options,
grpc_context_(store.symbolTable()), http_context_(store.symbolTable()),
router_context_(store.symbolTable()), process_context_(std::move(process_context)),
hooks_(hooks), quic_stat_names_(store.symbolTable()), server_contexts_(*this),
enable_reuse_port_default_(true), stats_flush_in_progress_(false) {}
enable_reuse_port_default_(true), stats_flush_in_progress_(false) {

InjectableSingleton<ThreadLocal::SlotAllocator>::initialize(&thread_local_);
}

InstanceBase::~InstanceBase() {
InjectableSingleton<ThreadLocal::SlotAllocator>::clear();

terminate();

// Stop logging to file before all the AccessLogManager and its dependencies are
Expand Down
33 changes: 33 additions & 0 deletions test/extensions/matching/string/lua/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_package",
)
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_extension_cc_test(
name = "lua_test",
srcs = ["lua_test.cc"],
extension_names = ["envoy.matching.string.lua"],
deps = [
"//source/extensions/matching/string/lua:config",
"//test/test_common:logging_lib",
"//test/test_common:utility_lib",
],
)

envoy_extension_cc_test(
name = "lua_integration_test",
srcs = ["lua_integration_test.cc"],
extension_names = ["envoy.matching.string.lua"],
deps = [
"//source/extensions/matching/string/lua:config",
"//test/integration:http_integration_lib",
],
)
83 changes: 83 additions & 0 deletions test/extensions/matching/string/lua/lua_integration_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "source/common/http/utility.h"
#include "source/common/protobuf/protobuf.h"
#include "envoy/extensions/matching/string/lua/v3/lua.pb.h"

#include "test/integration/http_integration.h"

#include "gtest/gtest.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace String {
namespace Lua {

class LuaIntegrationTest : public testing::TestWithParam<Network::Address::IpVersion>,
public HttpIntegrationTest {
public:
LuaIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {
autonomous_upstream_ = true;
config_helper_.addConfigModifier(
[](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) {
::envoy::extensions::matching::string::lua::v3::Lua config;
config.set_inline_code(
R"(
function match(str)
return str == "good" or str == "acceptable"
end
)");
auto* header_match = hcm.mutable_route_config()
->mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_match()
->add_headers();

header_match->set_name("lua-header");

auto* string_match_extension = header_match->mutable_string_match()->mutable_extension();
string_match_extension->set_name("unused but must be set");
string_match_extension->mutable_typed_config()->PackFrom(config);
});
HttpIntegrationTest::initialize();
}
};

INSTANTIATE_TEST_SUITE_P(IpVersions, LuaIntegrationTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);

TEST_P(LuaIntegrationTest, HeaderMatcher) {
Http::TestRequestHeaderMapImpl matching_request_headers{
{":method", "GET"}, {":path", "/"},
{":scheme", "http"}, {":authority", "example.com"},
{"lua-header", "acceptable"},
};
codec_client_ = makeHttpConnection(lookupPort("http"));
{
auto response = codec_client_->makeHeaderOnlyRequest(matching_request_headers);
ASSERT_TRUE(response->waitForEndStream());
ASSERT_TRUE(response->complete());
EXPECT_EQ("200", response->headers().Status()->value().getStringView());
}

Http::TestRequestHeaderMapImpl non_matching_request_headers{
{":method", "GET"},
{":path", "/"},
{":scheme", "http"},
{":authority", "example.com"},
{"lua-header", "unacceptable"},
};
{
auto response = codec_client_->makeHeaderOnlyRequest(non_matching_request_headers);
ASSERT_TRUE(response->waitForEndStream());
ASSERT_TRUE(response->complete());
EXPECT_EQ("404", response->headers().Status()->value().getStringView());
}
}

} // namespace Lua
} // namespace String
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
Loading

0 comments on commit 5ba4161

Please sign in to comment.