329 lines
9.2 KiB
Go
329 lines
9.2 KiB
Go
package modeling
|
|
|
|
import (
|
|
"container/list"
|
|
"errors"
|
|
"math"
|
|
"sync"
|
|
|
|
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"
|
|
)
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
modelSortings [][]resource.Quantity
|
|
defaultModelSorting []clusterapis.ResourceName
|
|
)
|
|
|
|
// ResourceSummary records the list of resourceModels
|
|
type ResourceSummary []resourceModels
|
|
|
|
// 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, mrmory, gpu...
|
|
// User can specify which parameters need to be included before the cluster starts
|
|
// +required
|
|
resourceList ResourceList
|
|
}
|
|
|
|
// ResourceList is a set of (resource name, quantity) pairs.
|
|
type ResourceList map[clusterapis.ResourceName]resource.Quantity
|
|
|
|
// InitSummary is the init function of modeling data structure
|
|
func InitSummary(resourceModels []clusterapis.ResourceModel) (ResourceSummary, error) {
|
|
var rsName []clusterapis.ResourceName
|
|
var rsList []ResourceList
|
|
for _, rm := range resourceModels {
|
|
tmp := map[clusterapis.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 nil, errors.New("the number of resourceName is not equal the number of resourceList")
|
|
}
|
|
var rs ResourceSummary
|
|
if len(rsName) != 0 {
|
|
defaultModelSorting = rsName
|
|
}
|
|
rs = make(ResourceSummary, len(rsList))
|
|
// generate a sorted array by first priority of ResourceName
|
|
modelSortings = make([][]resource.Quantity, len(rsList))
|
|
for index := 0; index < len(rsList); index++ {
|
|
for i, name := range rsName {
|
|
modelSortings[i] = append(modelSortings[i], rsList[index][name])
|
|
}
|
|
}
|
|
return rs, nil
|
|
}
|
|
|
|
// NewClusterResourceNode create new cluster resource node
|
|
func NewClusterResourceNode(resourceList corev1.ResourceList) ClusterResourceNode {
|
|
return ClusterResourceNode{
|
|
quantity: 1,
|
|
resourceList: ConvertToResourceList(resourceList),
|
|
}
|
|
}
|
|
|
|
func (rs *ResourceSummary) getIndex(crn ClusterResourceNode) int {
|
|
index := math.MaxInt
|
|
for i, m := range defaultModelSorting {
|
|
tmpIndex := searchLastLessElement(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 clusterResourceNodeComparator(a, b interface{}) int {
|
|
s1 := a.(ClusterResourceNode)
|
|
s2 := b.(ClusterResourceNode)
|
|
for index := 0; index < len(defaultModelSorting); index++ {
|
|
tmp1, tmp2 := s1.resourceList[defaultModelSorting[index]], s2.resourceList[defaultModelSorting[index]]
|
|
|
|
diff := tmp1.Cmp(tmp2)
|
|
if diff != 0 {
|
|
return diff
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func safeChangeNum(num *int, change int) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
*num += change
|
|
}
|
|
|
|
// AddToResourceSummary add resource node into modeling summary
|
|
func (rs *ResourceSummary) AddToResourceSummary(crn ClusterResourceNode) {
|
|
index := rs.getIndex(crn)
|
|
modeling := &(*rs)[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 clusterResourceNodeComparator(element.Value, crn) {
|
|
case 0:
|
|
{
|
|
tmpCrn := element.Value.(ClusterResourceNode)
|
|
safeChangeNum(&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 = llConvertToRbt(modeling.linkedlist)
|
|
modeling.linkedlist = nil
|
|
}
|
|
tmpNode := root.GetNode(crn)
|
|
if tmpNode != nil {
|
|
node := tmpNode.Key.(ClusterResourceNode)
|
|
safeChangeNum(&node.quantity, crn.quantity)
|
|
tmpNode.Key = node
|
|
} else {
|
|
root.Put(crn, crn.quantity)
|
|
}
|
|
modeling.redblackTree = root
|
|
}
|
|
safeChangeNum(&modeling.Quantity, crn.quantity)
|
|
}
|
|
|
|
func llConvertToRbt(list *list.List) *rbt.Tree {
|
|
root := rbt.NewWith(clusterResourceNodeComparator)
|
|
for element := list.Front(); element != nil; element = element.Next() {
|
|
tmpCrn := element.Value.(ClusterResourceNode)
|
|
root.Put(tmpCrn, tmpCrn.quantity)
|
|
}
|
|
return root
|
|
}
|
|
|
|
func rbtConvertToLl(rbt *rbt.Tree) *list.List {
|
|
root := list.New()
|
|
for _, v := range rbt.Keys() {
|
|
root.PushBack(v)
|
|
}
|
|
return root
|
|
}
|
|
|
|
// ConvertToResourceList is convert from corev1.ResourceList to ResourceList
|
|
func ConvertToResourceList(rslist corev1.ResourceList) ResourceList {
|
|
resourceList := ResourceList{}
|
|
for name, quantity := range rslist {
|
|
if name == corev1.ResourceCPU {
|
|
resourceList[clusterapis.ResourceCPU] = quantity
|
|
} else if name == corev1.ResourceMemory {
|
|
resourceList[clusterapis.ResourceMemory] = quantity
|
|
} else if name == corev1.ResourceStorage {
|
|
resourceList[clusterapis.ResourceStorage] = quantity
|
|
} else if name == corev1.ResourceEphemeralStorage {
|
|
resourceList[clusterapis.ResourceEphemeralStorage] = quantity
|
|
}
|
|
}
|
|
return resourceList
|
|
}
|
|
|
|
// 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: unknow error")
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// DeleteFromResourceSummary dalete resource node into modeling summary
|
|
func (rs *ResourceSummary) DeleteFromResourceSummary(crn ClusterResourceNode) error {
|
|
index := rs.getIndex(crn)
|
|
modeling := &(*rs)[index]
|
|
if rs.GetNodeNumFromModel(modeling) >= 6 {
|
|
root := modeling.redblackTree
|
|
tmpNode := root.GetNode(crn)
|
|
if tmpNode != nil {
|
|
node := tmpNode.Key.(ClusterResourceNode)
|
|
safeChangeNum(&node.quantity, -crn.quantity)
|
|
tmpNode.Key = node
|
|
if node.quantity == 0 {
|
|
root.Remove(tmpNode)
|
|
}
|
|
} else {
|
|
return errors.New("delete fail: node no found in redblack tree")
|
|
}
|
|
modeling.redblackTree = root
|
|
} else {
|
|
root, tree := modeling.linkedlist, modeling.redblackTree
|
|
if root == nil && tree != nil {
|
|
root = rbtConvertToLl(tree)
|
|
}
|
|
if root == nil && tree == nil {
|
|
return errors.New("delete fail: node no found in linked list")
|
|
}
|
|
found := false
|
|
// traverse linkedlist to remove quantity of recourse modeling
|
|
for element := root.Front(); element != nil; element = element.Next() {
|
|
if clusterResourceNodeComparator(element.Value, crn) == 0 {
|
|
tmpCrn := element.Value.(ClusterResourceNode)
|
|
safeChangeNum(&tmpCrn.quantity, -crn.quantity)
|
|
element.Value = tmpCrn
|
|
if tmpCrn.quantity == 0 {
|
|
root.Remove(element)
|
|
}
|
|
found = true
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return errors.New("delete fail: node no found in linkedlist")
|
|
}
|
|
modeling.linkedlist = root
|
|
}
|
|
safeChangeNum(&modeling.Quantity, -crn.quantity)
|
|
return nil
|
|
}
|
|
|
|
// UpdateInResourceSummary update resource node into modeling summary
|
|
func (rs *ResourceSummary) UpdateInResourceSummary(oldNode, newNode ClusterResourceNode) error {
|
|
rs.AddToResourceSummary(newNode)
|
|
err := rs.DeleteFromResourceSummary(oldNode)
|
|
if err != nil {
|
|
return errors.New("delete fail: node no found in linked list")
|
|
}
|
|
return nil
|
|
}
|