Skip to content

Commit 27fb1d3

Browse files
briramszuercher
authored andcommitted
thrift_proxy: add service name matching to router implementation (#4130)
Currently, the thrift router only supports method matching as a way to route thrift requests. This builds on that by adding the ability to specify a service name that is used when matching. This change updates the RouteMatch proto definition to use a oneof field to indicate what type of matching should be done, as well as an invert flag that will allow for inverse matching rules. Additionally: * ensure new RouteEntryImplBase implementations check that inversion and wildcard matching are not enabled at the same time, as this would result in no matches for a route * implement service matching as checking the prefix of the method name, as that's how it's implemented in thrift *Risk Level:* Low *Testing:* * new and existing unit tests pass. * updated integration test use new matching rules and ensure that expected upstreams receive requests. *Documentation:* n/a *Release Notes:* n/a Signed-off-by: Brian Ramos <[email protected]>
1 parent 8c189a5 commit 27fb1d3

File tree

6 files changed

+341
-24
lines changed

6 files changed

+341
-24
lines changed

api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,24 @@ message Route {
2727
RouteAction route = 2 [(validate.rules).message.required = true, (gogoproto.nullable) = false];
2828
}
2929

30-
// [#comment:next free field: 2]
30+
// [#comment:next free field: 4]
3131
message RouteMatch {
32-
// If specified, the route must exactly match the request method name. As a special case, an
33-
// empty string matches any request method name.
34-
string method = 1;
32+
oneof match_specifier {
33+
option (validate.required) = true;
34+
35+
// If specified, the route must exactly match the request method name. As a special case, an
36+
// empty string matches any request method name.
37+
string method_name = 1;
38+
39+
// If specified, the route must have the service name as the request method name prefix. As a
40+
// special case, an empty string matches any service name. Only relevant when service
41+
// multiplexing.
42+
string service_name = 2;
43+
}
44+
45+
// Inverts whatever matching is done in match_specifier. Cannot be combined with wildcard matching
46+
// as that would result in routes never being matched.
47+
bool invert = 3;
3548
}
3649

3750
// [#comment:next free field: 2]

source/extensions/filters/network/thrift_proxy/router/router_impl.cc

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "envoy/upstream/cluster_manager.h"
55
#include "envoy/upstream/thread_local_cluster.h"
66

7+
#include "common/common/utility.h"
8+
79
#include "extensions/filters/network/thrift_proxy/app_exception_impl.h"
810

911
namespace Envoy {
@@ -24,14 +26,45 @@ RouteConstSharedPtr RouteEntryImplBase::clusterEntry() const { return shared_fro
2426

2527
MethodNameRouteEntryImpl::MethodNameRouteEntryImpl(
2628
const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route)
27-
: RouteEntryImplBase(route), method_name_(route.match().method()) {}
29+
: RouteEntryImplBase(route), method_name_(route.match().method_name()),
30+
invert_(route.match().invert()) {
31+
if (method_name_.empty() && invert_) {
32+
throw EnvoyException("Cannot have an empty method name with inversion enabled");
33+
}
34+
}
2835

2936
RouteConstSharedPtr MethodNameRouteEntryImpl::matches(const MessageMetadata& metadata) const {
30-
if (method_name_.empty()) {
37+
bool matches =
38+
method_name_.empty() || (metadata.hasMethodName() && metadata.methodName() == method_name_);
39+
40+
if (matches ^ invert_) {
3141
return clusterEntry();
3242
}
3343

34-
if (metadata.hasMethodName() && metadata.methodName() == method_name_) {
44+
return nullptr;
45+
}
46+
47+
ServiceNameRouteEntryImpl::ServiceNameRouteEntryImpl(
48+
const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route)
49+
: RouteEntryImplBase(route), invert_(route.match().invert()) {
50+
const std::string service_name = route.match().service_name();
51+
if (service_name.empty() && invert_) {
52+
throw EnvoyException("Cannot have an empty service name with inversion enabled");
53+
}
54+
55+
if (!service_name.empty() && !StringUtil::endsWith(service_name, ":")) {
56+
service_name_ = service_name + ":";
57+
} else {
58+
service_name_ = service_name;
59+
}
60+
}
61+
62+
RouteConstSharedPtr ServiceNameRouteEntryImpl::matches(const MessageMetadata& metadata) const {
63+
bool matches = service_name_.empty() ||
64+
(metadata.hasMethodName() &&
65+
StringUtil::startsWith(metadata.methodName().c_str(), service_name_));
66+
67+
if (matches ^ invert_) {
3568
return clusterEntry();
3669
}
3770

@@ -40,8 +73,19 @@ RouteConstSharedPtr MethodNameRouteEntryImpl::matches(const MessageMetadata& met
4073

4174
RouteMatcher::RouteMatcher(
4275
const envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration& config) {
76+
using envoy::config::filter::network::thrift_proxy::v2alpha1::RouteMatch;
77+
4378
for (const auto& route : config.routes()) {
44-
routes_.emplace_back(new MethodNameRouteEntryImpl(route));
79+
switch (route.match().match_specifier_case()) {
80+
case RouteMatch::MatchSpecifierCase::kMethodName:
81+
routes_.emplace_back(new MethodNameRouteEntryImpl(route));
82+
break;
83+
case RouteMatch::MatchSpecifierCase::kServiceName:
84+
routes_.emplace_back(new ServiceNameRouteEntryImpl(route));
85+
break;
86+
default:
87+
NOT_REACHED_GCOVR_EXCL_LINE;
88+
}
4589
}
4690
}
4791

source/extensions/filters/network/thrift_proxy/router/router_impl.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,27 @@ class MethodNameRouteEntryImpl : public RouteEntryImplBase {
5252

5353
const std::string& methodName() const { return method_name_; }
5454

55-
// RoutEntryImplBase
55+
// RouteEntryImplBase
5656
RouteConstSharedPtr matches(const MessageMetadata& metadata) const override;
5757

5858
private:
5959
const std::string method_name_;
60+
const bool invert_;
61+
};
62+
63+
class ServiceNameRouteEntryImpl : public RouteEntryImplBase {
64+
public:
65+
ServiceNameRouteEntryImpl(
66+
const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route);
67+
68+
const std::string& serviceName() const { return service_name_; }
69+
70+
// RouteEntryImplBase
71+
RouteConstSharedPtr matches(const MessageMetadata& metadata) const override;
72+
73+
private:
74+
std::string service_name_;
75+
const bool invert_;
6076
};
6177

6278
class RouteMatcher {

test/extensions/filters/network/thrift_proxy/conn_manager_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ stat_prefix: test
518518
name: "routes"
519519
routes:
520520
- match:
521-
method: name
521+
method_name: name
522522
route:
523523
cluster: cluster
524524
)EOF";

test/extensions/filters/network/thrift_proxy/integration_test.cc

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,18 @@ class ThriftConnManagerIntegrationTest
4646
route_config:
4747
name: "routes"
4848
routes:
49-
- match: {}
49+
- match:
50+
service_name: "svcname"
5051
route:
5152
cluster: "cluster_0"
53+
- match:
54+
method_name: "execute"
55+
route:
56+
cluster: "cluster_1"
57+
- match:
58+
method_name: "poke"
59+
route:
60+
cluster: "cluster_2"
5261
)EOF";
5362
}
5463

@@ -73,8 +82,7 @@ class ThriftConnManagerIntegrationTest
7382
preparePayloads(result_mode, "execute");
7483
ASSERT(request_bytes_.length() > 0);
7584
ASSERT(response_bytes_.length() > 0);
76-
77-
BaseIntegrationTest::initialize();
85+
initializeCommon();
7886
}
7987

8088
void initializeOneway() {
@@ -84,6 +92,24 @@ class ThriftConnManagerIntegrationTest
8492
ASSERT(request_bytes_.length() > 0);
8593
ASSERT(response_bytes_.length() == 0);
8694

95+
initializeCommon();
96+
}
97+
98+
// We allocate as many upstreams as there are clusters, with each upstream being allocated
99+
// to clusters in the order they're defined in the bootstrap config.
100+
void initializeCommon() {
101+
setUpstreamCount(3);
102+
103+
config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) {
104+
auto* c1 = bootstrap.mutable_static_resources()->add_clusters();
105+
c1->MergeFrom(bootstrap.static_resources().clusters()[0]);
106+
c1->set_name("cluster_1");
107+
108+
auto* c2 = bootstrap.mutable_static_resources()->add_clusters();
109+
c2->MergeFrom(bootstrap.static_resources().clusters()[0]);
110+
c2->set_name("cluster_2");
111+
});
112+
87113
BaseIntegrationTest::initialize();
88114
}
89115

@@ -140,6 +166,20 @@ class ThriftConnManagerIntegrationTest
140166
}
141167
}
142168

