Files
open-cluster-management/pkg/plugins/resource/resource.go
haoqing0110 07e3bfbee6 add resource based scheduling prioritizer
Signed-off-by: haoqing0110 <qhao@redhat.com>
2021-09-10 08:11:14 +00:00

182 lines
6.2 KiB
Go

package resource
import (
"context"
"fmt"
"regexp"
"sort"
clusterapiv1 "open-cluster-management.io/api/cluster/v1"
clusterapiv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
"open-cluster-management.io/placement/pkg/plugins"
)
const (
placementLabel = clusterapiv1alpha1.PlacementLabel
description = `
ResourceRatio[ResourceType] and ResourceAllocatable[ResourceType] prioritizer makes the scheduling
decisions based on the resource allocatable to capacity ratio or allocatable of managed clusters.
The [ResourceType] could be CPU or Memory.
The clusters that has the most allocatable to capacity ratio or allocatable are given the highest score,
while the least is given the lowest score.
`
)
var _ plugins.Prioritizer = &ResourcePrioritizer{}
var resourceMap = map[string]clusterapiv1.ResourceName{
"CPU": clusterapiv1.ResourceCPU,
"Memory": clusterapiv1.ResourceMemory,
}
type ResourcePrioritizer struct {
handle plugins.Handle
prioritizerName string
algorithm string
resource clusterapiv1.ResourceName
}
type ResourcePrioritizerBuilder struct {
resourcePrioritizer *ResourcePrioritizer
}
func NewResourcePrioritizerBuilder(handle plugins.Handle) *ResourcePrioritizerBuilder {
return &ResourcePrioritizerBuilder{
resourcePrioritizer: &ResourcePrioritizer{
handle: handle,
},
}
}
func (r *ResourcePrioritizerBuilder) WithPrioritizerName(name string) *ResourcePrioritizerBuilder {
r.resourcePrioritizer.prioritizerName = name
return r
}
func (r *ResourcePrioritizerBuilder) Build() *ResourcePrioritizer {
algorithm, resource := parsePrioritizerName(r.resourcePrioritizer.prioritizerName)
r.resourcePrioritizer.algorithm = algorithm
r.resourcePrioritizer.resource = resource
return r.resourcePrioritizer
}
// parese prioritizerName to algorithm and resource.
// For example, prioritizerName ResourceAllocatableCPU will return Allocatable, CPU.
func parsePrioritizerName(prioritizerName string) (algorithm string, resource clusterapiv1.ResourceName) {
s := regexp.MustCompile("[A-Z]+[a-z]*").FindAllString(prioritizerName, -1)
if len(s) == 3 {
return s[1], resourceMap[s[2]]
}
return "", ""
}
func (r *ResourcePrioritizer) Name() string {
return r.prioritizerName
}
func (r *ResourcePrioritizer) Description() string {
return description
}
func (r *ResourcePrioritizer) Score(ctx context.Context, placement *clusterapiv1alpha1.Placement, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) {
switch r.algorithm {
case "Ratio":
return mostResourceRatioScores(r.resource, clusters)
case "Allocatable":
return mostResourceAllocatableScores(r.resource, clusters)
}
return nil, nil
}
// Calculate clusters scores based on the resource allocatable to capacity ratio.
// The clusters that has the most allocatable to capacity ratio are given the highest score, while the least is given the lowest score.
// The score range is from -100 to 100.
func mostResourceRatioScores(resourceName clusterapiv1.ResourceName, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) {
scores := map[string]int64{}
for _, cluster := range clusters {
// get cluster resourceName's allocatable and capacity
allocatable, capacity, err := getClusterResource(cluster, resourceName)
if err != nil {
continue
}
// score = (resource_x_allocatable / resource_x_capacity - 0.5) * 2 * 100
if capacity != 0 {
ratio := float64(allocatable) / float64(capacity)
scores[cluster.Name] = int64((ratio - 0.5) * 2.0 * 100.0)
}
}
return scores, nil
}
// Calculate clusters scores based on the resource allocatable.
// The clusters that has the most allocatable are given the highest score, while the least is given the lowest score.
// The score range is from -100 to 100.
func mostResourceAllocatableScores(resourceName clusterapiv1.ResourceName, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) {
scores := map[string]int64{}
// get resourceName's min and max allocatable among all the clusters
minAllocatable, maxAllocatable, err := getClustersMinMaxAllocatableResource(clusters, resourceName)
if err != nil {
return scores, nil
}
for _, cluster := range clusters {
// get one cluster resourceName's allocatable
allocatable, _, err := getClusterResource(cluster, resourceName)
if err != nil {
continue
}
// score = ((resource_x_allocatable - min(resource_x_allocatable)) / (max(resource_x_allocatable) - min(resource_x_allocatable)) - 0.5) * 2 * 100
if (maxAllocatable - minAllocatable) != 0 {
ratio := float64(allocatable-minAllocatable) / float64(maxAllocatable-minAllocatable)
scores[cluster.Name] = int64((ratio - 0.5) * 2.0 * 100.0)
} else {
scores[cluster.Name] = 100.0
}
}
return scores, nil
}
// Go through one cluster resources and return the allocatable and capacity of the resourceName.
func getClusterResource(cluster *clusterapiv1.ManagedCluster, resourceName clusterapiv1.ResourceName) (allocatable, capacity float64, err error) {
if v, exist := cluster.Status.Allocatable[resourceName]; exist {
allocatable = v.AsApproximateFloat64()
} else {
return allocatable, capacity, fmt.Errorf("no allocatable %s found in cluster %s", resourceName, cluster.ObjectMeta.Name)
}
if v, exist := cluster.Status.Capacity[resourceName]; exist {
capacity = v.AsApproximateFloat64()
} else {
return allocatable, capacity, fmt.Errorf("no capacity %s found in cluster %s", resourceName, cluster.ObjectMeta.Name)
}
return allocatable, capacity, nil
}
// Go through all the cluster resources and return the min and max allocatable value of the resourceName.
func getClustersMinMaxAllocatableResource(clusters []*clusterapiv1.ManagedCluster, resourceName clusterapiv1.ResourceName) (minAllocatable, maxAllocatable float64, err error) {
allocatable := sort.Float64Slice{}
// get allocatable resources
for _, cluster := range clusters {
if alloc, _, err := getClusterResource(cluster, resourceName); err == nil {
allocatable = append(allocatable, alloc)
}
}
// return err if no allocatable resource
if len(allocatable) == 0 {
return 0, 0, fmt.Errorf("no allocatable %s found in clusters", resourceName)
}
// sort to get min and max
sort.Float64s(allocatable)
return allocatable[0], allocatable[len(allocatable)-1], nil
}