d5a2eb677f
Transparent proxies can set up filter chains that allow direct connections to upstream service instances. Services that can be dialed directly are stored in the PassthroughUpstreams map of the proxycfg snapshot. Previously these addresses were not being cleaned up based on new service health data. The list of addresses associated with an upstream service would only ever grow. As services scale up and down, eventually they will have instances assigned to an IP that was previously assigned to a different service. When IP addresses are duplicated across filter chain match rules the listener config will be rejected by Envoy. This commit updates the proxycfg snapshot management so that passthrough addresses can get cleaned up when no longer associated with a given upstream. There is still the possibility of a race condition here where due to timing an address is shared between multiple passthrough upstreams. That concern is mitigated by #12195, but will be further addressed in a follow-up.
150 lines
3.6 KiB
Go
150 lines
3.6 KiB
Go
package proxycfg
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
type UpstreamID struct {
|
|
Type string
|
|
Name string
|
|
Datacenter string
|
|
structs.EnterpriseMeta
|
|
}
|
|
|
|
func NewUpstreamID(u *structs.Upstream) UpstreamID {
|
|
id := UpstreamID{
|
|
Type: u.DestinationType,
|
|
Name: u.DestinationName,
|
|
Datacenter: u.Datacenter,
|
|
EnterpriseMeta: structs.NewEnterpriseMetaWithPartition(
|
|
u.DestinationPartition,
|
|
u.DestinationNamespace,
|
|
),
|
|
}
|
|
id.normalize()
|
|
return id
|
|
}
|
|
|
|
func NewUpstreamIDFromServiceName(sn structs.ServiceName) UpstreamID {
|
|
id := UpstreamID{
|
|
Name: sn.Name,
|
|
EnterpriseMeta: sn.EnterpriseMeta,
|
|
}
|
|
id.normalize()
|
|
return id
|
|
}
|
|
|
|
func NewUpstreamIDFromServiceID(sid structs.ServiceID) UpstreamID {
|
|
id := UpstreamID{
|
|
Name: sid.ID,
|
|
EnterpriseMeta: sid.EnterpriseMeta,
|
|
}
|
|
id.normalize()
|
|
return id
|
|
}
|
|
|
|
func NewUpstreamIDFromTargetID(tid string) UpstreamID {
|
|
// Drop the leading subset if one is present in the target ID.
|
|
separators := strings.Count(tid, ".")
|
|
if separators > 3 {
|
|
prefix := tid[:strings.Index(tid, ".")+1]
|
|
tid = strings.TrimPrefix(tid, prefix)
|
|
}
|
|
|
|
split := strings.SplitN(tid, ".", 4)
|
|
|
|
id := UpstreamID{
|
|
Name: split[0],
|
|
EnterpriseMeta: structs.NewEnterpriseMetaWithPartition(split[2], split[1]),
|
|
Datacenter: split[3],
|
|
}
|
|
id.normalize()
|
|
return id
|
|
}
|
|
|
|
func (u *UpstreamID) normalize() {
|
|
if u.Type == structs.UpstreamDestTypeService {
|
|
u.Type = ""
|
|
}
|
|
|
|
u.EnterpriseMeta.Normalize()
|
|
}
|
|
|
|
// String encodes the UpstreamID into a string for use in agent cache keys.
|
|
// You can decode it back again using UpstreamIDFromString.
|
|
func (u UpstreamID) String() string {
|
|
return UpstreamIDString(u.Type, u.Datacenter, u.Name, &u.EnterpriseMeta)
|
|
}
|
|
|
|
func (u UpstreamID) GoString() string {
|
|
return u.String()
|
|
}
|
|
|
|
func UpstreamIDFromString(input string) UpstreamID {
|
|
typ, dc, name, entMeta := ParseUpstreamIDString(input)
|
|
id := UpstreamID{
|
|
Type: typ,
|
|
Datacenter: dc,
|
|
Name: name,
|
|
EnterpriseMeta: *entMeta,
|
|
}
|
|
id.normalize()
|
|
return id
|
|
}
|
|
|
|
const upstreamTypePreparedQueryPrefix = structs.UpstreamDestTypePreparedQuery + ":"
|
|
|
|
func ParseUpstreamIDString(input string) (typ, dc, name string, meta *structs.EnterpriseMeta) {
|
|
if strings.HasPrefix(input, upstreamTypePreparedQueryPrefix) {
|
|
typ = structs.UpstreamDestTypePreparedQuery
|
|
input = strings.TrimPrefix(input, upstreamTypePreparedQueryPrefix)
|
|
}
|
|
|
|
idx := strings.LastIndex(input, "?dc=")
|
|
if idx != -1 {
|
|
dc = input[idx+4:]
|
|
input = input[0:idx]
|
|
}
|
|
|
|
name, meta = parseInnerUpstreamIDString(input)
|
|
|
|
return typ, dc, name, meta
|
|
}
|
|
|
|
// EnvoyID returns a string representation that uniquely identifies the
|
|
// upstream in a canonical but human readable way.
|
|
//
|
|
// This should be used for any situation where we generate identifiers in Envoy
|
|
// xDS structures for this upstream.
|
|
//
|
|
// This will ensure that generated identifiers for the same thing in OSS and
|
|
// Enterprise render the same and omit default namespaces and partitions.
|
|
func (u UpstreamID) EnvoyID() string {
|
|
name := u.enterpriseIdentifierPrefix() + u.Name
|
|
typ := u.Type
|
|
|
|
if u.Datacenter != "" {
|
|
name += "?dc=" + u.Datacenter
|
|
}
|
|
|
|
// Service is default type so never prefix it. This is more readable and long
|
|
// term it is the only type that matters so we can drop the prefix and have
|
|
// nicer naming in metrics etc.
|
|
if typ == "" || typ == structs.UpstreamDestTypeService {
|
|
return name
|
|
}
|
|
return typ + ":" + name
|
|
}
|
|
|
|
func UpstreamsToMap(us structs.Upstreams) map[UpstreamID]*structs.Upstream {
|
|
upstreamMap := make(map[UpstreamID]*structs.Upstream)
|
|
|
|
for i := range us {
|
|
u := us[i]
|
|
upstreamMap[NewUpstreamID(&u)] = &u
|
|
}
|
|
return upstreamMap
|
|
}
|