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

router: scoped rds (2a): scoped routing configuration protos #6675

Merged
merged 4 commits into from
Apr 26, 2019
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
21 changes: 21 additions & 0 deletions api/envoy/api/v2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,24 @@ api_go_grpc_library(
"//envoy/api/v2/route:route_go_proto",
],
)

api_proto_library_internal(
name = "srds",
srcs = ["srds.proto"],
has_services = 1,
visibility = [":friends"],
deps = [
":discovery",
"//envoy/api/v2/core:base",
"//envoy/api/v2/route",
],
)

api_go_grpc_library(
name = "srds",
proto = ":srds",
deps = [
":discovery_go_proto",
"//envoy/api/v2/core:base_go_proto",
],
)
142 changes: 142 additions & 0 deletions api/envoy/api/v2/srds.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
syntax = "proto3";

package envoy.api.v2;

option java_outer_classname = "SrdsProto";
option java_package = "io.envoyproxy.envoy.api.v2";
option java_multiple_files = true;
option java_generic_services = true;

import "envoy/api/v2/discovery.proto";

import "google/api/annotations.proto";

import "validate/validate.proto";
import "gogoproto/gogo.proto";

option (gogoproto.equal_all) = true;

// [#protodoc-title: HTTP scoped routing configuration]
// * Routing :ref:`architecture overview <arch_overview_http_routing>`
//
// .. attention::
//
// The Scoped RDS API is not yet fully implemented and *should not* be enabled in
// :ref:`envoy_api_msg_config.filter.network.http_connection_manager.v2.HttpConnectionManager`.
//
// TODO(AndresGuedez): Update :ref:`arch_overview_http_routing` with scoped routing overview and
// configuration details.

// The Scoped Routes Discovery Service (SRDS) API distributes
// :ref:`ScopedRouteConfiguration<envoy_api_msg.ScopedRouteConfiguration>` resources. Each
// ScopedRouteConfiguration resource represents a "routing scope" containing a mapping that allows
// the HTTP connection manager to dynamically assign a routing table (specified via
// a :ref:`RouteConfiguration<envoy_api_msg_RouteConfiguration>` message) to each HTTP request.
// [#proto-status: experimental]
service ScopedRoutesDiscoveryService {
rpc StreamScopedRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
}

rpc DeltaScopedRoutes(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) {
}

rpc FetchScopedRoutes(DiscoveryRequest) returns (DiscoveryResponse) {
option (google.api.http) = {
post: "/v2/discovery:scoped-routes"
body: "*"
};
}
}

