Skip to content
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
23 changes: 22 additions & 1 deletion docs/configuration/http_conn_man/route_config/route.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ next (e.g., redirect, forward, rewrite, etc.).
"priority": "...",
"headers": [],
"rate_limits": [],
"hash_policy": "{...}"
"hash_policy": "{...}",
"opaque_config": []
}

prefix
Expand Down Expand Up @@ -139,6 +140,9 @@ priority
:ref:`headers <config_http_conn_man_route_table_route_headers>`
*(optional, array)* Specifies a set of headers that the route should match on.

:ref:`opaque_config <config_http_conn_man_route_table_opaque_config>`
*(optional, array)* Specifies a set of optional route configuration values that can be accessed by filters.

.. _config_http_conn_man_route_table_route_rate_limits:

:ref:`rate_limits <config_http_conn_man_route_table_rate_limit_config>`
Expand Down Expand Up @@ -334,3 +338,20 @@ header_name
*(required, string)* The name of the request header that will be used to obtain the hash key. If
the request header is not present, the load balancer will use a random number as the hash,
effectively making the load balancing policy random.

.. _config_http_conn_man_route_table_opaque_config:

Opaque Config
-------------

Additional configuration can be provided to filters through the "Opaque Config" mechanism. A
list of properties are specified in the route config. The configuration is uninterpreted
by envoy and can be accessed within a user-defined filter. The configuration is a generic
string map. Nested objects are not supported.

.. code-block:: json

[
{"...": "..."}
]

