From e58a8da13895505082c0bfc1e8b8d866be20c8e1 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 9 Mar 2023 11:37:08 -0500 Subject: [PATCH] add peer locality to discovery chains --- agent/configentry/discoverychain.go | 3 ++ agent/consul/discoverychain/compile.go | 5 ++ agent/consul/state/config_entry.go | 28 +++++++++++ agent/consul/state/config_entry_test.go | 49 ++++++++++++++++++++ agent/structs/config_entry_discoverychain.go | 30 ++++++++++++ agent/structs/discovery_chain.go | 13 +++--- agent/structs/structs.deepcopy.go | 4 ++ proto/private/pbpeering/peering.go | 14 ++++++ 8 files changed, 140 insertions(+), 6 deletions(-) diff --git a/agent/configentry/discoverychain.go b/agent/configentry/discoverychain.go index 4acf3f9ff8858..a3b141c9a8f2c 100644 --- a/agent/configentry/discoverychain.go +++ b/agent/configentry/discoverychain.go @@ -2,6 +2,7 @@ package configentry import ( "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/proto/private/pbpeering" ) // DiscoveryChainSet is a wrapped set of raw cross-referenced config entries @@ -13,6 +14,7 @@ type DiscoveryChainSet struct { Splitters map[structs.ServiceID]*structs.ServiceSplitterConfigEntry Resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry Services map[structs.ServiceID]*structs.ServiceConfigEntry + Peers map[string]*pbpeering.Peering ProxyDefaults map[string]*structs.ProxyConfigEntry } @@ -22,6 +24,7 @@ func NewDiscoveryChainSet() *DiscoveryChainSet { Splitters: make(map[structs.ServiceID]*structs.ServiceSplitterConfigEntry), Resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry), Services: make(map[structs.ServiceID]*structs.ServiceConfigEntry), + Peers: make(map[string]*pbpeering.Peering), ProxyDefaults: make(map[string]*structs.ProxyConfigEntry), } } diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 0158fc90161c9..30b3e027054f5 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/proto/private/pbpeering" ) type CompileRequest struct { @@ -736,6 +737,10 @@ func (c *compiler) newTarget(opts structs.DiscoveryTargetOpts) *structs.Discover // Use the same representation for the name. This will NOT be overridden // later. t.Name = t.SNI + peer := c.entries.Peers[opts.Peer] + if peer != nil && peer.Remote != nil && peer.Remote.Locality != nil { + t.Locality = pbpeering.LocalityToStruct(peer.Remote.Locality) + } } prev, ok := c.loadedTargets[t.ID] diff --git a/agent/consul/state/config_entry.go b/agent/consul/state/config_entry.go index 903fcbe9ee75b..0e77f5f09d6b0 100644 --- a/agent/consul/state/config_entry.go +++ b/agent/consul/state/config_entry.go @@ -3,6 +3,7 @@ package state import ( "errors" "fmt" + "strings" memdb "github.com/hashicorp/go-memdb" @@ -1293,6 +1294,7 @@ func readDiscoveryChainConfigEntriesTxn( todoSplitters = make(map[structs.ServiceID]struct{}) todoResolvers = make(map[structs.ServiceID]struct{}) todoDefaults = make(map[structs.ServiceID]struct{}) + todoPeers = make(map[string]struct{}) ) sid := structs.NewServiceID(serviceName, entMeta) @@ -1394,6 +1396,10 @@ func readDiscoveryChainConfigEntriesTxn( for _, svc := range resolver.ListRelatedServices() { todoResolvers[svc] = struct{}{} } + + for _, peer := range resolver.RelatedPeers() { + todoPeers[peer] = struct{}{} + } } for { @@ -1435,6 +1441,28 @@ func readDiscoveryChainConfigEntriesTxn( res.Services[svcID] = entry } + peerEntMeta := structs.DefaultEnterpriseMetaInPartition(entMeta.PartitionOrDefault()) + for peerName := range todoPeers { + q := Query{ + Value: strings.ToLower(peerName), + EnterpriseMeta: *peerEntMeta, + } + idx, entry, err := peeringReadTxn(tx, ws, q) + if err != nil { + return 0, nil, err + } + if idx > maxIdx { + maxIdx = idx + } + + if entry == nil { + res.Peers[peerName] = nil + continue + } + + res.Peers[peerName] = entry + } + // Strip nils now that they are no longer necessary. for sid, entry := range res.Routers { if entry == nil { diff --git a/agent/consul/state/config_entry_test.go b/agent/consul/state/config_entry_test.go index 5253a20278e11..e63116dcae89e 100644 --- a/agent/consul/state/config_entry_test.go +++ b/agent/consul/state/config_entry_test.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/proto/private/pbpeering" + "github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/sdk/testutil" ) @@ -2065,6 +2067,53 @@ func TestStore_ReadDiscoveryChainConfigEntries_SubsetSplit(t *testing.T) { require.Len(t, entrySet.Services, 1) } +func TestStore_ReadDiscoveryChainConfigEntries_PeerLocality(t *testing.T) { + s := testConfigStateStore(t) + + entries := []structs.ConfigEntry{ + &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: "main", + Protocol: "http", + }, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "main", + Failover: map[string]structs.ServiceResolverFailover{ + "*": { + Targets: []structs.ServiceResolverFailoverTarget{ + {Peer: "cluster-01"}, + {Peer: "cluster-02"}, // Non-existant + }, + }, + }, + }, + } + + for _, entry := range entries { + require.NoError(t, s.EnsureConfigEntry(0, entry)) + } + + cluster01Peering := &pbpeering.Peering{ + ID: testFooPeerID, + Name: "cluster-01", + } + err := s.PeeringWrite(0, &pbpeering.PeeringWriteRequest{Peering: cluster01Peering}) + require.NoError(t, err) + + _, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil) + require.NoError(t, err) + + require.Len(t, entrySet.Routers, 0) + require.Len(t, entrySet.Splitters, 0) + require.Len(t, entrySet.Resolvers, 1) + require.Len(t, entrySet.Services, 1) + prototest.AssertDeepEqual(t, entrySet.Peers, map[string]*pbpeering.Peering{ + "cluster-01": cluster01Peering, + "cluster-02": nil, + }) +} + // TODO(rb): add ServiceIntentions tests func TestStore_ValidateGatewayNamesCannotBeShared(t *testing.T) { diff --git a/agent/structs/config_entry_discoverychain.go b/agent/structs/config_entry_discoverychain.go index 7b655475c7e84..fd9e0c603a6ea 100644 --- a/agent/structs/config_entry_discoverychain.go +++ b/agent/structs/config_entry_discoverychain.go @@ -871,6 +871,36 @@ type ServiceResolverConfigEntry struct { RaftIndex } +func (e *ServiceResolverConfigEntry) RelatedPeers() []string { + peers := make(map[string]struct{}) + + if r := e.Redirect; r != nil && r.Peer != "" { + peers[r.Peer] = struct{}{} + } + + if e.Failover != nil { + for _, f := range e.Failover { + for _, t := range f.Targets { + if t.Peer != "" { + peers[t.Peer] = struct{}{} + } + } + } + } + + if len(peers) == 0 { + return nil + } + + out := make([]string, 0, len(peers)) + for svc := range peers { + out = append(out, svc) + } + sort.Strings(out) + + return out +} + func (e *ServiceResolverConfigEntry) MarshalJSON() ([]byte, error) { type Alias ServiceResolverConfigEntry exported := &struct { diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index 67abde33b1777..545aa19a2b915 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -190,12 +190,13 @@ type DiscoveryTarget struct { // chain. It should be treated as a per-compile opaque string. ID string `json:",omitempty"` - Service string `json:",omitempty"` - ServiceSubset string `json:",omitempty"` - Namespace string `json:",omitempty"` - Partition string `json:",omitempty"` - Datacenter string `json:",omitempty"` - Peer string `json:",omitempty"` + Service string `json:",omitempty"` + ServiceSubset string `json:",omitempty"` + Namespace string `json:",omitempty"` + Partition string `json:",omitempty"` + Datacenter string `json:",omitempty"` + Peer string `json:",omitempty"` + Locality *Locality `json:",omitempty"` MeshGateway MeshGatewayConfig `json:",omitempty"` Subset ServiceResolverSubset `json:",omitempty"` diff --git a/agent/structs/structs.deepcopy.go b/agent/structs/structs.deepcopy.go index 3adcdaa8a7e20..5b87256487e92 100644 --- a/agent/structs/structs.deepcopy.go +++ b/agent/structs/structs.deepcopy.go @@ -125,6 +125,10 @@ func (o *CompiledDiscoveryChain) DeepCopy() *CompiledDiscoveryChain { if v2 != nil { cp_Targets_v2 = new(DiscoveryTarget) *cp_Targets_v2 = *v2 + if v2.Locality != nil { + cp_Targets_v2.Locality = new(Locality) + *cp_Targets_v2.Locality = *v2.Locality + } } cp.Targets[k2] = cp_Targets_v2 } diff --git a/proto/private/pbpeering/peering.go b/proto/private/pbpeering/peering.go index a0f337409ceca..94372ebe491a5 100644 --- a/proto/private/pbpeering/peering.go +++ b/proto/private/pbpeering/peering.go @@ -332,9 +332,23 @@ func (o *PeeringTrustBundle) DeepCopy() *PeeringTrustBundle { return cp } +// TODO: handle this with mog +// LocalityFromStruct converts a struct Locality to a protobuf Locality. func LocalityFromStruct(l structs.Locality) *Locality { return &Locality{ Region: l.Region, Zone: l.Zone, } } + +// TODO: handle this with mog +// LocalityToStruct converts a protobuf Locality to a struct Locality. +func LocalityToStruct(l *Locality) *structs.Locality { + if l == nil { + return nil + } + return &structs.Locality{ + Region: l.Region, + Zone: l.Zone, + } +}