diff --git a/docs/configuration/http_conn_man/route_config/route.rst b/docs/configuration/http_conn_man/route_config/route.rst index 161f418f7c0fd..9d366b06ae3aa 100644 --- a/docs/configuration/http_conn_man/route_config/route.rst +++ b/docs/configuration/http_conn_man/route_config/route.rst @@ -27,7 +27,8 @@ next (e.g., redirect, forward, rewrite, etc.). "priority": "...", "headers": [], "rate_limits": [], - "hash_policy": "{...}" + "hash_policy": "{...}", + "opaque_config": [] } prefix @@ -139,6 +140,9 @@ priority :ref:`headers ` *(optional, array)* Specifies a set of headers that the route should match on. +:ref:`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 ` @@ -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 + + [ + {"...": "..."} + ] + diff --git a/include/envoy/json/json_object.h b/include/envoy/json/json_object.h index a00a84ad37626..e4daf77eb7089 100644 --- a/include/envoy/json/json_object.h +++ b/include/envoy/json/json_object.h @@ -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; */ diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index 5f1d74adb05ce..05ad97306e119 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -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 the opaque configuration associated + * with the route + */ + virtual const std::multimap& opaqueConfig() const PURE; }; /** diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 19470d94823cd..8b3fb28f5476b 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -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 AsyncStreamImpl::RouteEntryImpl::opaque_config_; AsyncClientImpl::AsyncClientImpl(const Upstream::ClusterInfo& cluster, Stats::Store& stats_store, Event::Dispatcher& dispatcher, diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index eab69bcaf4899..99a1976c38251 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -128,6 +128,9 @@ class AsyncStreamImpl : public AsyncClient::Stream, const Router::VirtualCluster* virtualCluster(const Http::HeaderMap&) const override { return nullptr; } + const std::multimap& opaqueConfig() const override { + return opaque_config_; + } const Router::VirtualHost& virtualHost() const override { return virtual_host_; } bool autoHostRewrite() const override { return false; } @@ -135,6 +138,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, static const NullRetryPolicy retry_policy_; static const NullShadowPolicy shadow_policy_; static const NullVirtualHost virtual_host_; + static const std::multimap opaque_config_; const std::string& cluster_name_; Optional timeout_; diff --git a/source/common/json/config_schemas.cc b/source/common/json/config_schemas.cc index 85bd0ff050c91..2e96e4ea0f92a 100644 --- a/source/common/json/config_schemas.cc +++ b/source/common/json/config_schemas.cc @@ -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 diff --git a/source/common/json/json_loader.cc b/source/common/json/json_loader.cc index 532210fdce60d..bb1a5a7d019f9 100644 --- a/source/common/json/json_loader.cc +++ b/source/common/json/json_loader.cc @@ -182,6 +182,13 @@ class ObjectImplBase : public Object { } } + std::string asString() const { + 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: diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 8504c83256575..d80825b185d88 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -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); @@ -250,6 +250,19 @@ std::string RouteEntryImplBase::newPath(const Http::HeaderMap& headers) const { final_path); } +std::multimap +RouteEntryImplBase::parseOpaqueConfig(const Json::Object& route) { + std::multimap 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()) { diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 2e51dfb007586..d64a0769f9b9a 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -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& opaqueConfig() const override { + return opaque_config_; + } // Router::RedirectEntry std::string newPath(const Http::HeaderMap& headers) const override; @@ -258,6 +261,10 @@ class RouteEntryImplBase : public RouteEntry, return parent_->virtualCluster(headers); } + const std::multimap& opaqueConfig() const override { + return parent_->opaqueConfig(); + } + const VirtualHost& virtualHost() const override { return parent_->virtualHost(); } bool autoHostRewrite() const override { return parent_->autoHostRewrite(); } @@ -299,6 +306,8 @@ class RouteEntryImplBase : public RouteEntry, static Optional loadRuntimeData(const Json::Object& route); + static std::multimap 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; @@ -318,6 +327,7 @@ class RouteEntryImplBase : public RouteEntry, std::vector config_headers_; std::vector weighted_clusters_; std::unique_ptr hash_policy_; + const std::multimap opaque_config_; }; /** diff --git a/test/common/json/json_loader_test.cc b/test/common/json/json_loader_test.cc index 556c818a559b8..4f5159a1fc4ce 100644 --- a/test/common/json/json_loader_test.cc +++ b/test/common/json/json_loader_test.cc @@ -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); + } + return true; + }); +} + } // Json diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 4fc5cfa6d41a4..2a286f14eb2a8 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -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; + NiceMock cm; + ConfigImpl config(*loader, runtime, cm, true); + + const std::multimap& 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 diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 24c61d2b441f4..3a35c5508ef87 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -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 cluster_name_{"fake_cluster"}; TestVirtualCluster virtual_cluster_;