2018-10-03 18:18:55 +00:00
package xds
import (
"errors"
2019-07-02 03:10:51 +00:00
"fmt"
2020-07-07 15:43:04 +00:00
"net"
2019-07-24 01:56:39 +00:00
"strings"
2020-09-12 00:34:03 +00:00
"time"
2018-10-03 18:18:55 +00:00
2021-02-26 22:23:15 +00:00
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
2021-02-22 21:00:15 +00:00
2020-06-23 20:19:56 +00:00
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
2021-02-22 21:00:15 +00:00
2020-08-28 20:27:40 +00:00
"github.com/hashicorp/consul/agent/connect"
2018-10-03 18:18:55 +00:00
"github.com/hashicorp/consul/agent/proxycfg"
2019-07-02 03:10:51 +00:00
"github.com/hashicorp/consul/agent/structs"
2020-09-11 16:49:26 +00:00
"github.com/hashicorp/consul/logging"
2018-10-03 18:18:55 +00:00
)
2019-07-02 03:10:51 +00:00
// routesFromSnapshot returns the xDS API representation of the "routes" in the
// snapshot.
2020-09-03 22:19:58 +00:00
func ( s * Server ) routesFromSnapshot ( cInfo connectionInfo , cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2018-10-03 18:18:55 +00:00
if cfgSnap == nil {
return nil , errors . New ( "nil config given" )
}
2019-07-02 03:10:51 +00:00
switch cfgSnap . Kind {
case structs . ServiceKindConnectProxy :
2020-08-28 20:27:40 +00:00
return routesForConnectProxy ( cInfo , cfgSnap . Proxy . Upstreams , cfgSnap . ConnectProxy . DiscoveryChain )
2020-04-03 19:41:10 +00:00
case structs . ServiceKindIngressGateway :
2020-08-28 20:27:40 +00:00
return routesForIngressGateway ( cInfo , cfgSnap . IngressGateway . Upstreams , cfgSnap . IngressGateway . DiscoveryChain )
case structs . ServiceKindTerminatingGateway :
2020-09-03 22:19:58 +00:00
return s . routesFromSnapshotTerminatingGateway ( cInfo , cfgSnap )
2019-07-02 03:10:51 +00:00
default :
return nil , fmt . Errorf ( "Invalid service kind: %v" , cfgSnap . Kind )
}
}
2020-09-11 16:49:26 +00:00
// routesFromSnapshotConnectProxy returns the xDS API representation of the
// "routes" in the snapshot.
func routesForConnectProxy (
cInfo connectionInfo ,
upstreams structs . Upstreams ,
chains map [ string ] * structs . CompiledDiscoveryChain ,
) ( [ ] proto . Message , error ) {
var resources [ ] proto . Message
for _ , u := range upstreams {
upstreamID := u . Identifier ( )
var chain * structs . CompiledDiscoveryChain
if u . DestinationType != structs . UpstreamDestTypePreparedQuery {
chain = chains [ upstreamID ]
}
if chain == nil || chain . IsDefault ( ) {
// TODO(rb): make this do the old school stuff too
} else {
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( cInfo , upstreamID , chain , [ ] string { "*" } )
if err != nil {
return nil , err
}
2021-02-26 22:23:15 +00:00
route := & envoy_route_v3 . RouteConfiguration {
2020-09-11 16:49:26 +00:00
Name : upstreamID ,
2021-02-26 22:23:15 +00:00
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost { virtualHost } ,
2020-09-11 16:49:26 +00:00
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the sane value of true to prevent
// null-routing traffic.
ValidateClusters : makeBoolValue ( true ) ,
}
resources = append ( resources , route )
}
}
// TODO(rb): make sure we don't generate an empty result
return resources , nil
}
2020-08-28 20:27:40 +00:00
// routesFromSnapshotTerminatingGateway returns the xDS API representation of the "routes" in the snapshot.
// For any HTTP service we will return a default route.
2020-09-03 22:19:58 +00:00
func ( s * Server ) routesFromSnapshotTerminatingGateway ( _ connectionInfo , cfgSnap * proxycfg . ConfigSnapshot ) ( [ ] proto . Message , error ) {
2019-07-02 03:10:51 +00:00
if cfgSnap == nil {
return nil , errors . New ( "nil config given" )
}
2020-09-03 22:19:58 +00:00
logger := s . Logger . Named ( logging . TerminatingGateway )
2019-07-02 03:10:51 +00:00
var resources [ ] proto . Message
2020-09-02 15:10:50 +00:00
for _ , svc := range cfgSnap . TerminatingGateway . ValidServices ( ) {
2020-08-28 20:27:40 +00:00
clusterName := connect . ServiceSNI ( svc . Name , "" , svc . NamespaceOrDefault ( ) , cfgSnap . Datacenter , cfgSnap . Roots . TrustDomain )
resolver , hasResolver := cfgSnap . TerminatingGateway . ServiceResolvers [ svc ]
svcConfig := cfgSnap . TerminatingGateway . ServiceConfigs [ svc ]
cfg , err := ParseProxyConfig ( svcConfig . ProxyConfig )
2020-09-03 16:21:20 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to parse upstream config: %v" , err )
}
if ! structs . IsProtocolHTTPLike ( cfg . Protocol ) {
2020-08-28 20:27:40 +00:00
// Routes can only be defined for HTTP services
continue
}
if ! hasResolver {
// Use a zero value resolver with no timeout and no subsets
resolver = & structs . ServiceResolverConfigEntry { }
}
2020-09-02 15:10:50 +00:00
2020-09-11 15:21:43 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if resolver . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = resolver . LoadBalancer
2020-09-02 15:10:50 +00:00
}
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb )
2020-08-28 20:27:40 +00:00
if err != nil {
2020-09-03 22:19:58 +00:00
logger . Error ( "failed to make route" , "cluster" , clusterName , "error" , err )
2020-08-28 20:27:40 +00:00
continue
}
resources = append ( resources , route )
// If there is a service-resolver for this service then also setup routes for each subset
for name := range resolver . Subsets {
clusterName = connect . ServiceSNI ( svc . Name , name , svc . NamespaceOrDefault ( ) , cfgSnap . Datacenter , cfgSnap . Roots . TrustDomain )
2020-09-02 15:10:50 +00:00
route , err := makeNamedDefaultRouteWithLB ( clusterName , lb )
2020-08-28 20:27:40 +00:00
if err != nil {
2020-09-03 22:19:58 +00:00
logger . Error ( "failed to make route" , "cluster" , clusterName , "error" , err )
2020-08-28 20:27:40 +00:00
continue
}
resources = append ( resources , route )
}
}
return resources , nil
}
2021-02-26 22:23:15 +00:00
func makeNamedDefaultRouteWithLB ( clusterName string , lb * structs . LoadBalancer ) ( * envoy_route_v3 . RouteConfiguration , error ) {
2020-08-28 20:27:40 +00:00
action := makeRouteActionFromName ( clusterName )
2020-09-02 15:10:50 +00:00
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , action . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2021-02-26 22:23:15 +00:00
return & envoy_route_v3 . RouteConfiguration {
2020-08-28 20:27:40 +00:00
Name : clusterName ,
2021-02-26 22:23:15 +00:00
VirtualHosts : [ ] * envoy_route_v3 . VirtualHost {
2020-08-28 20:27:40 +00:00
{
Name : clusterName ,
Domains : [ ] string { "*" } ,
2021-02-26 22:23:15 +00:00
Routes : [ ] * envoy_route_v3 . Route {
2020-08-28 20:27:40 +00:00
{
Match : makeDefaultRouteMatch ( ) ,
Action : action ,
} ,
} ,
} ,
} ,
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the sane value of true to prevent
// null-routing traffic.
ValidateClusters : makeBoolValue ( true ) ,
} , nil
}
// routesForIngressGateway returns the xDS API representation of the
2020-04-16 23:24:11 +00:00
// "routes" in the snapshot.
2020-08-28 20:27:40 +00:00
func routesForIngressGateway (
cInfo connectionInfo ,
upstreams map [ proxycfg . IngressListenerKey ] structs . Upstreams ,
chains map [ string ] * structs . CompiledDiscoveryChain ,
) ( [ ] proto . Message , error ) {
2020-04-16 23:24:11 +00:00
var result [ ] proto . Message
2020-08-28 20:27:40 +00:00
for listenerKey , upstreams := range upstreams {
2020-04-16 23:24:11 +00:00
// Do not create any route configuration for TCP listeners
if listenerKey . Protocol == "tcp" {
continue
}
2021-02-26 22:23:15 +00:00
upstreamRoute := & envoy_route_v3 . RouteConfiguration {
2020-04-16 23:24:11 +00:00
Name : listenerKey . RouteName ( ) ,
// ValidateClusters defaults to true when defined statically and false
// when done via RDS. Re-set the sane value of true to prevent
// null-routing traffic.
ValidateClusters : makeBoolValue ( true ) ,
}
for _ , u := range upstreams {
upstreamID := u . Identifier ( )
2020-08-28 20:27:40 +00:00
chain := chains [ upstreamID ]
2020-04-23 15:06:19 +00:00
if chain == nil {
continue
}
2020-07-07 15:43:04 +00:00
domains := generateUpstreamIngressDomains ( listenerKey , u )
2020-07-09 22:04:51 +00:00
virtualHost , err := makeUpstreamRouteForDiscoveryChain ( cInfo , upstreamID , chain , domains )
2020-04-23 15:06:19 +00:00
if err != nil {
return nil , err
2020-04-16 23:24:11 +00:00
}
2020-04-23 15:06:19 +00:00
upstreamRoute . VirtualHosts = append ( upstreamRoute . VirtualHosts , virtualHost )
2020-04-16 23:24:11 +00:00
}
result = append ( result , upstreamRoute )
}
return result , nil
}
2020-07-07 15:43:04 +00:00
func generateUpstreamIngressDomains ( listenerKey proxycfg . IngressListenerKey , u structs . Upstream ) [ ] string {
var domains [ ] string
domainsSet := make ( map [ string ] bool )
namespace := u . GetEnterpriseMeta ( ) . NamespaceOrDefault ( )
switch {
case len ( u . IngressHosts ) > 0 :
// If a user has specified hosts, do not add the default
// "<service-name>.ingress.*" prefixes
domains = u . IngressHosts
case namespace != structs . IntentionDefaultNamespace :
domains = [ ] string { fmt . Sprintf ( "%s.ingress.%s.*" , u . DestinationName , namespace ) }
default :
domains = [ ] string { fmt . Sprintf ( "%s.ingress.*" , u . DestinationName ) }
}
for _ , h := range domains {
domainsSet [ h ] = true
}
// Host headers may contain port numbers in them, so we need to make sure
// we match on the host with and without the port number. Well-known
// ports like HTTP/HTTPS are stripped from Host headers, but other ports
// will be in the header.
for _ , h := range domains {
_ , _ , err := net . SplitHostPort ( h )
// Error message from Go's net/ipsock.go
// We check to see if a port is not missing, and ignore the
// error from SplitHostPort otherwise, since we have previously
// validated the Host values and should trust the user's input.
if err == nil || ! strings . Contains ( err . Error ( ) , "missing port in address" ) {
continue
}
domainWithPort := fmt . Sprintf ( "%s:%d" , h , listenerKey . Port )
// Do not add a duplicate domain if a hostname with port is already in the
// set
if ! domainsSet [ domainWithPort ] {
domains = append ( domains , domainWithPort )
}
}
return domains
}
2019-07-02 03:10:51 +00:00
func makeUpstreamRouteForDiscoveryChain (
2020-07-09 22:04:51 +00:00
cInfo connectionInfo ,
2020-04-16 23:24:11 +00:00
routeName string ,
2019-07-02 03:10:51 +00:00
chain * structs . CompiledDiscoveryChain ,
2020-04-23 15:06:19 +00:00
serviceDomains [ ] string ,
2021-02-26 22:23:15 +00:00
) ( * envoy_route_v3 . VirtualHost , error ) {
var routes [ ] * envoy_route_v3 . Route
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
startNode := chain . Nodes [ chain . StartNode ]
if startNode == nil {
2020-08-12 16:19:20 +00:00
return nil , fmt . Errorf ( "missing first node in compiled discovery chain for: %s" , chain . ServiceName )
2019-08-02 03:44:05 +00:00
}
switch startNode . Type {
2019-07-02 03:10:51 +00:00
case structs . DiscoveryGraphNodeTypeRouter :
2021-02-26 22:23:15 +00:00
routes = make ( [ ] * envoy_route_v3 . Route , 0 , len ( startNode . Routes ) )
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
for _ , discoveryRoute := range startNode . Routes {
2020-07-09 22:04:51 +00:00
routeMatch := makeRouteMatchForDiscoveryRoute ( cInfo , discoveryRoute )
2019-07-02 03:10:51 +00:00
var (
2021-02-26 22:23:15 +00:00
routeAction * envoy_route_v3 . Route_Route
2019-07-02 03:10:51 +00:00
err error
)
2019-08-02 03:44:05 +00:00
nextNode := chain . Nodes [ discoveryRoute . NextNode ]
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if nextNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = nextNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2019-08-02 03:44:05 +00:00
switch nextNode . Type {
case structs . DiscoveryGraphNodeTypeSplitter :
2020-04-03 19:41:10 +00:00
routeAction , err = makeRouteActionForSplitter ( nextNode . Splits , chain )
2019-07-02 03:10:51 +00:00
if err != nil {
2020-06-23 20:19:56 +00:00
return nil , err
2019-07-02 03:10:51 +00:00
}
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2019-08-02 03:44:05 +00:00
case structs . DiscoveryGraphNodeTypeResolver :
2020-08-28 20:27:40 +00:00
routeAction = makeRouteActionForChainCluster ( nextNode . Resolver . Target , chain )
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
default :
2020-06-23 20:19:56 +00:00
return nil , fmt . Errorf ( "unexpected graph node after route %q" , nextNode . Type )
2019-07-02 03:10:51 +00:00
}
2019-07-12 19:16:21 +00:00
// TODO(rb): Better help handle the envoy case where you need (prefix=/foo/,rewrite=/) and (exact=/foo,rewrite=/) to do a full rewrite
destination := discoveryRoute . Definition . Destination
if destination != nil {
if destination . PrefixRewrite != "" {
routeAction . Route . PrefixRewrite = destination . PrefixRewrite
}
if destination . RequestTimeout > 0 {
2020-06-23 20:19:56 +00:00
routeAction . Route . Timeout = ptypes . DurationProto ( destination . RequestTimeout )
2019-07-12 19:16:21 +00:00
}
if destination . HasRetryFeatures ( ) {
2021-02-26 22:23:15 +00:00
retryPolicy := & envoy_route_v3 . RetryPolicy { }
2019-07-12 19:16:21 +00:00
if destination . NumRetries > 0 {
retryPolicy . NumRetries = makeUint32Value ( int ( destination . NumRetries ) )
}
// The RetryOn magic values come from: https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/http_filters/router_filter#config-http-filters-router-x-envoy-retry-on
if destination . RetryOnConnectFailure {
retryPolicy . RetryOn = "connect-failure"
}
if len ( destination . RetryOnStatusCodes ) > 0 {
if retryPolicy . RetryOn != "" {
2020-02-18 15:25:47 +00:00
retryPolicy . RetryOn = retryPolicy . RetryOn + ",retriable-status-codes"
2019-07-12 19:16:21 +00:00
} else {
retryPolicy . RetryOn = "retriable-status-codes"
}
retryPolicy . RetriableStatusCodes = destination . RetryOnStatusCodes
}
routeAction . Route . RetryPolicy = retryPolicy
}
}
2021-02-26 22:23:15 +00:00
routes = append ( routes , & envoy_route_v3 . Route {
2019-07-02 03:10:51 +00:00
Match : routeMatch ,
Action : routeAction ,
} )
}
case structs . DiscoveryGraphNodeTypeSplitter :
2020-04-03 19:41:10 +00:00
routeAction , err := makeRouteActionForSplitter ( startNode . Splits , chain )
2019-07-02 03:10:51 +00:00
if err != nil {
2020-06-23 20:19:56 +00:00
return nil , err
2019-07-02 03:10:51 +00:00
}
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if startNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = startNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2021-02-26 22:23:15 +00:00
defaultRoute := & envoy_route_v3 . Route {
2019-07-02 03:10:51 +00:00
Match : makeDefaultRouteMatch ( ) ,
Action : routeAction ,
}
2021-02-26 22:23:15 +00:00
routes = [ ] * envoy_route_v3 . Route { defaultRoute }
2019-07-02 03:10:51 +00:00
2019-08-02 03:44:05 +00:00
case structs . DiscoveryGraphNodeTypeResolver :
2020-08-28 20:27:40 +00:00
routeAction := makeRouteActionForChainCluster ( startNode . Resolver . Target , chain )
2020-09-11 16:49:26 +00:00
var lb * structs . LoadBalancer
2020-09-02 15:10:50 +00:00
if startNode . LoadBalancer != nil {
2020-09-11 15:21:43 +00:00
lb = startNode . LoadBalancer
2020-09-02 15:10:50 +00:00
}
2020-09-02 21:13:50 +00:00
if err := injectLBToRouteAction ( lb , routeAction . Route ) ; err != nil {
2020-08-28 20:27:40 +00:00
return nil , fmt . Errorf ( "failed to apply load balancer configuration to route action: %v" , err )
}
2019-07-02 03:10:51 +00:00
2021-02-26 22:23:15 +00:00
defaultRoute := & envoy_route_v3 . Route {
2019-07-02 03:10:51 +00:00
Match : makeDefaultRouteMatch ( ) ,
Action : routeAction ,
}
2021-02-26 22:23:15 +00:00
routes = [ ] * envoy_route_v3 . Route { defaultRoute }
2019-07-02 03:10:51 +00:00
default :
2020-08-12 16:19:20 +00:00
return nil , fmt . Errorf ( "unknown first node in discovery chain of type: %s" , startNode . Type )
2019-07-02 03:10:51 +00:00
}
2021-02-26 22:23:15 +00:00
host := & envoy_route_v3 . VirtualHost {
2020-04-16 23:24:11 +00:00
Name : routeName ,
2020-04-23 15:06:19 +00:00
Domains : serviceDomains ,
2020-04-16 23:24:11 +00:00
Routes : routes ,
}
return host , nil
2019-07-02 03:10:51 +00:00
}
2021-02-26 22:23:15 +00:00
func makeRouteMatchForDiscoveryRoute ( _ connectionInfo , discoveryRoute * structs . DiscoveryRoute ) * envoy_route_v3 . RouteMatch {
2019-07-02 03:10:51 +00:00
match := discoveryRoute . Definition . Match
if match == nil || match . IsEmpty ( ) {
return makeDefaultRouteMatch ( )
}
2021-02-26 22:23:15 +00:00
em := & envoy_route_v3 . RouteMatch { }
2019-07-02 03:10:51 +00:00
switch {
case match . HTTP . PathExact != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Path {
2019-07-12 19:16:21 +00:00
Path : match . HTTP . PathExact ,
}
2019-07-02 03:10:51 +00:00
case match . HTTP . PathPrefix != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Prefix {
2019-07-12 19:16:21 +00:00
Prefix : match . HTTP . PathPrefix ,
}
2019-07-02 03:10:51 +00:00
case match . HTTP . PathRegex != "" :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_SafeRegex {
2020-07-31 20:52:49 +00:00
SafeRegex : makeEnvoyRegexMatch ( match . HTTP . PathRegex ) ,
2019-07-12 19:16:21 +00:00
}
2019-07-02 03:10:51 +00:00
default :
2021-02-26 22:23:15 +00:00
em . PathSpecifier = & envoy_route_v3 . RouteMatch_Prefix {
2019-07-12 19:16:21 +00:00
Prefix : "/" ,
}
2019-07-02 03:10:51 +00:00
}
if len ( match . HTTP . Header ) > 0 {
2021-02-26 22:23:15 +00:00
em . Headers = make ( [ ] * envoy_route_v3 . HeaderMatcher , 0 , len ( match . HTTP . Header ) )
2019-07-02 03:10:51 +00:00
for _ , hdr := range match . HTTP . Header {
2021-02-26 22:23:15 +00:00
eh := & envoy_route_v3 . HeaderMatcher {
2019-07-02 03:10:51 +00:00
Name : hdr . Name ,
}
switch {
case hdr . Exact != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_ExactMatch {
2019-07-02 03:10:51 +00:00
ExactMatch : hdr . Exact ,
}
case hdr . Regex != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_SafeRegexMatch {
2020-07-31 20:52:49 +00:00
SafeRegexMatch : makeEnvoyRegexMatch ( hdr . Regex ) ,
2019-07-02 03:10:51 +00:00
}
case hdr . Prefix != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_PrefixMatch {
2019-07-02 03:10:51 +00:00
PrefixMatch : hdr . Prefix ,
}
case hdr . Suffix != "" :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_SuffixMatch {
2019-07-02 03:10:51 +00:00
SuffixMatch : hdr . Suffix ,
}
case hdr . Present :
2021-02-26 22:23:15 +00:00
eh . HeaderMatchSpecifier = & envoy_route_v3 . HeaderMatcher_PresentMatch {
2019-07-02 03:10:51 +00:00
PresentMatch : true ,
}
default :
continue // skip this impossible situation
}
if hdr . Invert {
eh . InvertMatch = true
}
em . Headers = append ( em . Headers , eh )
}
}
2019-07-24 01:56:39 +00:00
if len ( match . HTTP . Methods ) > 0 {
methodHeaderRegex := strings . Join ( match . HTTP . Methods , "|" )
2021-02-26 22:23:15 +00:00
eh := & envoy_route_v3 . HeaderMatcher {
2019-07-24 01:56:39 +00:00
Name : ":method" ,
2021-02-26 22:23:15 +00:00
HeaderMatchSpecifier : & envoy_route_v3 . HeaderMatcher_SafeRegexMatch {
2020-07-09 22:04:51 +00:00
SafeRegexMatch : makeEnvoyRegexMatch ( methodHeaderRegex ) ,
2020-07-31 20:52:49 +00:00
} ,
2019-07-24 01:56:39 +00:00
}
2020-07-09 22:04:51 +00:00
2019-07-24 01:56:39 +00:00
em . Headers = append ( em . Headers , eh )
}
2019-07-02 03:10:51 +00:00
if len ( match . HTTP . QueryParam ) > 0 {
2021-02-26 22:23:15 +00:00
em . QueryParameters = make ( [ ] * envoy_route_v3 . QueryParameterMatcher , 0 , len ( match . HTTP . QueryParam ) )
2019-07-02 03:10:51 +00:00
for _ , qm := range match . HTTP . QueryParam {
2021-02-26 22:23:15 +00:00
eq := & envoy_route_v3 . QueryParameterMatcher {
2019-07-24 01:55:26 +00:00
Name : qm . Name ,
}
switch {
case qm . Exact != "" :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_Exact {
2020-07-31 20:52:49 +00:00
Exact : qm . Exact ,
2020-07-09 22:04:51 +00:00
} ,
2020-07-31 20:52:49 +00:00
} ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
case qm . Regex != "" :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_StringMatch {
StringMatch : & envoy_matcher_v3 . StringMatcher {
MatchPattern : & envoy_matcher_v3 . StringMatcher_SafeRegex {
2020-07-31 20:52:49 +00:00
SafeRegex : makeEnvoyRegexMatch ( qm . Regex ) ,
2020-07-09 22:04:51 +00:00
} ,
2020-07-31 20:52:49 +00:00
} ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
case qm . Present :
2021-02-26 22:23:15 +00:00
eq . QueryParameterMatchSpecifier = & envoy_route_v3 . QueryParameterMatcher_PresentMatch {
2020-07-31 20:52:49 +00:00
PresentMatch : true ,
2020-07-09 22:04:51 +00:00
}
2019-07-24 01:55:26 +00:00
default :
continue // skip this impossible situation
2019-07-02 03:10:51 +00:00
}
em . QueryParameters = append ( em . QueryParameters , eq )
}
}
return em
}
2021-02-26 22:23:15 +00:00
func makeDefaultRouteMatch ( ) * envoy_route_v3 . RouteMatch {
return & envoy_route_v3 . RouteMatch {
PathSpecifier : & envoy_route_v3 . RouteMatch_Prefix {
2019-07-02 03:10:51 +00:00
Prefix : "/" ,
} ,
// TODO(banks) Envoy supports matching only valid GRPC
// requests which might be nice to add here for gRPC services
// but it's not supported in our current envoy SDK version
// although docs say it was supported by 1.8.0. Going to defer
// that until we've updated the deps.
}
}
2021-02-26 22:23:15 +00:00
func makeRouteActionForChainCluster ( targetID string , chain * structs . CompiledDiscoveryChain ) * envoy_route_v3 . Route_Route {
2019-08-02 20:34:54 +00:00
target := chain . Targets [ targetID ]
2020-08-28 20:27:40 +00:00
return makeRouteActionFromName ( CustomizeClusterName ( target . Name , chain ) )
}
2019-08-02 20:34:54 +00:00
2021-02-26 22:23:15 +00:00
func makeRouteActionFromName ( clusterName string ) * envoy_route_v3 . Route_Route {
return & envoy_route_v3 . Route_Route {
Route : & envoy_route_v3 . RouteAction {
ClusterSpecifier : & envoy_route_v3 . RouteAction_Cluster {
2019-07-02 03:10:51 +00:00
Cluster : clusterName ,
} ,
} ,
}
}
2021-02-26 22:23:15 +00:00
func makeRouteActionForSplitter ( splits [ ] * structs . DiscoverySplit , chain * structs . CompiledDiscoveryChain ) ( * envoy_route_v3 . Route_Route , error ) {
clusters := make ( [ ] * envoy_route_v3 . WeightedCluster_ClusterWeight , 0 , len ( splits ) )
2019-07-02 03:10:51 +00:00
for _ , split := range splits {
2019-08-02 03:44:05 +00:00
nextNode := chain . Nodes [ split . NextNode ]
if nextNode . Type != structs . DiscoveryGraphNodeTypeResolver {
return nil , fmt . Errorf ( "unexpected splitter destination node type: %s" , nextNode . Type )
2019-07-02 03:10:51 +00:00
}
2019-08-02 20:34:54 +00:00
targetID := nextNode . Resolver . Target
target := chain . Targets [ targetID ]
2019-08-02 03:03:34 +00:00
2019-08-19 18:03:03 +00:00
clusterName := CustomizeClusterName ( target . Name , chain )
2019-07-02 03:10:51 +00:00
2019-07-12 19:16:21 +00:00
// The smallest representable weight is 1/10000 or .01% but envoy
// deals with integers so scale everything up by 100x.
2021-02-26 22:23:15 +00:00
cw := & envoy_route_v3 . WeightedCluster_ClusterWeight {
2019-07-12 19:16:21 +00:00
Weight : makeUint32Value ( int ( split . Weight * 100 ) ) ,
2019-07-02 03:10:51 +00:00
Name : clusterName ,
}
clusters = append ( clusters , cw )
}
2021-02-26 22:23:15 +00:00
return & envoy_route_v3 . Route_Route {
Route : & envoy_route_v3 . RouteAction {
ClusterSpecifier : & envoy_route_v3 . RouteAction_WeightedClusters {
WeightedClusters : & envoy_route_v3 . WeightedCluster {
2019-07-02 03:10:51 +00:00
Clusters : clusters ,
2019-07-12 19:16:21 +00:00
TotalWeight : makeUint32Value ( 10000 ) , // scaled up 100%
2019-07-02 03:10:51 +00:00
} ,
} ,
} ,
} , nil
2018-10-03 18:18:55 +00:00
}
2020-09-02 21:13:50 +00:00
2021-02-26 22:23:15 +00:00
func injectLBToRouteAction ( lb * structs . LoadBalancer , action * envoy_route_v3 . RouteAction ) error {
2020-09-11 15:21:43 +00:00
if lb == nil || ! lb . IsHashBased ( ) {
2020-09-02 21:13:50 +00:00
return nil
}
2021-02-26 22:23:15 +00:00
result := make ( [ ] * envoy_route_v3 . RouteAction_HashPolicy , 0 , len ( lb . HashPolicies ) )
2020-09-11 15:21:43 +00:00
for _ , policy := range lb . HashPolicies {
2020-09-02 21:13:50 +00:00
if policy . SourceIP {
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_ConnectionProperties_ {
ConnectionProperties : & envoy_route_v3 . RouteAction_HashPolicy_ConnectionProperties {
2020-09-02 21:13:50 +00:00
SourceIp : true ,
} ,
} ,
Terminal : policy . Terminal ,
} )
continue
}
switch policy . Field {
case structs . HashPolicyHeader :
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_Header_ {
Header : & envoy_route_v3 . RouteAction_HashPolicy_Header {
2020-09-02 21:13:50 +00:00
HeaderName : policy . FieldValue ,
} ,
} ,
Terminal : policy . Terminal ,
} )
case structs . HashPolicyCookie :
2021-02-26 22:23:15 +00:00
cookie := envoy_route_v3 . RouteAction_HashPolicy_Cookie {
2020-09-02 21:13:50 +00:00
Name : policy . FieldValue ,
}
if policy . CookieConfig != nil {
cookie . Path = policy . CookieConfig . Path
2020-09-12 00:34:03 +00:00
if policy . CookieConfig . TTL != 0 * time . Second {
cookie . Ttl = ptypes . DurationProto ( policy . CookieConfig . TTL )
}
// Envoy will generate a session cookie if the ttl is present and zero.
if policy . CookieConfig . Session {
cookie . Ttl = ptypes . DurationProto ( 0 * time . Second )
}
2020-09-02 21:13:50 +00:00
}
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_Cookie_ {
2020-09-02 21:13:50 +00:00
Cookie : & cookie ,
} ,
Terminal : policy . Terminal ,
} )
case structs . HashPolicyQueryParam :
2021-02-26 22:23:15 +00:00
result = append ( result , & envoy_route_v3 . RouteAction_HashPolicy {
PolicySpecifier : & envoy_route_v3 . RouteAction_HashPolicy_QueryParameter_ {
QueryParameter : & envoy_route_v3 . RouteAction_HashPolicy_QueryParameter {
2020-09-02 21:13:50 +00:00
Name : policy . FieldValue ,
} ,
} ,
Terminal : policy . Terminal ,
} )
default :
return fmt . Errorf ( "unsupported load balancer hash policy field: %v" , policy . Field )
}
}
action . HashPolicy = result
return nil
}