diff --git a/agent/configentry/discoverychain.go b/agent/configentry/discoverychain.go index 4acf3f9ff885..a3b141c9a8f2 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 0158fc90161c..3fb329d139b7 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.LocalityToStructs(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 903fcbe9ee75..0e77f5f09d6b 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 5253a20278e1..e63116dcae89 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 7b655475c7e8..fd9e0c603a6e 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 67abde33b177..545aa19a2b91 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 72a7a464a698..b1b7748effd4 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 }