247 lines
7.1 KiB
Go
247 lines
7.1 KiB
Go
/*
|
|
Copyright 2022 The Karmada 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.
|
|
*/
|
|
|
|
package modeling
|
|
|
|
import (
|
|
"container/list"
|
|
"errors"
|
|
"math"
|
|
|
|
rbt "github.com/emirpasic/gods/trees/redblacktree"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
"k8s.io/klog/v2"
|
|
|
|
clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
|
)
|
|
|
|
// ResourceSummary records the list of resourceModels
|
|
type ResourceSummary struct {
|
|
RMs []resourceModels
|
|
modelSortings [][]resource.Quantity
|
|
modelSortingResourceNames []corev1.ResourceName
|
|
}
|
|
|
|
// resourceModels records the number of each allocatable resource models.
|
|
type resourceModels struct {
|
|
// quantity is the total number of each allocatable resource models
|
|
// +required
|
|
Quantity int
|
|
|
|
// when the number of node is less than or equal to six, it will be sorted by linkedlist,
|
|
// when the number of node is more than six, it will be sorted by red-black tree.
|
|
|
|
// when the data structure is linkedlist,
|
|
// each item will store clusterResourceNode.
|
|
// +required
|
|
linkedlist *list.List
|
|
|
|
// when the data structure is redblack tree,
|
|
// each item will store a key-value pair,
|
|
// key is ResourceList, the value is quantity of this ResourceList
|
|
// +optional
|
|
redblackTree *rbt.Tree
|
|
}
|
|
|
|
// ClusterResourceNode represents the each raw resource entity without modeling.
|
|
type ClusterResourceNode struct {
|
|
// quantity is the the number of this node
|
|
// Only when the resourceLists are exactly the same can they be counted as the same node.
|
|
// +required
|
|
quantity int
|
|
|
|
// resourceList records the resource list of this node.
|
|
// It maybe contain cpu, memory, gpu...
|
|
// User can specify which parameters need to be included before the cluster starts
|
|
// +required
|
|
resourceList corev1.ResourceList
|
|
}
|
|
|
|
// InitSummary is the init function of modeling data structure
|
|
func InitSummary(resourceModel []clusterapis.ResourceModel) (ResourceSummary, error) {
|
|
var rsName []corev1.ResourceName
|
|
var rsList []corev1.ResourceList
|
|
for _, rm := range resourceModel {
|
|
tmp := map[corev1.ResourceName]resource.Quantity{}
|
|
for _, rmItem := range rm.Ranges {
|
|
if len(rsName) != len(rm.Ranges) {
|
|
rsName = append(rsName, rmItem.Name)
|
|
}
|
|
tmp[rmItem.Name] = rmItem.Min
|
|
}
|
|
rsList = append(rsList, tmp)
|
|
}
|
|
|
|
if len(rsName) != 0 && len(rsList) != 0 && (len(rsName) != len(rsList[0])) {
|
|
return ResourceSummary{}, errors.New("the number of resourceName is not equal the number of resourceList")
|
|
}
|
|
|
|
rms := make([]resourceModels, len(rsList))
|
|
// generate a sorted array by first priority of ResourceName
|
|
modelSortings := make([][]resource.Quantity, len(rsName))
|
|
for index := 0; index < len(rsList); index++ {
|
|
for i, name := range rsName {
|
|
modelSortings[i] = append(modelSortings[i], rsList[index][name])
|
|
}
|
|
}
|
|
return ResourceSummary{RMs: rms, modelSortings: modelSortings, modelSortingResourceNames: rsName}, nil
|
|
}
|
|
|
|
// NewClusterResourceNode create new cluster resource node
|
|
func NewClusterResourceNode(resourceList corev1.ResourceList) ClusterResourceNode {
|
|
return ClusterResourceNode{
|
|
quantity: 1,
|
|
resourceList: resourceList,
|
|
}
|
|
}
|
|
|
|
func (rs *ResourceSummary) getIndex(crn ClusterResourceNode) int {
|
|
index := math.MaxInt
|
|
for i, m := range rs.modelSortingResourceNames {
|
|
tmpIndex := searchLastLessElement(rs.modelSortings[i], crn.resourceList[m])
|
|
if tmpIndex < index {
|
|
index = tmpIndex
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func searchLastLessElement(nums []resource.Quantity, target resource.Quantity) int {
|
|
low, high := 0, len(nums)-1
|
|
for low <= high {
|
|
mid := low + ((high - low) >> 1)
|
|
diff1 := nums[mid].Cmp(target)
|
|
var diff2 int
|
|
if mid != len(nums)-1 {
|
|
diff2 = nums[mid+1].Cmp(target)
|
|
}
|
|
// diff < 1 means nums[mid] <= target
|
|
// diff == 1 means nums[mid+1] > target
|
|
if diff1 < 1 {
|
|
if (mid == len(nums)-1) || (diff2 == 1) {
|
|
// find the last less element that equal to target element
|
|
return mid
|
|
}
|
|
low = mid + 1
|
|
} else {
|
|
high = mid - 1
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// clusterResourceNodeComparator provides a fast comparison on clusterResourceNodes
|
|
func (rs *ResourceSummary) clusterResourceNodeComparator(a, b interface{}) int {
|
|
s1 := a.(ClusterResourceNode)
|
|
s2 := b.(ClusterResourceNode)
|
|
for index := 0; index < len(rs.modelSortingResourceNames); index++ {
|
|
tmp1, tmp2 := s1.resourceList[rs.modelSortingResourceNames[index]], s2.resourceList[rs.modelSortingResourceNames[index]]
|
|
|
|
diff := tmp1.Cmp(tmp2)
|
|
if diff != 0 {
|
|
return diff
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// AddToResourceSummary add resource node into modeling summary
|
|
func (rs *ResourceSummary) AddToResourceSummary(crn ClusterResourceNode) {
|
|
index := rs.getIndex(crn)
|
|
if index == -1 {
|
|
klog.Error("ClusterResource can not add to resource summary: index is invalid.")
|
|
return
|
|
}
|
|
modeling := &(*rs).RMs[index]
|
|
if rs.GetNodeNumFromModel(modeling) <= 5 {
|
|
root := modeling.linkedlist
|
|
if root == nil {
|
|
root = list.New()
|
|
}
|
|
found := false
|
|
// traverse linkedlist to add quantity of recourse modeling
|
|
for element := root.Front(); element != nil; element = element.Next() {
|
|
switch rs.clusterResourceNodeComparator(element.Value, crn) {
|
|
case 0:
|
|
{
|
|
tmpCrn := element.Value.(ClusterResourceNode)
|
|
tmpCrn.quantity += crn.quantity
|
|
element.Value = tmpCrn
|
|
found = true
|
|
break
|
|
}
|
|
case 1:
|
|
{
|
|
root.InsertBefore(crn, element)
|
|
found = true
|
|
break
|
|
}
|
|
case -1:
|
|
{
|
|
continue
|
|
}
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
root.PushBack(crn)
|
|
}
|
|
modeling.linkedlist = root
|
|
} else {
|
|
root := modeling.redblackTree
|
|
if root == nil {
|
|
root = rs.llConvertToRbt(modeling.linkedlist)
|
|
modeling.linkedlist = nil
|
|
}
|
|
tmpNode := root.GetNode(crn)
|
|
if tmpNode != nil {
|
|
node := tmpNode.Key.(ClusterResourceNode)
|
|
node.quantity += crn.quantity
|
|
tmpNode.Key = node
|
|
} else {
|
|
root.Put(crn, crn.quantity)
|
|
}
|
|
modeling.redblackTree = root
|
|
}
|
|
modeling.Quantity += crn.quantity
|
|
}
|
|
|
|
func (rs *ResourceSummary) llConvertToRbt(list *list.List) *rbt.Tree {
|
|
root := rbt.NewWith(rs.clusterResourceNodeComparator)
|
|
for element := list.Front(); element != nil; element = element.Next() {
|
|
tmpCrn := element.Value.(ClusterResourceNode)
|
|
root.Put(tmpCrn, tmpCrn.quantity)
|
|
}
|
|
return root
|
|
}
|
|
|
|
// GetNodeNumFromModel is for getting node number from the modeling
|
|
func (rs *ResourceSummary) GetNodeNumFromModel(model *resourceModels) int {
|
|
if model.linkedlist != nil && model.redblackTree == nil {
|
|
return model.linkedlist.Len()
|
|
} else if model.linkedlist == nil && model.redblackTree != nil {
|
|
return model.redblackTree.Size()
|
|
} else if model.linkedlist == nil && model.redblackTree == nil {
|
|
return 0
|
|
} else if model.linkedlist != nil && model.redblackTree != nil {
|
|
klog.Info("GetNodeNum: unknown error")
|
|
}
|
|
return 0
|
|
}
|