169+
// Multiplexed requests are handled by the service name route match,
170+
// while oneway's are handled by the "poke" method. All other requests
171+
// are handled by "execute".
172+
FakeUpstream* getExpectedUpstream(bool oneway) {
173+
int upstreamIdx = 1;
174+
if (multiplexed_) {
175+
upstreamIdx = 0;
176+
} else if (oneway) {
177+
upstreamIdx = 2;
178+
}
179+
180+
return fake_upstreams_[upstreamIdx].get();
181+
}
182+
143183
std::string transport_;
144184
std::string protocol_;
145185
bool multiplexed_;
@@ -176,7 +216,8 @@ TEST_P(ThriftConnManagerIntegrationTest, Success) {
176216
tcp_client->write(request_bytes_.toString());
177217

178218
FakeRawConnectionPtr fake_upstream_connection;
179-
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
219+
FakeUpstream* expected_upstream = getExpectedUpstream(false);
220+
ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection));
180221
std::string data;
181222
ASSERT_TRUE(fake_upstream_connection->waitForData(request_bytes_.length(), &data));
182223
Buffer::OwnedImpl upstream_request(data);
@@ -201,8 +242,9 @@ TEST_P(ThriftConnManagerIntegrationTest, IDLException) {
201242
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));
202243
tcp_client->write(request_bytes_.toString());
203244

