225 lines
6.3 KiB
Go
225 lines
6.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package consul
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/lib"
|
|
)
|
|
|
|
// nodeSorter takes a list of nodes and a parallel vector of distances and
|
|
// implements sort.Interface, keeping both structures coherent and sorting by
|
|
// distance.
|
|
type nodeSorter struct {
|
|
Nodes structs.Nodes
|
|
Vec []float64
|
|
}
|
|
|
|
// newNodeSorter returns a new sorter for the given source coordinate and set of
|
|
// nodes.
|
|
func (s *Server) newNodeSorter(cs lib.CoordinateSet, nodes structs.Nodes) (sort.Interface, error) {
|
|
state := s.fsm.State()
|
|
vec := make([]float64, len(nodes))
|
|
for i, node := range nodes {
|
|
_, other, err := state.Coordinate(nil, node.Node, node.GetEnterpriseMeta())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c1, c2 := cs.Intersect(other)
|
|
vec[i] = lib.ComputeDistance(c1, c2)
|
|
}
|
|
return &nodeSorter{nodes, vec}, nil
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *nodeSorter) Len() int {
|
|
return len(n.Nodes)
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *nodeSorter) Swap(i, j int) {
|
|
n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
|
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *nodeSorter) Less(i, j int) bool {
|
|
return n.Vec[i] < n.Vec[j]
|
|
}
|
|
|
|
// serviceNodeSorter takes a list of service nodes and a parallel vector of
|
|
// distances and implements sort.Interface, keeping both structures coherent and
|
|
// sorting by distance.
|
|
type serviceNodeSorter struct {
|
|
Nodes structs.ServiceNodes
|
|
Vec []float64
|
|
}
|
|
|
|
// newServiceNodeSorter returns a new sorter for the given source coordinate and
|
|
// set of service nodes.
|
|
func (s *Server) newServiceNodeSorter(cs lib.CoordinateSet, nodes structs.ServiceNodes) (sort.Interface, error) {
|
|
state := s.fsm.State()
|
|
vec := make([]float64, len(nodes))
|
|
for i, node := range nodes {
|
|
_, other, err := state.Coordinate(nil, node.Node, &node.EnterpriseMeta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c1, c2 := cs.Intersect(other)
|
|
vec[i] = lib.ComputeDistance(c1, c2)
|
|
}
|
|
return &serviceNodeSorter{nodes, vec}, nil
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *serviceNodeSorter) Len() int {
|
|
return len(n.Nodes)
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *serviceNodeSorter) Swap(i, j int) {
|
|
n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
|
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *serviceNodeSorter) Less(i, j int) bool {
|
|
return n.Vec[i] < n.Vec[j]
|
|
}
|
|
|
|
// serviceNodeSorter takes a list of health checks and a parallel vector of
|
|
// distances and implements sort.Interface, keeping both structures coherent and
|
|
// sorting by distance.
|
|
type healthCheckSorter struct {
|
|
Checks structs.HealthChecks
|
|
Vec []float64
|
|
}
|
|
|
|
// newHealthCheckSorter returns a new sorter for the given source coordinate and
|
|
// set of health checks with nodes.
|
|
func (s *Server) newHealthCheckSorter(cs lib.CoordinateSet, checks structs.HealthChecks) (sort.Interface, error) {
|
|
state := s.fsm.State()
|
|
vec := make([]float64, len(checks))
|
|
for i, check := range checks {
|
|
_, other, err := state.Coordinate(nil, check.Node, &check.EnterpriseMeta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c1, c2 := cs.Intersect(other)
|
|
vec[i] = lib.ComputeDistance(c1, c2)
|
|
}
|
|
return &healthCheckSorter{checks, vec}, nil
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *healthCheckSorter) Len() int {
|
|
return len(n.Checks)
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *healthCheckSorter) Swap(i, j int) {
|
|
n.Checks[i], n.Checks[j] = n.Checks[j], n.Checks[i]
|
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *healthCheckSorter) Less(i, j int) bool {
|
|
return n.Vec[i] < n.Vec[j]
|
|
}
|
|
|
|
// checkServiceNodeSorter takes a list of service nodes and a parallel vector of
|
|
// distances and implements sort.Interface, keeping both structures coherent and
|
|
// sorting by distance.
|
|
type checkServiceNodeSorter struct {
|
|
Nodes structs.CheckServiceNodes
|
|
Vec []float64
|
|
}
|
|
|
|
// newCheckServiceNodeSorter returns a new sorter for the given source coordinate
|
|
// and set of nodes with health checks.
|
|
func (s *Server) newCheckServiceNodeSorter(cs lib.CoordinateSet, nodes structs.CheckServiceNodes) (sort.Interface, error) {
|
|
state := s.fsm.State()
|
|
vec := make([]float64, len(nodes))
|
|
for i, node := range nodes {
|
|
_, other, err := state.Coordinate(nil, node.Node.Node, node.Node.GetEnterpriseMeta())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c1, c2 := cs.Intersect(other)
|
|
vec[i] = lib.ComputeDistance(c1, c2)
|
|
}
|
|
return &checkServiceNodeSorter{nodes, vec}, nil
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *checkServiceNodeSorter) Len() int {
|
|
return len(n.Nodes)
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *checkServiceNodeSorter) Swap(i, j int) {
|
|
n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
|
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (n *checkServiceNodeSorter) Less(i, j int) bool {
|
|
return n.Vec[i] < n.Vec[j]
|
|
}
|
|
|
|
// newSorterByDistanceFrom returns a sorter for the given type.
|
|
func (s *Server) newSorterByDistanceFrom(cs lib.CoordinateSet, subj interface{}) (sort.Interface, error) {
|
|
switch v := subj.(type) {
|
|
case structs.Nodes:
|
|
return s.newNodeSorter(cs, v)
|
|
case structs.ServiceNodes:
|
|
return s.newServiceNodeSorter(cs, v)
|
|
case structs.HealthChecks:
|
|
return s.newHealthCheckSorter(cs, v)
|
|
case structs.CheckServiceNodes:
|
|
return s.newCheckServiceNodeSorter(cs, v)
|
|
default:
|
|
panic(fmt.Errorf("Unhandled type passed to newSorterByDistanceFrom: %#v", subj))
|
|
}
|
|
}
|
|
|
|
// sortNodesByDistanceFrom is used to sort results from our service catalog based
|
|
// on the round trip time from the given source node. Nodes with missing coordinates
|
|
// will get stable sorted at the end of the list.
|
|
//
|
|
// If coordinates are disabled this will be a no-op.
|
|
func (s *Server) sortNodesByDistanceFrom(source structs.QuerySource, subj interface{}) error {
|
|
// We can't sort if there's no source node.
|
|
if source.Node == "" {
|
|
return nil
|
|
}
|
|
|
|
// We can't compare coordinates across DCs.
|
|
if source.Datacenter != s.config.Datacenter {
|
|
return nil
|
|
}
|
|
|
|
// There won't always be coordinates for the source node. If there are
|
|
// none then we can bail out because there's no meaning for the sort.
|
|
state := s.fsm.State()
|
|
_, cs, err := state.Coordinate(nil, source.Node, source.NodeEnterpriseMeta())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(cs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Do the sort!
|
|
sorter, err := s.newSorterByDistanceFrom(cs, subj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sort.Stable(sorter)
|
|
return nil
|
|
}
|