Skip to content

Commit ad22529

Browse files
Vrishal Kulkarnimeta-codesync[bot]
authored andcommitted
Support route handles that must see every request
Summary: A binary that embeds mcrouter sometimes needs a route handle that sees *every* request — an interceptor, a local overlay, a shim in front of production. Today the only way to get one is to author or rewrite the routing config, which means knowing how that config expresses its entry point (`route` vs `routes`, alias lists, `PrefixSelectorRoute` policies), reproducing whatever the config would otherwise have routed to, and re-doing all of it whenever that config changes. Configs that reach their entry point through JSONM macros cannot be edited that way at all, since the leaves do not exist until the preprocessor has run. Give binaries a way to say "put this on top" and let mcrouter resolve the config normally. `ExtraRouteHandleProviderIf::wrapRoot()` is a new hook, defaulting to identity, called from `ProxyRoute`'s constructor once the root handle is built. It is applied after the existing `BigValueRoute` and `LoggingRoute` wrappers and above routing prefix selection, so the injected handle sees every request and is created exactly once per proxy, whatever shape the loaded config has. `McRouteHandleProvider::extraProvider()` exposes the provider so `ProxyConfig` can pass it to `ProxyRoute`, alongside the `release*()` accessors it already calls on that object a few lines earlier. Nothing changes for existing routers: the `ProxyRoute` parameter defaults to `nullptr`, and no in-tree provider overrides `wrapRoot()` in this diff, so every current mcrouter builds an identical route handle tree. The first user is TAO Sandbox's `mock_tao` / `mock_ucache` (next in this stack). It needs `MockTaoRoute` / `MockUcacheRoute` in front of whatever config the flavor and the host's `/etc/mcrouter` overrides resolve to; today it hand-writes a config that bypasses both and has drifted from the real one as a result. Reviewed By: ghostonhuang Differential Revision: D116661224 fbshipit-source-id: e07f7a2f69ca3e95cc35aaf4275d126841ab8964
1 parent 3907970 commit ad22529

6 files changed

Lines changed: 132 additions & 3 deletions

File tree

mcrouter/ProxyConfig-inl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ ProxyConfig<RouterInfo>::ProxyConfig(
152152
.enableAsyncDlBroadcast = enableAsyncDlBroadcast,
153153
.enableSetDistribution = enableSetDistribution,
154154
.enableCrossRegionSetRpc = enableCrossRegionSetRpc,
155-
.enableGlobalBigValueRoute = enableGlobalBigValueRoute});
155+
.enableGlobalBigValueRoute = enableGlobalBigValueRoute},
156+
provider.extraProvider());
156157
serviceInfo_ = std::make_shared<ServiceInfo<RouterInfo>>(proxy, *this);
157158
}
158159

mcrouter/routes/ExtraRouteHandleProviderIf.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ class ExtraRouteHandleProviderIf {
4343
folly::StringPiece type,
4444
const folly::dynamic& json) = 0;
4545

46+
/**
47+
* Wraps the root of the route handle tree, after the config's entry point
48+
* has been turned into route handles.
49+
*
50+
* Lets a binary inject a route handle that must see every request no matter
51+
* how the config expresses its entry point (route vs routes, aliases,
52+
* prefix selectors). The wrapper sits above routing prefix selection, so it
53+
* is created exactly once per proxy.
54+
*/
55+
virtual std::shared_ptr<RouteHandleIf> wrapRoot(
56+
std::shared_ptr<RouteHandleIf> root) {
57+
return root;
58+
}
59+
4660
virtual ~ExtraRouteHandleProviderIf() {}
4761
};
4862
} // namespace mcrouter

mcrouter/routes/McRouteHandleProvider.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ class McRouteHandleProvider
146146
return std::move(accessPoints_);
147147
}
148148

149+
ExtraRouteHandleProviderIf<RouterInfo>* extraProvider() {
150+
return extraProvider_.get();
151+
}
152+
149153
~McRouteHandleProvider() override;
150154

151155
private:

mcrouter/routes/ProxyRoute-inl.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "mcrouter/Proxy.h"
1313
#include "mcrouter/lib/config/RouteHandleBuilder.h"
1414
#include "mcrouter/routes/BigValueRoute.h"
15+
#include "mcrouter/routes/ExtraRouteHandleProviderIf.h"
1516
#include "mcrouter/routes/LoggingRoute.h"
1617
#include "mcrouter/routes/McRouteHandleBuilder.h"
1718