245+
FakeUpstream* expected_upstream = getExpectedUpstream(false);
204246
FakeRawConnectionPtr fake_upstream_connection;
205-
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
247+
ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection));
206248
std::string data;
207249
ASSERT_TRUE(fake_upstream_connection->waitForData(request_bytes_.length(), &data));
208250
Buffer::OwnedImpl upstream_request(data);
@@ -227,8 +269,9 @@ TEST_P(ThriftConnManagerIntegrationTest, Exception) {
227269
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));
228270
tcp_client->write(request_bytes_.toString());
229271

272+
FakeUpstream* expected_upstream = getExpectedUpstream(false);
230273
FakeRawConnectionPtr fake_upstream_connection;
231-
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
274+
ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection));
232275
std::string data;
233276
ASSERT_TRUE(fake_upstream_connection->waitForData(request_bytes_.length(), &data));
234277
Buffer::OwnedImpl upstream_request(data);
@@ -253,8 +296,9 @@ TEST_P(ThriftConnManagerIntegrationTest, Oneway) {
253296
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0"));
254297
tcp_client->write(request_bytes_.toString());
255298

299+
FakeUpstream* expected_upstream = getExpectedUpstream(true);
256300
FakeRawConnectionPtr fake_upstream_connection;
257-
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
301+
ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection));
258302
std::string data;
259303
ASSERT_TRUE(fake_upstream_connection->waitForData(request_bytes_.length(), &data));
260304
Buffer::OwnedImpl upstream_request(data);
@@ -274,8 +318,9 @@ TEST_P(ThriftConnManagerIntegrationTest, OnewayEarlyClose) {
274318
tcp_client->write(request_bytes_.toString());
275319
tcp_client->close();
276320

321+
FakeUpstream* expected_upstream = getExpectedUpstream(true);
277322
FakeRawConnectionPtr fake_upstream_connection;
278-
ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection));
323+
ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection));
279324
std::string data;
280325
ASSERT_TRUE(fake_upstream_connection->waitForData(request_bytes_.length(), &data));
281326
Buffer::OwnedImpl upstream_request(data);

0 commit comments

Comments
 (0)