// Specifies a routing scope, which associates a
// :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>` to a
// :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name).
//
// The HTTP connection manager builds up a table consisting of these Key to RouteConfiguration
// mappings, and looks up the RouteConfiguration to use per request according to the algorithm
// specified in the
// :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
// assigned to the HttpConnectionManager.
//
// For example, with the following configurations (in YAML):
//
// HttpConnectionManager config:
//
// .. code::
//
// ...
// scoped_routes:
// name: foo-scoped-routes
// scope_key_builder:
// fragments:
// - header_value_extractor:
// name: X-Route-Selector
// element_separator: ,
// element:
// separator: =
// key: vip
//
// ScopedRouteConfiguration resources (specified statically via
// :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`
// or obtained dynamically via SRDS):
//
// .. code::
//
// (1)
// name: route-scope1
// route_configuration_name: route-config1
// key:
// fragments:
// - string_key: 172.10.10.20
//
// (2)
// name: route-scope2
// route_configuration_name: route-config2
// key:
// fragments:
// - string_key: 172.20.20.30
//
// A request from a client such as:
//
// .. code::
//
// GET / HTTP/1.1
// Host: foo.com
// X-Route-Selector: vip=172.10.10.20
//
// would result in the routing table defined by the `route-config1` RouteConfiguration being
// assigned to the HTTP request/stream.
//
// [#comment:next free field: 4]
// [#proto-status: experimental]
message ScopedRouteConfiguration {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether this message should be in the same file as the related HCM protos? Is there any compelling reason to split them like this? It seems like they are very closely related? Don't have a strong opinion on this but putting all of the required definitions together in an srds.proto might make this complicated feature easier to grok? WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My intent is to maintain consistency by having the xDS API "top-level" proto specification under api/envoy/api/v2.

I found it more readable to have the ScopedRoutes proto defined in the http_connection_manager.proto along its consumer rather than pairing it with the ScopedRouteConfiguration proto here. However, this may just be my bias being the implementor so I can certainly consolidate the protos into srds.proto if that is more approachable for those with less context.

Copy link
Member

Choose a reason for hiding this comment

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

@htuch thoughts here? My preference is to keep them together for readability, but I don't feel strongly about it if others do.

Copy link
Member

Choose a reason for hiding this comment

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

I think the split is reasonable to me, given how the service and the builder are used. I think it might even warrant moving to a separate peer proto alongside HCM config (but same package namespace) as it's self-contained, but we can do that optionally in a followup.

// The name assigned to the routing scope.
string name = 1 [(validate.rules).string.min_bytes = 1];

// Specifies a key which is matched against the output of the
// :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
// specified in the HttpConnectionManager. The matching is done per HTTP request and is dependent
// on the order of the fragments contained in the Key.
message Key {
message Fragment {
oneof type {
option (validate.required) = true;

// A string to match against.
string string_key = 1;
}
}

// The ordered set of fragments to match against. The order must match the fragments in the
// corresponding
// :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`.
repeated Fragment fragments = 1 [(validate.rules).repeated .min_items = 1];
}

// The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an RDS server to
// fetch the :ref:`envoy_api_msg_RouteConfiguration` associated with this scope.
string route_configuration_name = 2 [(validate.rules).string.min_bytes = 1];

// The key to match against.
Key key = 3 [(validate.rules).message.required = true];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ api_proto_library_internal(
srcs = ["http_connection_manager.proto"],
deps = [
"//envoy/api/v2:rds",
"//envoy/api/v2:srds",
"//envoy/api/v2/core:base",
"//envoy/api/v2/core:config_source",
"//envoy/api/v2/core:protocol",
Expand All @@ -20,6 +21,7 @@ api_go_proto_library(
proto = ":http_connection_manager",
deps = [
"//envoy/api/v2:rds_go_grpc",
"//envoy/api/v2:srds_go_grpc",
"//envoy/api/v2/core:base_go_proto",
"//envoy/api/v2/core:config_source_go_proto",
"//envoy/api/v2/core:protocol_go_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ option go_package = "v2";
import "envoy/api/v2/core/config_source.proto";
import "envoy/api/v2/core/protocol.proto";
import "envoy/api/v2/rds.proto";
import "envoy/api/v2/srds.proto";
import "envoy/config/filter/accesslog/v2/accesslog.proto";
import "envoy/type/percent.proto";

Expand All @@ -24,7 +25,7 @@ import "gogoproto/gogo.proto";
// [#protodoc-title: HTTP connection manager]
// HTTP connection manager :ref:`configuration overview <config_http_conn_man>`.

// [#comment:next free field: 31]
// [#comment:next free field: 32]
message HttpConnectionManager {
enum CodecType {
option (gogoproto.goproto_enum_prefix) = false;
Expand Down Expand Up @@ -61,6 +62,11 @@ message HttpConnectionManager {

// The route table for the connection manager is static and is specified in this property.
envoy.api.v2.RouteConfiguration route_config = 4;

// A route table will be dynamically assigned to each request based on request attributes
// (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are
// specified in this message.
ScopedRoutes scoped_routes = 31;
}

// A list of individual HTTP filters that make up the filter chain for
Expand Down Expand Up @@ -419,6 +425,119 @@ message Rds {
string route_config_name = 2 [(validate.rules).string.min_bytes = 1];
}

// This message is used to work around the limitations with 'oneof' and repeated fields.
message ScopedRouteConfigurationsList {
repeated envoy.api.v2.ScopedRouteConfiguration scoped_route_configurations = 1
[(validate.rules).repeated .min_items = 1];
}

message ScopedRoutes {
// The name assigned to the scoped routing configuration.
string name = 1 [(validate.rules).string.min_bytes = 1];

// Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These
// keys are matched against a set of :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>`
// objects assembled from :ref:`ScopedRouteConfiguration<envoy_api_msg_ScopedRouteConfiguration>`
// messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via
// :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`.
//
// Upon receiving a request's headers, the Router will build a key using the algorithm specified
// by this message. This key will be used to look up the routing table (i.e., the
// :ref:`RouteConfiguration<envoy_api_msg_RouteConfiguration>`) to use for the request.
message ScopeKeyBuilder {
// Specifies the mechanism for constructing key fragments which are composed into scope keys.
message FragmentBuilder {
// Specifies how the value of a header should be extracted.
// The following example maps the structure of a header to the fields in this message.
//
// .. code::
//
// <0> <1> <-- index
// X-Header: a=b;c=d
// | || |
// | || \----> <element_separator>
// | ||
// | |\----> <element.separator>
// | |
// | \----> <element.key>
// |
// \----> <name>
//
// Each 'a=b' key-value pair constitutes an 'element' of the header field.
message HeaderValueExtractor {
// The name of the header field to extract the value from.
string name = 1 [(validate.rules).string.min_bytes = 1];

// The element separator (e.g., ';' separates 'a;b;c;d').
// Default: empty string. This causes the entirety of the header field to be extracted.
// If this field is set to an empty string and 'index' is used in the oneof below, 'index'
// must be set to 0.
string element_separator = 2;

// Specifies a header field's key value pair to match on.
message KvElement {
// The separator between key and value (e.g., '=' separates 'k=v;...').
string separator = 1 [(validate.rules).string.min_bytes = 1];

// The key to match on.
string key = 2 [(validate.rules).string.min_bytes = 1];
}

oneof extract_type {
// Specifies the zero based index of the element to extract.
uint32 index = 3;

// Specifies the key value pair to extract the value from.
KvElement element = 4;
}
}

oneof type {
option (validate.required) = true;

// Specifies how a header field's value should be extracted.
HeaderValueExtractor header_value_extractor = 1;
}
}

// The final scope key consists of the ordered union of these fragments.
repeated FragmentBuilder fragments = 1 [(validate.rules).repeated .min_items = 1];
}

// The algorithm to use for constructing a scope key for each request.
ScopeKeyBuilder scope_key_builder = 2 [(validate.rules).message.required = true];

// Configuration source specifier for RDS.
// This config source is used to subscribe to RouteConfiguration resources specified in
// ScopedRouteConfiguration messages.
envoy.api.v2.core.ConfigSource rds_config_source = 3
[(validate.rules).message.required = true, (gogoproto.nullable) = false];

oneof config_specifier {
option (validate.required) = true;

// The set of routing scopes corresponding to the HCM. A scope is assigned to a request by
// matching a key constructed from the request's attributes according to the algorithm specified
// by the
// :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
// in this message.
ScopedRouteConfigurationsList scoped_route_configurations_list = 4;

// The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS
// API. A scope is assigned to a request by matching a key constructed from the request's
// attributes according to the algorithm specified by the
// :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
// in this message.
ScopedRds scoped_rds = 5;
}
}

message ScopedRds {
// Configuration source specifier for scoped RDS.
envoy.api.v2.core.ConfigSource scoped_rds_config_source = 1
[(validate.rules).message.required = true, (gogoproto.nullable) = false];
}

message HttpFilter {
// The name of the filter to instantiate. The name must match a supported
// filter. The built-in filters are:
Expand Down
1 change: 1 addition & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ PROTO_RST="
/envoy/api/v2/cluster/circuit_breaker/envoy/api/v2/cluster/circuit_breaker.proto.rst
/envoy/api/v2/rds/envoy/api/v2/rds.proto.rst
/envoy/api/v2/route/route/envoy/api/v2/route/route.proto.rst
/envoy/api/v2/srds/envoy/api/v2/srds.proto.rst
/envoy/api/v2/lds/envoy/api/v2/lds.proto.rst
/envoy/api/v2/listener/listener/envoy/api/v2/listener/listener.proto.rst
/envoy/api/v2/ratelimit/ratelimit/envoy/api/v2/ratelimit/ratelimit.proto.rst
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v2/http_routes/http_routes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ HTTP route management
:maxdepth: 2

../api/v2/rds.proto
../api/v2/srds.proto
../api/v2/route/route.proto
2 changes: 2 additions & 0 deletions tools/spelling_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ SPIFFE
SPKI
SQL
SR
SRDS
SRV
SS
SSL
Expand Down Expand Up @@ -271,6 +272,7 @@ WS
Welford's
Werror
XDS
vip
xDSes
XFCC
XFF
Expand Down