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

route match: Add runtime_fraction field for more granular routing #4217

Merged
merged 15 commits into from
Sep 19, 2018
3 changes: 3 additions & 0 deletions DEPRECATED.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ A logged warning is expected for each deprecated item that is in deprecation win
* Setting hosts via `hosts` field in `Cluster` is deprecated. Use `load_assignment` instead.
* Use of `response_headers_to_*` and `request_headers_to_add` are deprecated at the `RouteAction`
level. Please use the configuration options at the `Route` level.
* Use of `runtime` in `RouteMatch`, found in
[route.proto](https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/route/route.proto).
Set the `runtime_fraction` field instead.
* Use of the string `user` field in `Authenticated` in [rbac.proto](https://github.com/envoyproxy/envoy/blob/master/api/envoy/config/rbac/v2alpha/rbac.proto)
is deprecated in favor of the new `StringMatcher` based `principal_name` field.

Expand Down
4 changes: 4 additions & 0 deletions api/envoy/api/v2/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ api_proto_library_internal(
visibility = [
":friends",
],
deps = [
"//envoy/type:percent",
],
)

api_go_proto_library(
name = "base",
proto = ":base",
deps = ["//envoy/type:percent_go_proto"],
)

api_proto_library_internal(
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/api/v2/core/base.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import "google/protobuf/wrappers.proto";
import "validate/validate.proto";
import "gogoproto/gogo.proto";

import "envoy/type/percent.proto";

option (gogoproto.equal_all) = true;

// [#protodoc-title: Common types]
Expand Down Expand Up @@ -215,3 +217,13 @@ message SocketOption {
SocketState state = 6
[(validate.rules).message.required = true, (validate.rules).enum.defined_only = true];
}

// Runtime derived FractionalPercent with defaults for when the numerator or denominator is not
// specified via a runtime key.
message RuntimeFractionalPercent {
// Default value if the runtime value's for the numerator/denominator keys are not available.
envoy.type.FractionalPercent default_value = 1 [(validate.rules).message.required = true];

// Runtime key for a YAML representation of a FractionalPercent.
string runtime_key = 2;
}
37 changes: 27 additions & 10 deletions api/envoy/api/v2/route/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,33 @@ message RouteMatch {
// is true.
google.protobuf.BoolValue case_sensitive = 4;

// Indicates that the route should additionally match on a runtime key. An
// integer between 0-100. Every time the route is considered for a match, a
// random number between 0-99 is selected. If the number is <= the value found
// in the key (checked first) or, if the key is not present, the default
// value, the route is a match (assuming everything also about the route
// matches). A runtime route configuration can be used to roll out route changes in a
// gradual manner without full code/config deploys. Refer to the
// :ref:`traffic shifting <config_http_conn_man_route_table_traffic_splitting_shift>` docs
// for additional documentation.
core.RuntimeUInt32 runtime = 5;
oneof runtime_specifier {
// Indicates that the route should additionally match on a runtime key. An integer between
// 0-100. Every time the route is considered for a match, a random number between 0-99 is
// selected. If the number is <= the value found in the key (checked first) or, if the key is
// not present, the default value, the route is a match (assuming everything also about the
// route matches). A runtime route configuration can be used to roll out route changes in a
// gradual manner without full code/config deploys. Refer to the :ref:`traffic shifting
// <config_http_conn_man_route_table_traffic_splitting_shift>` docs for additional
// documentation.
//
// .. attention::
//
// **This field is deprecated**. Set the
// :ref:`runtime_fraction<envoy_api_field_route.RouteMatch.runtime_fraction>` field instead.
core.RuntimeUInt32 runtime = 5 [deprecated = true];
tonya11en marked this conversation as resolved.
Show resolved Hide resolved

// Indicates that the route should additionally match on a runtime key. Every time the route
// is considered for a match, it must also fall under the percentage of matches indicated by
// this field. For some fraction N/D, a random number in the range [0,D) is selected. If the
// number is <= the value of the numberator N, or if the key is not present, the default
// value, the router continues to evaluate the remaining match criteria. A runtime_fraction
// route configuration can be used to roll out route changes in a gradual manner (with more
// granularity than the deprecated runtime field) without full code/config deploys. Refer to
// the :ref:`traffic shifting <config_http_conn_man_route_table_traffic_splitting_shift>` docs
// for additional documentation.
core.RuntimeFractionalPercent runtime_fraction = 8;
}

// Specifies a set of headers that the route should match on. The router will
// check the request’s headers against all the specified headers in the route
Expand Down
7 changes: 4 additions & 3 deletions source/common/access_log/access_log_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ bool RuntimeFilter::evaluate(const RequestInfo::RequestInfo&,
const Http::HeaderEntry* uuid = request_header.RequestId();
uint64_t random_value;
if (use_independent_randomness_ || uuid == nullptr ||
!UuidUtils::uuidModBy(uuid->value().c_str(), random_value,
ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent_))) {
!UuidUtils::uuidModBy(
uuid->value().c_str(), random_value,
ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent_.denominator()))) {
random_value = random_.random();
}

return runtime_.snapshot().featureEnabled(
runtime_key_, percent_.numerator(), random_value,
ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent_));
ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent_.denominator()));
}