@@ -42,7 +43,8 @@ template <class RouterInfo>
4243
ProxyRoute<RouterInfo>::ProxyRoute(
4344
Proxy<RouterInfo>& proxy,
4445
const RouteSelectorMap<typename RouterInfo::RouteHandleIf>& routeSelectors,
45-
RootRouteRolloutOpts rolloutOpts)
46+
RootRouteRolloutOpts rolloutOpts,
47+
ExtraRouteHandleProviderIf<RouterInfo>* extraProvider)
4648
: proxy_(proxy),
4749
root_(
4850
makeRouteHandleWithInfo<RouterInfo, RootRoute>(
@@ -57,6 +59,11 @@ ProxyRoute<RouterInfo>::ProxyRoute(
5759
if (proxy_.getRouterOptions().enable_logging_route) {
5860
root_ = createLoggingRoute<RouterInfo>(std::move(root_));
5961
}
62+
// Applied last so the injected handle sees every request, before routing
63+
// prefix selection and before any of the wrappers above.
64+
if (extraProvider) {
65+
root_ = extraProvider->wrapRoot(std::move(root_));
66+
}
6067
}
6168

6269
template <class RouterInfo>

mcrouter/routes/ProxyRoute.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ namespace mcrouter {
3131
template <class RouterInfo>
3232
class Proxy;
3333

34+
template <class RouterInfo>
35+
class ExtraRouteHandleProviderIf;
36+
3437
/**
3538
* This is the top-most level of Mcrouter's RouteHandle tree.
3639
*/
@@ -45,7 +48,8 @@ class ProxyRoute {
4548
Proxy<RouterInfo>& proxy,
4649
const RouteSelectorMap<typename RouterInfo::RouteHandleIf>&
4750
routeSelectors,
48-
RootRouteRolloutOpts rolloutOpts);
51+
RootRouteRolloutOpts rolloutOpts,
52+
ExtraRouteHandleProviderIf<RouterInfo>* extraProvider = nullptr);
4953

5054
template <class Request>
5155
bool traverse(

mcrouter/test/cpp_unit_tests/mc_route_handle_provider_test.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
#include "mcrouter/CarbonRouterInstance.h"
1616
#include "mcrouter/PoolFactory.h"
1717
#include "mcrouter/Proxy.h"
18+
#include "mcrouter/lib/RouteHandleTraverser.h"
19+
#include "mcrouter/lib/config/RouteHandleBuilder.h"
1820
#include "mcrouter/lib/config/RouteHandleFactory.h"
1921
#include "mcrouter/lib/network/gen/MemcacheRouterInfo.h"
2022
#include "mcrouter/options.h"
23+
#include "mcrouter/routes/McExtraRouteHandleProvider.h"
2124
#include "mcrouter/routes/McRouteHandleProvider.h"
2225
#include "mcrouter/routes/McrouterRouteHandle.h"
26+
#include "mcrouter/routes/PrefixSelectorRoute.h"
27+
#include "mcrouter/routes/ProxyRoute.h"
28+
#include "mcrouter/routes/RouteSelectorMap.h"
2329

2430
using namespace facebook::memcache;
2531
using namespace facebook::memcache::mcrouter;
@@ -113,6 +119,14 @@ struct TestSetup {
113119
return rhProvider_;
114120
}
115121

122+
RouteHandleFactory<McrouterRouteHandleIf>& factory() {
123+
return rhFactory_;
124+
}
125+
126+
Proxy<MemcacheRouterInfo>& proxy() {
127+
return *router_->getProxy(0);
128+
}
129+
116130
McrouterRouteHandlePtr getRoute(const char* jsonStr) {
117131
return rhFactory_.create(parseJsonString(jsonStr));
118132
}
@@ -131,6 +145,63 @@ struct TestSetup {
131145
}
132146
};
133147

148+
// Recognizable pass-through handle, so a traversal can tell whether the root
149+
// of the tree is the one ProxyRoute built or one an extra provider put there.
150+
template <class RouteHandleIf>
151+
class MarkerRoute {
152+
public:
153+
static std::string routeName() {
154+
return "marker";
155+
}
156+
157+
explicit MarkerRoute(std::shared_ptr<RouteHandleIf> child)
158+
: child_(std::move(child)) {}
159+
160+
template <class Request>
161+
bool traverse(const Request& req, RouteHandleTraverser<RouteHandleIf>& t)
162+
const {
163+
return t(*child_, req);
164+
}
165+
166+
template <class Request>
167+
ReplyT<Request> route(const Request& req) const {
168+
return child_->route(req);
169+
}
170+
171+
private:
172+
std::shared_ptr<RouteHandleIf> child_;
173+
};
174+
175+
class MarkerExtraProvider
176+
: public McExtraRouteHandleProvider<MemcacheRouterInfo> {
177+
public:
178+
McrouterRouteHandlePtr wrapRoot(McrouterRouteHandlePtr root) override {
179+
return makeRouteHandle<McrouterRouteHandleIf, MarkerRoute>(std::move(root));
180+
}
181+
};
182+
183+
// Single-entry selector map, enough to build a ProxyRoute.
184+
RouteSelectorMap<McrouterRouteHandleIf> nullRouteSelectors(TestSetup& setup) {
185+
RouteSelectorMap<McrouterRouteHandleIf> selectors;
186+
selectors[setup.proxy().getRouterOptions().default_route] =
187+
std::make_shared<PrefixSelectorRoute<McrouterRouteHandleIf>>(
188+
setup.factory(), parseJsonString(R"("NullRoute")"));
189+
return selectors;
190+
}
191+
192+
// Name of the first route handle a traversal of `proxyRoute` reaches.
193+
std::string rootRouteName(ProxyRoute<MemcacheRouterInfo>& proxyRoute) {
194+
std::string name;
195+
RouteHandleTraverser<McrouterRouteHandleIf> t{
196+
[&name](const McrouterRouteHandleIf& rh) {
197+
if (name.empty()) {
198+
name = rh.routeName();
199+
}
200+
}};
201+
proxyRoute.traverse(McGetRequest("key"), t);
202+
return name;
203+
}
204+
134205
} // namespace
135206

136207
TEST(McRouteHandleProviderTest, sanity) {
@@ -210,3 +281,31 @@ TEST(McRouteHandleProvider, bucketized_pool_route_and_mcreplay_asynclogRoutes) {
210281
"bucketize|total_buckets=1000|bucketization_keyspace=tst|prefix_map_enabled=false",
211282
asynclogRoutes["test.asynclog"]->routeName());
212283
}
284+
285+
// A provider that does not override wrapRoot leaves the tree alone, so
286+
// existing routers keep the root that ProxyRoute built for them.
287+
TEST(McRouteHandleProvider, wrap_root_defaults_to_identity) {
288+
TestSetup setup;
289+
auto rh = setup.getRoute(kConstShard);
290+
ASSERT_TRUE(setup.provider().extraProvider() != nullptr);
291+
EXPECT_EQ(rh, setup.provider().extraProvider()->wrapRoot(rh));
292+
}
293+
294+
TEST(McRouteHandleProvider, proxy_route_without_provider_is_unwrapped) {
295+
TestSetup setup;
296+
auto selectors = nullRouteSelectors(setup);
297+
ProxyRoute<MemcacheRouterInfo> proxyRoute(
298+
setup.proxy(), selectors, RootRouteRolloutOpts{});
299+
EXPECT_EQ("root", rootRouteName(proxyRoute));
300+
}
301+
302+
// The handle an extra provider returns from wrapRoot ends up above the root,
303+
// so it sees every request no matter how the config selects routes.
304+
TEST(McRouteHandleProvider, proxy_route_applies_wrap_root) {
305+
TestSetup setup;
306+
auto selectors = nullRouteSelectors(setup);
307+
MarkerExtraProvider extraProvider;
308+
ProxyRoute<MemcacheRouterInfo> proxyRoute(
309+
setup.proxy(), selectors, RootRouteRolloutOpts{}, &extraProvider);
310+
EXPECT_EQ("marker", rootRouteName(proxyRoute));
311+
}

0 commit comments

Comments
 (0)