Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dubbo: support wildcard match for dubbo interface #15121

Merged
merged 3 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion api/envoy/extensions/filters/network/dubbo_proxy/v3/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ message RouteConfiguration {
// The name of the route configuration. Reserved for future use in asynchronous route discovery.
string name = 1;

// The interface name of the service.
// The interface name of the service. Wildcard interface are supported in the suffix or prefix form.
// e.g. ``*.methods.add`` will match ``com.dev.methods.add``, ``com.prod.methods.add``, etc.
// ``com.dev.methods.*`` will match ``com.dev.methods.add``, ``com.dev.methods.update``, etc.
// Special wildcard ``*`` matching any interface.
//
// .. note::
//
// The wildcard will not match the empty string.
// e.g. ``*.methods.add`` will match ``com.dev.methods.add`` but not ``.methods.add``.
htuch marked this conversation as resolved.
Show resolved Hide resolved
string interface = 2;

// Which group does the interface belong to.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ RouteConstSharedPtr MethodRouteEntryImpl::matches(const MessageMetadata& metadat

SingleRouteMatcherImpl::SingleRouteMatcherImpl(const RouteConfig& config,
Server::Configuration::FactoryContext&)
: service_name_(config.interface()), group_(config.group()), version_(config.version()) {
: interface_matcher_(config.interface()), group_(config.group()), version_(config.version()) {
using envoy::extensions::filters::network::dubbo_proxy::v3::RouteMatch;

for (const auto& route : config.routes()) {
Expand All @@ -184,7 +184,7 @@ RouteConstSharedPtr SingleRouteMatcherImpl::route(const MessageMetadata& metadat
ASSERT(metadata.hasInvocationInfo());
const auto& invocation = metadata.invocationInfo();

if (service_name_ == invocation.serviceName() &&
if (interface_matcher_.match(invocation.serviceName()) &&
(group_.value().empty() ||
(invocation.serviceGroup().has_value() && invocation.serviceGroup().value() == group_)) &&
(version_.value().empty() || (invocation.serviceVersion().has_value() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,47 @@ class MethodRouteEntryImpl : public RouteEntryImplBase {

class SingleRouteMatcherImpl : public RouteMatcher, public Logger::Loggable<Logger::Id::dubbo> {
public:
class InterfaceMatcher {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/envoyproxy/envoy/blob/main/source/extensions/tracers/xray/util.cc#L26

Is it possible to reuse this existing implementation? Just add case-sensitive settings on top of this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to add some comments in the proto file to indicate that the interface uses wildcards to match.

Copy link
Member Author

@wbpcode wbpcode Feb 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we only support the same prefix or suffix match as the HTTP virtual host, I don't think it's necessary to use such a relatively complex implementation in xray. The current implementation should have better performance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add some new comments.

public:
using MatcherImpl = std::function<bool(const absl::string_view)>;
InterfaceMatcher(const std::string& interface_name) {
if (interface_name == "*") {
impl_ = [](const absl::string_view interface) { return !interface.empty(); };
return;
}
if (absl::StartsWith(interface_name, "*")) {
const std::string suffix = interface_name.substr(1);
impl_ = [suffix](const absl::string_view interface) {
return interface.size() > suffix.size() && absl::EndsWith(interface, suffix);
};
return;
}
if (absl::EndsWith(interface_name, "*")) {
const std::string prefix = interface_name.substr(0, interface_name.size() - 1);
impl_ = [prefix](const absl::string_view interface) {
return interface.size() > prefix.size() && absl::StartsWith(interface, prefix);
};
return;
}
impl_ = [interface_name](const absl::string_view interface) {
return interface == interface_name;
};
}

bool match(const absl::string_view interface) const { return impl_(interface); }

private:
MatcherImpl impl_;
};

using RouteConfig = envoy::extensions::filters::network::dubbo_proxy::v3::RouteConfiguration;
SingleRouteMatcherImpl(const RouteConfig& config, Server::Configuration::FactoryContext& context);

RouteConstSharedPtr route(const MessageMetadata& metadata, uint64_t random_value) const override;

private:
std::vector<RouteEntryImplBaseConstSharedPtr> routes_;
const std::string service_name_;
const InterfaceMatcher interface_matcher_;
const absl::optional<std::string> group_;
const absl::optional<std::string> version_;
};
Expand Down
114 changes: 114 additions & 0 deletions test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,120 @@ parseDubboProxyFromV2Yaml(const std::string& yaml) {

} // namespace

TEST(DubboRouteMatcherTest, RouteByServiceNameWithWildcard) {
{
const std::string yaml = R"EOF(
name: local_route
interface: "*"
routes:
- match:
method:
name:
exact: "add"
route:
cluster: user_service_dubbo_server
)EOF";
envoy::extensions::filters::network::dubbo_proxy::v3::RouteConfiguration config =
parseRouteConfigurationFromV2Yaml(yaml);

NiceMock<Server::Configuration::MockFactoryContext> context;
SingleRouteMatcherImpl matcher(config, context);
auto invo = std::make_shared<RpcInvocationImpl>();
MessageMetadata metadata;
metadata.setInvocationInfo(invo);
invo->setMethodName("add");
EXPECT_EQ(nullptr, matcher.route(metadata, 0));

invo->setServiceName("unknown");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("random");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("fake_service");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());
}
{
const std::string yaml = R"EOF(
name: local_route
interface: "*.test.com"
routes:
- match:
method:
name:
exact: "add"
route:
cluster: user_service_dubbo_server
)EOF";
envoy::extensions::filters::network::dubbo_proxy::v3::RouteConfiguration config =
parseRouteConfigurationFromV2Yaml(yaml);

NiceMock<Server::Configuration::MockFactoryContext> context;
SingleRouteMatcherImpl matcher(config, context);
auto invo = std::make_shared<RpcInvocationImpl>();
MessageMetadata metadata;
metadata.setInvocationInfo(invo);
invo->setMethodName("add");
EXPECT_EQ(nullptr, matcher.route(metadata, 0));

invo->setServiceName(".test.com");
EXPECT_EQ(nullptr, matcher.route(metadata, 0));

invo->setServiceName("code.test.com");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("fake.test.com");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("fake_service");

EXPECT_EQ(nullptr, matcher.route(metadata, 0));
}
{
const std::string yaml = R"EOF(
name: local_route
interface: "com.test.*"
routes:
- match:
method:
name:
exact: "add"
route:
cluster: user_service_dubbo_server
)EOF";
envoy::extensions::filters::network::dubbo_proxy::v3::RouteConfiguration config =
parseRouteConfigurationFromV2Yaml(yaml);

NiceMock<Server::Configuration::MockFactoryContext> context;
SingleRouteMatcherImpl matcher(config, context);
auto invo = std::make_shared<RpcInvocationImpl>();
MessageMetadata metadata;
metadata.setInvocationInfo(invo);
invo->setMethodName("add");
EXPECT_EQ(nullptr, matcher.route(metadata, 0));

invo->setServiceName("com.test.");
EXPECT_EQ(nullptr, matcher.route(metadata, 0));

invo->setServiceName("com.test.code");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("com.test.fake");

EXPECT_EQ("user_service_dubbo_server", matcher.route(metadata, 0)->routeEntry()->clusterName());

invo->setServiceName("fake_service");

EXPECT_EQ(nullptr, matcher.route(metadata, 0));
}
}

TEST(DubboRouteMatcherTest, RouteByServiceNameWithAnyMethod) {
{
const std::string yaml = R"EOF(
Expand Down