OperatorFilter::OperatorFilter(const Protobuf::RepeatedPtrField<
Expand Down
5 changes: 3 additions & 2 deletions source/common/protobuf/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ uint64_t convertPercent(double percent, uint64_t max_value) {
return max_value * (percent / 100.0);
}

uint64_t fractionalPercentDenominatorToInt(const envoy::type::FractionalPercent& percent) {
switch (percent.denominator()) {
uint64_t fractionalPercentDenominatorToInt(
const envoy::type::FractionalPercent::DenominatorType& denominator) {
switch (denominator) {
case envoy::type::FractionalPercent::HUNDRED:
return 100;
case envoy::type::FractionalPercent::TEN_THOUSAND:
Expand Down
5 changes: 3 additions & 2 deletions source/common/protobuf/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ uint64_t convertPercent(double percent, uint64_t max_value);

/**
* Convert a fractional percent denominator enum into an integer.
* @param percent supplies percent to convert.
* @param denominator supplies denominator to convert.
* @return the converted denominator.
*/
uint64_t fractionalPercentDenominatorToInt(const envoy::type::FractionalPercent& percent);
uint64_t fractionalPercentDenominatorToInt(
const envoy::type::FractionalPercent::DenominatorType& denominator);

} // namespace ProtobufPercentHelper
} // namespace Envoy
Expand Down
46 changes: 37 additions & 9 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

#include "envoy/http/header_map.h"
#include "envoy/runtime/runtime.h"
#include "envoy/type/percent.pb.validate.h"
#include "envoy/upstream/cluster_manager.h"
#include "envoy/upstream/upstream.h"

#include "common/common/assert.h"
#include "common/common/empty_string.h"
#include "common/common/fmt.h"
#include "common/common/hash.h"
#include "common/common/logger.h"
#include "common/common/utility.h"
#include "common/config/metadata.h"
#include "common/config/rds_json.h"
Expand All @@ -26,6 +28,7 @@
#include "common/http/headers.h"
#include "common/http/utility.h"
#include "common/http/websocket/ws_handler_impl.h"
#include "common/protobuf/protobuf.h"
#include "common/protobuf/utility.h"
#include "common/router/retry_state_impl.h"

Expand Down Expand Up @@ -260,7 +263,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost,
timeout_(PROTOBUF_GET_MS_OR_DEFAULT(route.route(), timeout, DEFAULT_ROUTE_TIMEOUT_MS)),
idle_timeout_(PROTOBUF_GET_OPTIONAL_MS(route.route(), idle_timeout)),
max_grpc_timeout_(PROTOBUF_GET_OPTIONAL_MS(route.route(), max_grpc_timeout)),
runtime_(loadRuntimeData(route.match())), loader_(factory_context.runtime()),
loader_(factory_context.runtime()), runtime_(loadRuntimeData(route.match())),
host_redirect_(route.redirect().host_redirect()),
path_redirect_(route.redirect().path_redirect()),
https_redirect_(route.redirect().https_redirect()),
Expand Down Expand Up @@ -345,8 +348,11 @@ bool RouteEntryImplBase::matchRoute(const Http::HeaderMap& headers, uint64_t ran
bool matches = true;

if (runtime_) {
matches &= loader_.snapshot().featureEnabled(runtime_.value().key_, runtime_.value().default_,
random_value);
matches &= random_value % runtime_->denominator_val_ < runtime_->numerator_val_;
if (!matches) {
// No need to waste further cycles calculating a route match.
return false;
}
}

matches &= Http::HeaderUtility::matchHeaders(headers, config_headers_);
Expand Down Expand Up @@ -404,14 +410,36 @@ void RouteEntryImplBase::finalizeResponseHeaders(
absl::optional<RouteEntryImplBase::RuntimeData>
RouteEntryImplBase::loadRuntimeData(const envoy::api::v2::route::RouteMatch& route_match) {
absl::optional<RuntimeData> runtime;
if (route_match.has_runtime()) {
RuntimeData data;
data.key_ = route_match.runtime().runtime_key();
data.default_ = route_match.runtime().default_value();
runtime = data;
RuntimeData runtime_data;

if (route_match.runtime_specifier_case() == envoy::api::v2::route::RouteMatch::kRuntimeFraction) {
envoy::type::FractionalPercent fractional_percent;
const std::string& fraction_yaml =
loader_.snapshot().get(route_match.runtime_fraction().runtime_key());

try {
MessageUtil::loadFromYamlAndValidate(fraction_yaml, fractional_percent);
} catch (const EnvoyException& ex) {
ENVOY_LOG(error, "failed to parse string value for runtime key {}: {}",
route_match.runtime_fraction().runtime_key(), ex.what());
fractional_percent = route_match.runtime_fraction().default_value();
}

runtime_data.numerator_val_ = fractional_percent.numerator();
runtime_data.denominator_val_ =
ProtobufPercentHelper::fractionalPercentDenominatorToInt(fractional_percent.denominator());
} else if (route_match.runtime_specifier_case() == envoy::api::v2::route::RouteMatch::kRuntime) {
// For backwards compatibility, the deprecated 'runtime' field must be converted to a
// RuntimeData format with a variable denominator type. The 'runtime' field assumes a percentage
// (0-100), so the hard-coded denominator value reflects this.
runtime_data.denominator_val_ = 100;
runtime_data.numerator_val_ = loader_.snapshot().getInteger(
route_match.runtime().runtime_key(), route_match.runtime().default_value());
} else {
return runtime;
}

return runtime;
return runtime_data;
}

void RouteEntryImplBase::finalizePathHeader(Http::HeaderMap& headers,
Expand Down
12 changes: 6 additions & 6 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ class RouteEntryImplBase : public RouteEntry,
public DirectResponseEntry,
public Route,
public PathMatchCriterion,
public std::enable_shared_from_this<RouteEntryImplBase> {
public std::enable_shared_from_this<RouteEntryImplBase>,
Logger::Loggable<Logger::Id::router> {
public:
/**
* @throw EnvoyException with reason if the route configuration contains any errors
Expand Down Expand Up @@ -362,8 +363,8 @@ class RouteEntryImplBase : public RouteEntry,

private:
struct RuntimeData {
std::string key_{};
uint64_t default_{};
uint64_t numerator_val_{};
uint64_t denominator_val_{};
};

class DynamicRouteEntry : public RouteEntry, public Route {
Expand Down Expand Up @@ -494,8 +495,7 @@ class RouteEntryImplBase : public RouteEntry,

typedef std::shared_ptr<WeightedClusterEntry> WeightedClusterEntrySharedPtr;

static absl::optional<RuntimeData>
loadRuntimeData(const envoy::api::v2::route::RouteMatch& route);
absl::optional<RuntimeData> loadRuntimeData(const envoy::api::v2::route::RouteMatch& route);

static std::multimap<std::string, std::string>
parseOpaqueConfig(const envoy::api::v2::route::Route& route);
Expand All @@ -516,8 +516,8 @@ class RouteEntryImplBase : public RouteEntry,
const std::chrono::milliseconds timeout_;
const absl::optional<std::chrono::milliseconds> idle_timeout_;
const absl::optional<std::chrono::milliseconds> max_grpc_timeout_;
const absl::optional<RuntimeData> runtime_;
Runtime::Loader& loader_;
const absl::optional<RuntimeData> runtime_;
const std::string host_redirect_;
const std::string path_redirect_;
const bool https_redirect_;
Expand Down
10 changes: 6 additions & 4 deletions source/extensions/filters/http/fault/fault_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,14 @@ bool FaultFilter::isDelayEnabled() {
bool enabled = config_->runtime().snapshot().featureEnabled(
DELAY_PERCENT_KEY, fault_settings_->delayPercentage().numerator(),
config_->randomGenerator().random(),
ProtobufPercentHelper::fractionalPercentDenominatorToInt(fault_settings_->delayPercentage()));
ProtobufPercentHelper::fractionalPercentDenominatorToInt(
fault_settings_->delayPercentage().denominator()));
if (!downstream_cluster_delay_percent_key_.empty()) {
enabled |= config_->runtime().snapshot().featureEnabled(
downstream_cluster_delay_percent_key_, fault_settings_->delayPercentage().numerator(),
config_->randomGenerator().random(),
ProtobufPercentHelper::fractionalPercentDenominatorToInt(
fault_settings_->delayPercentage()));
fault_settings_->delayPercentage().denominator()));
}
return enabled;
}
Expand All @@ -149,13 +150,14 @@ bool FaultFilter::isAbortEnabled() {
bool enabled = config_->runtime().snapshot().featureEnabled(
ABORT_PERCENT_KEY, fault_settings_->abortPercentage().numerator(),
config_->randomGenerator().random(),
ProtobufPercentHelper::fractionalPercentDenominatorToInt(fault_settings_->abortPercentage()));
ProtobufPercentHelper::fractionalPercentDenominatorToInt(
fault_settings_->abortPercentage().denominator()));
if (!downstream_cluster_abort_percent_key_.empty()) {
enabled |= config_->runtime().snapshot().featureEnabled(
downstream_cluster_abort_percent_key_, fault_settings_->abortPercentage().numerator(),
config_->randomGenerator().random(),
ProtobufPercentHelper::fractionalPercentDenominatorToInt(
fault_settings_->abortPercentage()));
fault_settings_->abortPercentage().denominator()));
}
return enabled;
}
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/filters/network/mongo_proxy/proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ absl::optional<uint64_t> ProxyFilter::delayDuration() {
fault_config_->delayPercentage().numerator(),
generator_.random(),
ProtobufPercentHelper::fractionalPercentDenominatorToInt(
fault_config_->delayPercentage()))) {
fault_config_->delayPercentage().denominator()))) {
return result;
}

Expand Down
Loading