dragonfly/manager/searcher/searcher.go

252 lines
6.7 KiB
Go

/*
* Copyright 2020 The Dragonfly Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//go:generate mockgen -destination mocks/searcher_mock.go -source searcher.go -package mocks
package searcher
import (
"context"
"errors"
"fmt"
"net"
"sort"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/yl2chen/cidranger"
"go.uber.org/zap"
logger "d7y.io/dragonfly/v2/internal/dflog"
"d7y.io/dragonfly/v2/manager/models"
"d7y.io/dragonfly/v2/pkg/math"
"d7y.io/dragonfly/v2/pkg/types"
)
const (
// Condition IDC key.
ConditionIDC = "idc"
// Condition location key.
ConditionLocation = "location"
)
const (
// cidrAffinityWeight is CIDR affinity weight.
cidrAffinityWeight float64 = 0.4
// idcAffinityWeight is IDC affinity weight.
idcAffinityWeight float64 = 0.35
// locationAffinityWeight is location affinity weight.
locationAffinityWeight = 0.24
// clusterTypeWeight is cluster type weight.
clusterTypeWeight float64 = 0.01
)
const (
// Maximum score.
maxScore float64 = 1.0
// Minimum score.
minScore = 0
)
const (
// Maximum number of elements.
maxElementLen = 5
)
// Scheduler cluster scopes.
type Scopes struct {
IDC string `mapstructure:"idc"`
Location string `mapstructure:"location"`
CIDRs []string `mapstructure:"cidrs"`
}
type Searcher interface {
// FindSchedulerClusters finds scheduler clusters that best matches the evaluation.
FindSchedulerClusters(ctx context.Context, schedulerClusters []models.SchedulerCluster, ip, hostname string,
conditions map[string]string, log *zap.SugaredLogger) ([]models.SchedulerCluster, error)
}
type searcher struct{}
func New(pluginDir string) Searcher {
s, err := LoadPlugin(pluginDir)
if err != nil {
logger.Info("use default searcher")
return &searcher{}
}
logger.Info("use searcher plugin")
return s
}
// FindSchedulerClusters finds scheduler clusters that best matches the evaluation.
func (s *searcher) FindSchedulerClusters(ctx context.Context, schedulerClusters []models.SchedulerCluster, ip, hostname string,
conditions map[string]string, log *zap.SugaredLogger) ([]models.SchedulerCluster, error) {
log = log.With("ip", ip, "hostname", hostname, "conditions", conditions)
if len(schedulerClusters) <= 0 {
return nil, errors.New("empty scheduler clusters")
}
clusters := FilterSchedulerClusters(conditions, schedulerClusters)
if len(clusters) == 0 {
return nil, fmt.Errorf("conditions %#v does not match any scheduler cluster", conditions)
}
sort.Slice(
clusters,
func(i, j int) bool {
var si, sj Scopes
if err := mapstructure.Decode(clusters[i].Scopes, &si); err != nil {
log.Errorf("cluster %s decode scopes failed: %v", clusters[i].Name, err)
return false
}
if err := mapstructure.Decode(clusters[j].Scopes, &sj); err != nil {
log.Errorf("cluster %s decode scopes failed: %v", clusters[i].Name, err)
return false
}
return Evaluate(ip, hostname, conditions, si, clusters[i], log) > Evaluate(ip, hostname, conditions, sj, clusters[j], log)
},
)
return clusters, nil
}
// Filter the scheduler clusters that dfdaemon can be used.
func FilterSchedulerClusters(conditions map[string]string, schedulerClusters []models.SchedulerCluster) []models.SchedulerCluster {
var clusters []models.SchedulerCluster
for _, schedulerCluster := range schedulerClusters {
// There are no active schedulers in the scheduler cluster
if len(schedulerCluster.Schedulers) == 0 {
continue
}
clusters = append(clusters, schedulerCluster)
}
return clusters
}
// Evaluate the degree of matching between scheduler cluster and dfdaemon.
func Evaluate(ip, hostname string, conditions map[string]string, scopes Scopes, cluster models.SchedulerCluster, log *zap.SugaredLogger) float64 {
return cidrAffinityWeight*calculateCIDRAffinityScore(ip, scopes.CIDRs, log) +
idcAffinityWeight*calculateIDCAffinityScore(conditions[ConditionIDC], scopes.IDC) +
locationAffinityWeight*calculateMultiElementAffinityScore(conditions[ConditionLocation], scopes.Location) +
clusterTypeWeight*calculateClusterTypeScore(cluster)
}
// calculateCIDRAffinityScore 0.0~1.0 larger and better.
func calculateCIDRAffinityScore(ip string, cidrs []string, log *zap.SugaredLogger) float64 {
// Construct CIDR ranger.
ranger := cidranger.NewPCTrieRanger()
for _, cidr := range cidrs {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
log.Error(err)
continue
}
if err := ranger.Insert(cidranger.NewBasicRangerEntry(*network)); err != nil {
log.Error(err)
continue
}
}
// Determine whether an IP is contained in the constructed networks ranger.
contains, err := ranger.Contains(net.ParseIP(ip))
if err != nil {
log.Error(err)
return minScore
}
if !contains {
return minScore
}
return maxScore
}
// calculateIDCAffinityScore 0.0~1.0 larger and better.
func calculateIDCAffinityScore(dst, src string) float64 {
if dst == "" || src == "" {
return minScore
}
if dst == src {
return maxScore
}
// Dst has only one element, src has multiple elements separated by "|".
// When dst element matches one of the multiple elements of src,
// it gets the max score of idc.
srcElements := strings.Split(src, types.AffinitySeparator)
for _, srcElement := range srcElements {
if dst == srcElement {
return maxScore
}
}
return minScore
}
// calculateMultiElementAffinityScore 0.0~1.0 larger and better.
func calculateMultiElementAffinityScore(dst, src string) float64 {
if dst == "" || src == "" {
return minScore
}
if dst == src {
return maxScore
}
// Calculate the number of multi-element matches divided by "|".
var score, elementLen int
dstElements := strings.Split(dst, types.AffinitySeparator)
srcElements := strings.Split(src, types.AffinitySeparator)
elementLen = math.Min(len(dstElements), len(srcElements))
// Maximum element length is 5.
if elementLen > maxElementLen {
elementLen = maxElementLen
}
for i := 0; i < elementLen; i++ {
if dstElements[i] != srcElements[i] {
break
}
score++
}
return float64(score) / float64(maxElementLen)
}
// calculateClusterTypeScore 0.0~1.0 larger and better.
func calculateClusterTypeScore(cluster models.SchedulerCluster) float64 {
if cluster.IsDefault {
return maxScore
}
return minScore
}