consul/agent/xds/xdscommon/xdscommon.go

195 lines
6.7 KiB
Go

package xdscommon
import (
"github.com/golang/protobuf/proto"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
)
const (
// Resource types in xDS v3. These are copied from
// envoyproxy/go-control-plane/pkg/resource/v3/resource.go since we don't need any of
// the rest of that package.
apiTypePrefix = "type.googleapis.com/"
// EndpointType is the TypeURL for Endpoint discovery responses.
EndpointType = apiTypePrefix + "envoy.config.endpoint.v3.ClusterLoadAssignment"
// ClusterType is the TypeURL for Cluster discovery responses.
ClusterType = apiTypePrefix + "envoy.config.cluster.v3.Cluster"
// RouteType is the TypeURL for Route discovery responses.
RouteType = apiTypePrefix + "envoy.config.route.v3.RouteConfiguration"
// ListenerType is the TypeURL for Listener discovery responses.
ListenerType = apiTypePrefix + "envoy.config.listener.v3.Listener"
)
type IndexedResources struct {
// Index is a map of typeURL => resourceName => resource
Index map[string]map[string]proto.Message
// ChildIndex is a map of typeURL => parentResourceName => list of
// childResourceNames. This only applies if the child and parent do not
// share a name.
ChildIndex map[string]map[string][]string
}
func EmptyIndexedResources() *IndexedResources {
return &IndexedResources{
Index: map[string]map[string]proto.Message{
ListenerType: make(map[string]proto.Message),
RouteType: make(map[string]proto.Message),
ClusterType: make(map[string]proto.Message),
EndpointType: make(map[string]proto.Message),
},
ChildIndex: map[string]map[string][]string{
ListenerType: make(map[string][]string),
ClusterType: make(map[string][]string),
},
}
}
type ServiceConfig struct {
// Kind identifies the final proxy kind that will make the request to the
// destination service.
Kind api.ServiceKind
EnvoyExtensions []api.EnvoyExtension
}
// PluginConfiguration is passed into Envoy plugins. It should depend on the
// API client rather than the structs package because the API client is meant
// to be public.
type PluginConfiguration struct {
// ServiceConfigs is a mapping from service names to the data Envoy plugins
// need to override the default Envoy configurations.
ServiceConfigs map[api.CompoundServiceName]ServiceConfig
// SNIToServiceName is a mapping from SNIs to service names. This allows
// Envoy plugins to easily convert from an SNI Envoy resource name to the
// associated service's CompoundServiceName
SNIToServiceName map[string]api.CompoundServiceName
// EnvoyIDToServiceName is a mapping from EnvoyIDs to service names. This allows
// Envoy plugins to easily convert from an EnvoyID Envoy resource name to the
// associated service's CompoundServiceName
EnvoyIDToServiceName map[string]api.CompoundServiceName
// Kind is mode the local Envoy proxy is running in. For now, only
// terminating gateways are supported.
Kind api.ServiceKind
}
// MakePluginConfiguration generates the configuration that will be sent to
// Envoy plugins.
func MakePluginConfiguration(cfgSnap *proxycfg.ConfigSnapshot) PluginConfiguration {
serviceConfigs := make(map[api.CompoundServiceName]ServiceConfig)
sniMappings := make(map[string]api.CompoundServiceName)
envoyIDMappings := make(map[string]api.CompoundServiceName)
trustDomain := ""
if cfgSnap.Roots != nil {
trustDomain = cfgSnap.Roots.TrustDomain
}
switch cfgSnap.Kind {
case structs.ServiceKindConnectProxy:
connectProxies := make(map[proxycfg.UpstreamID]struct{})
for uid, upstreamData := range cfgSnap.ConnectProxy.WatchedUpstreamEndpoints {
for _, serviceNodes := range upstreamData {
// Lambdas and likely other integrations won't be attached to nodes.
// After agentless, we may need to reconsider this.
if len(serviceNodes) == 0 {
connectProxies[uid] = struct{}{}
}
for _, serviceNode := range serviceNodes {
if serviceNode.Service.Kind == structs.ServiceKindTypical || serviceNode.Service.Kind == structs.ServiceKindConnectProxy {
connectProxies[uid] = struct{}{}
}
}
}
}
// TODO(peering): consider PeerUpstreamEndpoints in addition to DiscoveryChain
for uid, dc := range cfgSnap.ConnectProxy.DiscoveryChain {
if _, ok := connectProxies[uid]; !ok {
continue
}
serviceConfigs[upstreamIDToCompoundServiceName(uid)] = ServiceConfig{
Kind: api.ServiceKindConnectProxy,
EnvoyExtensions: convertEnvoyExtensions(dc.EnvoyExtensions),
}
compoundServiceName := upstreamIDToCompoundServiceName(uid)
meta := uid.EnterpriseMeta
sni := connect.ServiceSNI(uid.Name, "", meta.NamespaceOrDefault(), meta.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
sniMappings[sni] = compoundServiceName
envoyIDMappings[uid.EnvoyID()] = compoundServiceName
}
case structs.ServiceKindTerminatingGateway:
for svc, c := range cfgSnap.TerminatingGateway.ServiceConfigs {
compoundServiceName := serviceNameToCompoundServiceName(svc)
serviceConfigs[compoundServiceName] = ServiceConfig{
EnvoyExtensions: convertEnvoyExtensions(c.EnvoyExtensions),
Kind: api.ServiceKindTerminatingGateway,
}
sni := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
sniMappings[sni] = compoundServiceName
envoyID := proxycfg.NewUpstreamIDFromServiceName(svc)
envoyIDMappings[envoyID.EnvoyID()] = compoundServiceName
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
if hasResolver {
for subsetName := range resolver.Subsets {
sni := connect.ServiceSNI(svc.Name, subsetName, svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, trustDomain)
sniMappings[sni] = compoundServiceName
}
}
}
}
return PluginConfiguration{
ServiceConfigs: serviceConfigs,
SNIToServiceName: sniMappings,
EnvoyIDToServiceName: envoyIDMappings,
Kind: api.ServiceKind(cfgSnap.Kind),
}
}
func serviceNameToCompoundServiceName(svc structs.ServiceName) api.CompoundServiceName {
return api.CompoundServiceName{
Name: svc.Name,
Partition: svc.PartitionOrDefault(),
Namespace: svc.NamespaceOrDefault(),
}
}
func upstreamIDToCompoundServiceName(uid proxycfg.UpstreamID) api.CompoundServiceName {
return api.CompoundServiceName{
Name: uid.Name,
Partition: uid.PartitionOrDefault(),
Namespace: uid.NamespaceOrDefault(),
}
}
func convertEnvoyExtensions(structExtensions []structs.EnvoyExtension) []api.EnvoyExtension {
var extensions []api.EnvoyExtension
for _, e := range structExtensions {
extensions = append(extensions, api.EnvoyExtension{
Name: e.Name,
Required: e.Required,
Arguments: e.Arguments,
})
}
return extensions
}