mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
34a32d4ce5
The peer name will eventually show up elsewhere in the resource. For now though this rips it out of where we don’t want it to be.
210 lines
6.8 KiB
Go
210 lines
6.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package selectiontracker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/hashicorp/consul/internal/controller"
|
|
"github.com/hashicorp/consul/internal/radix"
|
|
"github.com/hashicorp/consul/internal/resource"
|
|
"github.com/hashicorp/consul/lib/stringslice"
|
|
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
|
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
|
)
|
|
|
|
type WorkloadSelectionTracker struct {
|
|
lock sync.Mutex
|
|
prefixes *radix.Tree[[]*pbresource.ID]
|
|
exact *radix.Tree[[]*pbresource.ID]
|
|
|
|
// workloadSelectors contains a map keyed on resource references with values
|
|
// being the selector that resource is currently associated with. This map
|
|
// is kept mainly to make tracking removal operations more efficient.
|
|
// Generally any operation that could take advantage of knowing where
|
|
// in the trees the resource id is referenced can use this to prevent
|
|
// needing to search the whole tree.
|
|
workloadSelectors map[resource.ReferenceKey]*pbcatalog.WorkloadSelector
|
|
}
|
|
|
|
func New() *WorkloadSelectionTracker {
|
|
return &WorkloadSelectionTracker{
|
|
prefixes: radix.New[[]*pbresource.ID](),
|
|
exact: radix.New[[]*pbresource.ID](),
|
|
workloadSelectors: make(map[resource.ReferenceKey]*pbcatalog.WorkloadSelector),
|
|
}
|
|
}
|
|
|
|
// MapWorkload will return a slice of controller.Requests with 1 resource for
|
|
// each resource that selects the specified Workload resource.
|
|
func (t *WorkloadSelectionTracker) MapWorkload(_ context.Context, _ controller.Runtime, res *pbresource.Resource) ([]controller.Request, error) {
|
|
resIds := t.GetIDsForWorkload(res.Id)
|
|
|
|
return controller.MakeRequests(nil, resIds), nil
|
|
}
|
|
|
|
func (t *WorkloadSelectionTracker) GetIDsForWorkload(id *pbresource.ID) []*pbresource.ID {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
var result []*pbresource.ID
|
|
|
|
workloadTreeKey := treePathFromNameOrPrefix(id.GetTenancy(), id.GetName())
|
|
|
|
// gather the list of all resources that select the specified workload using a prefix match
|
|
t.prefixes.WalkPath(workloadTreeKey, func(path string, ids []*pbresource.ID) bool {
|
|
result = append(result, ids...)
|
|
return false
|
|
})
|
|
|
|
// gather the list of all resources that select the specified workload using an exact match
|
|
exactReqs, _ := t.exact.Get(workloadTreeKey)
|
|
|
|
// return the combined list of all resources that select the specified workload
|
|
return append(result, exactReqs...)
|
|
}
|
|
|
|
// TrackIDForSelector will associate workloads matching the specified workload
|
|
// selector with the given resource id.
|
|
func (t *WorkloadSelectionTracker) TrackIDForSelector(id *pbresource.ID, selector *pbcatalog.WorkloadSelector) {
|
|
if selector == nil {
|
|
return
|
|
}
|
|
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
ref := resource.NewReferenceKey(id)
|
|
if previousSelector, found := t.workloadSelectors[ref]; found {
|
|
if stringslice.Equal(previousSelector.Names, selector.Names) &&
|
|
stringslice.Equal(previousSelector.Prefixes, selector.Prefixes) {
|
|
// the selector is unchanged so do nothing
|
|
return
|
|
}
|
|
|
|
// Potentially we could detect differences and do more minimal work. However
|
|
// users are not expected to alter workload selectors often and therefore
|
|
// not optimizing this further is probably fine. Therefore we are going
|
|
// to wipe all tracking of the id and reinsert things.
|
|
t.untrackID(id)
|
|
}
|
|
|
|
// loop over all the exact matching rules and associate those workload names
|
|
// with the given resource id
|
|
for _, name := range selector.GetNames() {
|
|
key := treePathFromNameOrPrefix(id.GetTenancy(), name)
|
|
|
|
// lookup any resource id associations for the given workload name
|
|
leaf, _ := t.exact.Get(key)
|
|
|
|
// append the ID to the existing request list
|
|
t.exact.Insert(key, append(leaf, id))
|
|
}
|
|
|
|
// loop over all the prefix matching rules and associate those prefixes
|
|
// with the given resource id.
|
|
for _, prefix := range selector.GetPrefixes() {
|
|
key := treePathFromNameOrPrefix(id.GetTenancy(), prefix)
|
|
|
|
// lookup any resource id associations for the given workload name prefix
|
|
leaf, _ := t.prefixes.Get(key)
|
|
|
|
// append the new resource ID to the existing request list
|
|
t.prefixes.Insert(key, append(leaf, id))
|
|
}
|
|
|
|
t.workloadSelectors[ref] = selector
|
|
}
|
|
|
|
// UntrackID causes the tracker to stop tracking the given resource ID
|
|
func (t *WorkloadSelectionTracker) UntrackID(id *pbresource.ID) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
t.untrackID(id)
|
|
}
|
|
|
|
// GetSelector returns the currently stored selector for the given ID.
|
|
func (t *WorkloadSelectionTracker) GetSelector(id *pbresource.ID) *pbcatalog.WorkloadSelector {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
return t.workloadSelectors[resource.NewReferenceKey(id)]
|
|
}
|
|
|
|
// untrackID should be called to stop tracking a resource ID.
|
|
// This method assumes the lock is already held. Besides modifying
|
|
// the prefix & name trees to not reference this ID, it will also
|
|
// delete any corresponding entry within the workloadSelectors map
|
|
func (t *WorkloadSelectionTracker) untrackID(id *pbresource.ID) {
|
|
ref := resource.NewReferenceKey(id)
|
|
selector, found := t.workloadSelectors[ref]
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
exactTreePaths := make([]string, len(selector.GetNames()))
|
|
for i, name := range selector.GetNames() {
|
|
exactTreePaths[i] = treePathFromNameOrPrefix(id.GetTenancy(), name)
|
|
}
|
|
|
|
prefixTreePaths := make([]string, len(selector.GetPrefixes()))
|
|
for i, prefix := range selector.GetPrefixes() {
|
|
prefixTreePaths[i] = treePathFromNameOrPrefix(id.GetTenancy(), prefix)
|
|
}
|
|
|
|
removeIDFromTreeAtPaths(t.exact, id, exactTreePaths)
|
|
removeIDFromTreeAtPaths(t.prefixes, id, prefixTreePaths)
|
|
|
|
// If we don't do this deletion then reinsertion of the id for
|
|
// tracking in the future could prevent selection criteria from
|
|
// being properly inserted into the radix trees.
|
|
delete(t.workloadSelectors, ref)
|
|
}
|
|
|
|
// removeIDFromTree will remove the given resource ID from all leaf nodes in the radix tree.
|
|
func removeIDFromTreeAtPaths(t *radix.Tree[[]*pbresource.ID], id *pbresource.ID, paths []string) {
|
|
for _, path := range paths {
|
|
ids, _ := t.Get(path)
|
|
|
|
foundIdx := -1
|
|
for idx, resID := range ids {
|
|
if resource.EqualID(resID, id) {
|
|
foundIdx = idx
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundIdx != -1 {
|
|
l := len(ids)
|
|
|
|
if foundIdx == l-1 {
|
|
ids = ids[:foundIdx]
|
|
} else {
|
|
ids = slices.Delete(ids, foundIdx, foundIdx+1)
|
|
}
|
|
|
|
if len(ids) > 0 {
|
|
t.Insert(path, ids)
|
|
} else {
|
|
t.Delete(path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// treePathFromNameOrPrefix computes radix tree key from the resource tenancy and a selector name or prefix.
|
|
// The keys will be computed in the following form:
|
|
// <partition>/<namespace>/<name or prefix>.
|
|
func treePathFromNameOrPrefix(tenancy *pbresource.Tenancy, nameOrPrefix string) string {
|
|
// TODO(peering/v2) update paths for peer tenancy
|
|
return fmt.Sprintf("%s/%s/%s",
|
|
tenancy.GetPartition(),
|
|
tenancy.GetNamespace(),
|
|
nameOrPrefix)
|
|
}
|