2019-06-27 18:38:21 +00:00
|
|
|
package structs
|
|
|
|
|
|
|
|
import (
|
2019-08-19 20:31:05 +00:00
|
|
|
"encoding/json"
|
2019-06-27 18:38:21 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
2019-12-06 16:14:56 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/lib"
|
2019-06-27 18:38:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// CompiledDiscoveryChain is the result from taking a set of related config
|
|
|
|
// entries for a single service's discovery chain and restructuring them into a
|
|
|
|
// form that is more usable for actual service discovery.
|
|
|
|
type CompiledDiscoveryChain struct {
|
|
|
|
ServiceName string
|
|
|
|
Namespace string // the namespace that the chain was compiled within
|
|
|
|
Datacenter string // the datacenter that the chain was compiled within
|
|
|
|
|
2019-08-02 03:03:34 +00:00
|
|
|
// CustomizationHash is a unique hash of any data that affects the
|
|
|
|
// compilation of the discovery chain other than config entries or the
|
|
|
|
// name/namespace/datacenter evaluation criteria.
|
|
|
|
//
|
|
|
|
// If set, this value should be used to prefix/suffix any generated load
|
|
|
|
// balancer data plane objects to avoid sharing customized and
|
|
|
|
// non-customized versions.
|
2019-08-02 20:34:54 +00:00
|
|
|
CustomizationHash string `json:",omitempty"`
|
2019-08-02 03:03:34 +00:00
|
|
|
|
|
|
|
// Protocol is the overall protocol shared by everything in the chain.
|
2019-08-02 20:34:54 +00:00
|
|
|
Protocol string `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
// StartNode is the first key into the Nodes map that should be followed
|
|
|
|
// when walking the discovery chain.
|
|
|
|
StartNode string `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
// Nodes contains all nodes available for traversal in the chain keyed by a
|
|
|
|
// unique name. You can walk this by starting with StartNode.
|
2019-06-27 18:38:21 +00:00
|
|
|
//
|
2019-08-02 03:44:05 +00:00
|
|
|
// NOTE: The names should be treated as opaque values and are only
|
|
|
|
// guaranteed to be consistent within a single compilation.
|
|
|
|
Nodes map[string]*DiscoveryGraphNode `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
// Targets is a list of all targets used in this chain.
|
|
|
|
Targets map[string]*DiscoveryTarget `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
2019-08-05 18:30:35 +00:00
|
|
|
func (c *CompiledDiscoveryChain) WillFailoverThroughMeshGateway(node *DiscoveryGraphNode) bool {
|
|
|
|
if node.Type != DiscoveryGraphNodeTypeResolver {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
failover := node.Resolver.Failover
|
|
|
|
|
|
|
|
if failover != nil && len(failover.Targets) > 0 {
|
|
|
|
for _, failTargetID := range failover.Targets {
|
|
|
|
failTarget := c.Targets[failTargetID]
|
|
|
|
switch failTarget.MeshGateway.Mode {
|
|
|
|
case MeshGatewayModeLocal, MeshGatewayModeRemote:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-07-12 17:21:25 +00:00
|
|
|
// IsDefault returns true if the compiled chain represents no routing, no
|
|
|
|
// splitting, and only the default resolution. We have to be careful here to
|
|
|
|
// avoid returning "yep this is default" when the only resolver action being
|
|
|
|
// applied is redirection to another resolver that is default, so we double
|
|
|
|
// check the resolver matches the requested resolver.
|
2019-06-27 18:38:21 +00:00
|
|
|
func (c *CompiledDiscoveryChain) IsDefault() bool {
|
2019-08-02 03:44:05 +00:00
|
|
|
if c.StartNode == "" || len(c.Nodes) == 0 {
|
2019-06-27 18:38:21 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
node := c.Nodes[c.StartNode]
|
|
|
|
if node == nil {
|
|
|
|
panic("not possible: missing node named '" + c.StartNode + "' in chain '" + c.ServiceName + "'")
|
2019-07-24 01:20:24 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
if node.Type != DiscoveryGraphNodeTypeResolver {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !node.Resolver.Default {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
target := c.Targets[node.Resolver.Target]
|
|
|
|
|
2020-02-06 15:52:25 +00:00
|
|
|
return target.Service == c.ServiceName && target.Namespace == c.Namespace
|
2019-07-24 01:20:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 18:38:21 +00:00
|
|
|
const (
|
2019-08-02 03:44:05 +00:00
|
|
|
DiscoveryGraphNodeTypeRouter = "router"
|
|
|
|
DiscoveryGraphNodeTypeSplitter = "splitter"
|
|
|
|
DiscoveryGraphNodeTypeResolver = "resolver"
|
2019-06-27 18:38:21 +00:00
|
|
|
)
|
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
// DiscoveryGraphNode is a single node in the compiled discovery chain.
|
2019-06-27 18:38:21 +00:00
|
|
|
type DiscoveryGraphNode struct {
|
|
|
|
Type string
|
2019-08-02 03:44:05 +00:00
|
|
|
Name string // this is NOT necessarily a service
|
2019-06-27 18:38:21 +00:00
|
|
|
|
|
|
|
// fields for Type==router
|
|
|
|
Routes []*DiscoveryRoute `json:",omitempty"`
|
|
|
|
|
|
|
|
// fields for Type==splitter
|
|
|
|
Splits []*DiscoverySplit `json:",omitempty"`
|
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
// fields for Type==resolver
|
|
|
|
Resolver *DiscoveryResolver `json:",omitempty"`
|
2020-08-28 19:11:04 +00:00
|
|
|
|
|
|
|
// shared by Type==resolver || Type==splitter
|
2020-09-02 15:10:50 +00:00
|
|
|
LoadBalancer *LoadBalancer `json:",omitempty"`
|
2019-08-02 03:44:05 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 14:18:45 +00:00
|
|
|
func (s *DiscoveryGraphNode) IsRouter() bool {
|
|
|
|
return s.Type == DiscoveryGraphNodeTypeRouter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DiscoveryGraphNode) IsSplitter() bool {
|
|
|
|
return s.Type == DiscoveryGraphNodeTypeSplitter
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DiscoveryGraphNode) IsResolver() bool {
|
|
|
|
return s.Type == DiscoveryGraphNodeTypeResolver
|
|
|
|
}
|
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
func (s *DiscoveryGraphNode) MapKey() string {
|
|
|
|
return fmt.Sprintf("%s:%s", s.Type, s.Name)
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 03:44:05 +00:00
|
|
|
// compiled form of ServiceResolverConfigEntry
|
|
|
|
type DiscoveryResolver struct {
|
2019-08-02 20:34:54 +00:00
|
|
|
Default bool `json:",omitempty"`
|
|
|
|
ConnectTimeout time.Duration `json:",omitempty"`
|
|
|
|
Target string `json:",omitempty"`
|
|
|
|
Failover *DiscoveryFailover `json:",omitempty"`
|
2019-08-02 03:44:05 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 20:31:05 +00:00
|
|
|
func (r *DiscoveryResolver) MarshalJSON() ([]byte, error) {
|
|
|
|
type Alias DiscoveryResolver
|
|
|
|
exported := &struct {
|
|
|
|
ConnectTimeout string `json:",omitempty"`
|
|
|
|
*Alias
|
|
|
|
}{
|
|
|
|
ConnectTimeout: r.ConnectTimeout.String(),
|
|
|
|
Alias: (*Alias)(r),
|
|
|
|
}
|
|
|
|
if r.ConnectTimeout == 0 {
|
|
|
|
exported.ConnectTimeout = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(exported)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DiscoveryResolver) UnmarshalJSON(data []byte) error {
|
|
|
|
type Alias DiscoveryResolver
|
|
|
|
aux := &struct {
|
|
|
|
ConnectTimeout string
|
|
|
|
*Alias
|
|
|
|
}{
|
|
|
|
Alias: (*Alias)(r),
|
|
|
|
}
|
2019-12-06 16:14:56 +00:00
|
|
|
if err := lib.UnmarshalJSON(data, &aux); err != nil {
|
2019-08-19 20:31:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
if aux.ConnectTimeout != "" {
|
|
|
|
if r.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-27 18:38:21 +00:00
|
|
|
// compiled form of ServiceRoute
|
|
|
|
type DiscoveryRoute struct {
|
2019-08-02 03:44:05 +00:00
|
|
|
Definition *ServiceRoute `json:",omitempty"`
|
|
|
|
NextNode string `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// compiled form of ServiceSplit
|
|
|
|
type DiscoverySplit struct {
|
2019-08-02 03:44:05 +00:00
|
|
|
Weight float32 `json:",omitempty"`
|
|
|
|
NextNode string `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// compiled form of ServiceResolverFailover
|
|
|
|
type DiscoveryFailover struct {
|
2019-08-02 20:34:54 +00:00
|
|
|
Targets []string `json:",omitempty"`
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DiscoveryTarget represents all of the inputs necessary to use a resolver
|
|
|
|
// config entry to execute a catalog query to generate a list of service
|
|
|
|
// instances during discovery.
|
|
|
|
type DiscoveryTarget struct {
|
2019-08-19 18:03:03 +00:00
|
|
|
// ID is a unique identifier for referring to this target in a compiled
|
|
|
|
// chain. It should be treated as a per-compile opaque string.
|
2019-08-02 20:34:54 +00:00
|
|
|
ID string `json:",omitempty"`
|
|
|
|
|
2019-06-27 18:38:21 +00:00
|
|
|
Service string `json:",omitempty"`
|
|
|
|
ServiceSubset string `json:",omitempty"`
|
|
|
|
Namespace string `json:",omitempty"`
|
|
|
|
Datacenter string `json:",omitempty"`
|
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
MeshGateway MeshGatewayConfig `json:",omitempty"`
|
|
|
|
Subset ServiceResolverSubset `json:",omitempty"`
|
2019-08-19 17:19:44 +00:00
|
|
|
|
2019-08-19 18:03:03 +00:00
|
|
|
// External is true if this target is outside of this consul cluster.
|
|
|
|
External bool `json:",omitempty"`
|
|
|
|
|
|
|
|
// SNI is the sni field to use when connecting to this set of endpoints
|
|
|
|
// over TLS.
|
|
|
|
SNI string `json:",omitempty"`
|
|
|
|
|
|
|
|
// Name is the unique name for this target for use when generating load
|
|
|
|
// balancer objects. This has a structure similar to SNI, but will not be
|
|
|
|
// affected by SNI customizations.
|
|
|
|
Name string `json:",omitempty"`
|
2019-08-02 03:44:05 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
func NewDiscoveryTarget(service, serviceSubset, namespace, datacenter string) *DiscoveryTarget {
|
|
|
|
t := &DiscoveryTarget{
|
|
|
|
Service: service,
|
|
|
|
ServiceSubset: serviceSubset,
|
|
|
|
Namespace: namespace,
|
|
|
|
Datacenter: datacenter,
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
2019-08-02 20:34:54 +00:00
|
|
|
t.setID()
|
|
|
|
return t
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
func (t *DiscoveryTarget) setID() {
|
|
|
|
// NOTE: this format is similar to the SNI syntax for simplicity
|
|
|
|
if t.ServiceSubset == "" {
|
|
|
|
t.ID = fmt.Sprintf("%s.%s.%s", t.Service, t.Namespace, t.Datacenter)
|
2019-06-27 18:38:21 +00:00
|
|
|
} else {
|
2019-08-02 20:34:54 +00:00
|
|
|
t.ID = fmt.Sprintf("%s.%s.%s.%s", t.ServiceSubset, t.Service, t.Namespace, t.Datacenter)
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
2019-08-02 20:34:54 +00:00
|
|
|
}
|
2019-06-27 18:38:21 +00:00
|
|
|
|
2019-08-02 20:34:54 +00:00
|
|
|
func (t *DiscoveryTarget) String() string {
|
|
|
|
return t.ID
|
2019-06-27 18:38:21 +00:00
|
|
|
}
|
2020-01-24 15:04:58 +00:00
|
|
|
|
|
|
|
func (t *DiscoveryTarget) ServiceID() ServiceID {
|
|
|
|
return NewServiceID(t.Service, t.GetEnterpriseMetadata())
|
|
|
|
}
|