5 changes: 5 additions & 0 deletions include/envoy/json/json_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ class Object {
*/
virtual void validateSchema(const std::string& schema) const PURE;

/**
* @return the value of the object as a string
*/
virtual std::string asString() const PURE;

/**
* @return true if the JSON object is empty;
*/
Expand Down
6 changes: 6 additions & 0 deletions include/envoy/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ class RouteEntry {
* @return bool true if the :authority header should be overwritten with the upstream hostname.
*/
virtual bool autoHostRewrite() const PURE;

/**
* @return const std::multimap<std::string, std::string> the opaque configuration associated
* with the route
*/
virtual const std::multimap<std::string, std::string>& opaqueConfig() const PURE;
};

/**
Expand Down
1 change: 1 addition & 0 deletions source/common/http/async_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const AsyncStreamImpl::NullRetryPolicy AsyncStreamImpl::RouteEntryImpl::retry_po
const AsyncStreamImpl::NullShadowPolicy AsyncStreamImpl::RouteEntryImpl::shadow_policy_;
const AsyncStreamImpl::NullVirtualHost AsyncStreamImpl::RouteEntryImpl::virtual_host_;
const AsyncStreamImpl::NullRateLimitPolicy AsyncStreamImpl::NullVirtualHost::rate_limit_policy_;
const std::multimap<std::string, std::string> AsyncStreamImpl::RouteEntryImpl::opaque_config_;

AsyncClientImpl::AsyncClientImpl(const Upstream::ClusterInfo& cluster, Stats::Store& stats_store,
Event::Dispatcher& dispatcher,
Expand Down
4 changes: 4 additions & 0 deletions source/common/http/async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,17 @@ class AsyncStreamImpl : public AsyncClient::Stream,
const Router::VirtualCluster* virtualCluster(const Http::HeaderMap&) const override {
return nullptr;
}
const std::multimap<std::string, std::string>& opaqueConfig() const override {
return opaque_config_;
}
const Router::VirtualHost& virtualHost() const override { return virtual_host_; }
bool autoHostRewrite() const override { return false; }

static const NullRateLimitPolicy rate_limit_policy_;
static const NullRetryPolicy retry_policy_;
static const NullShadowPolicy shadow_policy_;
static const NullVirtualHost virtual_host_;
static const std::multimap<std::string, std::string> opaque_config_;

const std::string& cluster_name_;
Optional<std::chrono::milliseconds> timeout_;
Expand Down
4 changes: 4 additions & 0 deletions source/common/json/config_schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,10 @@ const std::string Json::Schema::ROUTE_ENTRY_CONFIGURATION_SCHEMA(R"EOF(
},
"required" : ["header_name"],
"additionalProperties" : false
},
"opaque_config" : {
"type" : "object",
"additionalProperties" : true
}
},
"additionalProperties" : false
Expand Down
7 changes: 7 additions & 0 deletions source/common/json/json_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ class ObjectImplBase : public Object {
}
}

std::string asString() const {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please add dedicated test for this function, including the exception part.

if (!value_.IsString()) {
throw Exception(fmt::format("'{}' is not a string", name_));
}
return value_.GetString();
}

bool empty() const override { return value_.IsObject() && value_.ObjectEmpty(); }

private:
Expand Down
15 changes: 14 additions & 1 deletion source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, const Json:
host_redirect_(route.getString("host_redirect", "")),
path_redirect_(route.getString("path_redirect", "")), retry_policy_(route),
rate_limit_policy_(route), shadow_policy_(route),
priority_(ConfigUtility::parsePriority(route)) {
priority_(ConfigUtility::parsePriority(route)), opaque_config_(parseOpaqueConfig(route)) {

route.validateSchema(Json::Schema::ROUTE_ENTRY_CONFIGURATION_SCHEMA);

Expand Down Expand Up @@ -250,6 +250,19 @@ std::string RouteEntryImplBase::newPath(const Http::HeaderMap& headers) const {
final_path);
}

std::multimap<std::string, std::string>
RouteEntryImplBase::parseOpaqueConfig(const Json::Object& route) {
std::multimap<std::string, std::string> ret;
if (route.hasObject("opaque_config")) {
Json::ObjectPtr obj = route.getObject("opaque_config");
obj->iterate([&ret, &obj](const std::string& name, const Json::Object& value) {
ret.emplace(name, value.asString());
return true;
});
}
return ret;
}

const RedirectEntry* RouteEntryImplBase::redirectEntry() const {
// A route for a request can exclusively be a route entry or a redirect entry.
if (isRedirect()) {
Expand Down
10 changes: 10 additions & 0 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ class RouteEntryImplBase : public RouteEntry,
std::chrono::milliseconds timeout() const override { return timeout_; }
const VirtualHost& virtualHost() const override { return vhost_; }
bool autoHostRewrite() const override { return auto_host_rewrite_; }
const std::multimap<std::string, std::string>& opaqueConfig() const override {
return opaque_config_;
}

// Router::RedirectEntry
std::string newPath(const Http::HeaderMap& headers) const override;
Expand Down Expand Up @@ -258,6 +261,10 @@ class RouteEntryImplBase : public RouteEntry,
return parent_->virtualCluster(headers);
}

const std::multimap<std::string, std::string>& opaqueConfig() const override {
return parent_->opaqueConfig();
}

const VirtualHost& virtualHost() const override { return parent_->virtualHost(); }
bool autoHostRewrite() const override { return parent_->autoHostRewrite(); }

Expand Down Expand Up @@ -299,6 +306,8 @@ class RouteEntryImplBase : public RouteEntry,

static Optional<RuntimeData> loadRuntimeData(const Json::Object& route);

static std::multimap<std::string, std::string> parseOpaqueConfig(const Json::Object& route);

// Default timeout is 15s if nothing is specified in the route config.
static const uint64_t DEFAULT_ROUTE_TIMEOUT_MS = 15000;

Expand All @@ -318,6 +327,7 @@ class RouteEntryImplBase : public RouteEntry,
std::vector<ConfigUtility::HeaderData> config_headers_;
std::vector<WeightedClusterEntryPtr> weighted_clusters_;
std::unique_ptr<const HashPolicyImpl> hash_policy_;
const std::multimap<std::string, std::string> opaque_config_;
};

/**
Expand Down
14 changes: 14 additions & 0 deletions test/common/json/json_loader_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,18 @@ TEST(JsonLoaderTest, Schema) {
EXPECT_NO_THROW(json->validateSchema(valid_schema));
}

TEST(JsonLoaderTest, AsString) {
ObjectPtr json = Factory::LoadFromString("{\"name1\": \"value1\", \"name2\": true}");
json->iterate([&](const std::string& key, const Json::Object& value) {
EXPECT_TRUE(key == "name1" || key == "name2");

if (key == "name1") {
EXPECT_EQ("value1", value.asString());
} else {
EXPECT_THROW(value.asString(), Exception);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should throw a JsonException

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nevermind, sorry

}
return true;
});
}

} // Json
41 changes: 41 additions & 0 deletions test/common/router/config_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1628,4 +1628,45 @@ TEST(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndPath) {
EXPECT_THROW(ConfigImpl(*loader, runtime, cm, true), EnvoyException);
}

TEST(RouteMatcherTest, TestOpaqueConfig) {
std::string json = R"EOF(
{
"virtual_hosts": [
{
"name": "default",
"domains": ["*"],
"routes": [
{
"prefix": "/api",
"cluster": "ats",
"opaque_config" : {
"name1": "value1",
"name2": "value2",
"name1": "value3"
}
}
]
}
]
}
)EOF";

Json::ObjectPtr loader = Json::Factory::LoadFromString(json);
NiceMock<Runtime::MockLoader> runtime;
NiceMock<Upstream::MockClusterManager> cm;
ConfigImpl config(*loader, runtime, cm, true);

const std::multimap<std::string, std::string>& opaque_config =
config.route(genHeaders("api.lyft.com", "/api", "GET"), 0)->routeEntry()->opaqueConfig();

EXPECT_EQ(2u, opaque_config.count("name1"));
auto range = opaque_config.equal_range("name1");
auto it = range.first;
EXPECT_EQ("value1", it->second);
++it;
EXPECT_EQ("value3", it->second);

EXPECT_EQ("value2", opaque_config.find("name2")->second);
}

} // Router
1 change: 1 addition & 0 deletions test/mocks/router/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class MockRouteEntry : public RouteEntry {
MOCK_CONST_METHOD0(virtualHostName, const std::string&());
MOCK_CONST_METHOD0(virtualHost, const VirtualHost&());
MOCK_CONST_METHOD0(autoHostRewrite, bool());
MOCK_CONST_METHOD0(opaqueConfig, const std::multimap<std::string, std::string>&());

std::string cluster_name_{"fake_cluster"};
TestVirtualCluster virtual_cluster_;
Expand Down