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 defaultModelSorting []clusterapis.ResourceName ) // ResourceSummary records the list of resourceModels type ResourceSummary struct { RMs []resourceModels modelSortings [][]resource.Quantity } // 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 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(resourceModel []clusterapis.ResourceModel) (ResourceSummary, error) { var rsName []clusterapis.ResourceName var rsList []ResourceList for _, rm := range resourceModel { 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 ResourceSummary{}, errors.New("the number of resourceName is not equal the number of resourceList") } if len(rsName) != 0 { defaultModelSorting = rsName } 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}, 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(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 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) 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 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: unknown error") } return 0 }