312 lines
9.5 KiB
Go
312 lines
9.5 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
|
)
|
|
|
|
// Resource is a collection of compute resource.
|
|
type Resource struct {
|
|
MilliCPU int64
|
|
Memory int64
|
|
EphemeralStorage int64
|
|
AllowedPodNumber int64
|
|
|
|
// ScalarResources
|
|
ScalarResources map[corev1.ResourceName]int64
|
|
}
|
|
|
|
// EmptyResource creates a empty resource object and returns.
|
|
func EmptyResource() *Resource {
|
|
return &Resource{}
|
|
}
|
|
|
|
// NewResource creates a new resource object from resource list.
|
|
func NewResource(rl corev1.ResourceList) *Resource {
|
|
r := &Resource{}
|
|
r.Add(rl)
|
|
return r
|
|
}
|
|
|
|
// Add is used to add two resources.
|
|
func (r *Resource) Add(rl corev1.ResourceList) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
for rName, rQuant := range rl {
|
|
switch rName {
|
|
case corev1.ResourceCPU:
|
|
r.MilliCPU += rQuant.MilliValue()
|
|
case corev1.ResourceMemory:
|
|
r.Memory += rQuant.Value()
|
|
case corev1.ResourcePods:
|
|
r.AllowedPodNumber += rQuant.Value()
|
|
case corev1.ResourceEphemeralStorage:
|
|
r.EphemeralStorage += rQuant.Value()
|
|
default:
|
|
if lifted.IsScalarResourceName(rName) {
|
|
r.AddScalar(rName, rQuant.Value())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sub is used to subtract two resources.
|
|
// Return error when the minuend is less than the subtrahend.
|
|
func (r *Resource) Sub(rl corev1.ResourceList) error {
|
|
for rName, rQuant := range rl {
|
|
switch rName {
|
|
case corev1.ResourceCPU:
|
|
cpu := rQuant.MilliValue()
|
|
if r.MilliCPU < cpu {
|
|
return fmt.Errorf("cpu difference is less than 0, remain %d, got %d", r.MilliCPU, cpu)
|
|
}
|
|
r.MilliCPU -= cpu
|
|
case corev1.ResourceMemory:
|
|
mem := rQuant.Value()
|
|
if r.Memory < mem {
|
|
return fmt.Errorf("memory difference is less than 0, remain %d, got %d", r.Memory, mem)
|
|
}
|
|
r.Memory -= mem
|
|
case corev1.ResourcePods:
|
|
pods := rQuant.Value()
|
|
if r.AllowedPodNumber < pods {
|
|
return fmt.Errorf("allowed pod difference is less than 0, remain %d, got %d", r.AllowedPodNumber, pods)
|
|
}
|
|
r.AllowedPodNumber -= pods
|
|
case corev1.ResourceEphemeralStorage:
|
|
ephemeralStorage := rQuant.Value()
|
|
if r.EphemeralStorage < ephemeralStorage {
|
|
return fmt.Errorf("allowed storage number difference is less than 0, remain %d, got %d", r.EphemeralStorage, ephemeralStorage)
|
|
}
|
|
r.EphemeralStorage -= ephemeralStorage
|
|
default:
|
|
if lifted.IsScalarResourceName(rName) {
|
|
rScalar, ok := r.ScalarResources[rName]
|
|
scalar := rQuant.Value()
|
|
if !ok && scalar > 0 {
|
|
return fmt.Errorf("scalar resources %s does not exist, got %d", rName, scalar)
|
|
}
|
|
if rScalar < scalar {
|
|
return fmt.Errorf("scalar resources %s difference is less than 0, remain %d, got %d", rName, rScalar, scalar)
|
|
}
|
|
r.ScalarResources[rName] = rScalar - scalar
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetMaxResource compares with ResourceList and takes max value for each Resource.
|
|
func (r *Resource) SetMaxResource(rl corev1.ResourceList) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
for rName, rQuant := range rl {
|
|
switch rName {
|
|
case corev1.ResourceCPU:
|
|
if cpu := rQuant.MilliValue(); cpu > r.MilliCPU {
|
|
r.MilliCPU = cpu
|
|
}
|
|
case corev1.ResourceMemory:
|
|
if mem := rQuant.Value(); mem > r.Memory {
|
|
r.Memory = mem
|
|
}
|
|
case corev1.ResourceEphemeralStorage:
|
|
if ephemeralStorage := rQuant.Value(); ephemeralStorage > r.EphemeralStorage {
|
|
r.EphemeralStorage = ephemeralStorage
|
|
}
|
|
case corev1.ResourcePods:
|
|
if pods := rQuant.Value(); pods > r.AllowedPodNumber {
|
|
r.AllowedPodNumber = pods
|
|
}
|
|
default:
|
|
if lifted.IsScalarResourceName(rName) {
|
|
if value := rQuant.Value(); value > r.ScalarResources[rName] {
|
|
r.SetScalar(rName, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddScalar adds a resource by a scalar value of this resource.
|
|
func (r *Resource) AddScalar(name corev1.ResourceName, quantity int64) {
|
|
r.SetScalar(name, r.ScalarResources[name]+quantity)
|
|
}
|
|
|
|
// SetScalar sets a resource by a scalar value of this resource.
|
|
func (r *Resource) SetScalar(name corev1.ResourceName, quantity int64) {
|
|
// Lazily allocate scalar resource map.
|
|
if r.ScalarResources == nil {
|
|
r.ScalarResources = map[corev1.ResourceName]int64{}
|
|
}
|
|
r.ScalarResources[name] = quantity
|
|
}
|
|
|
|
// ResourceList returns a resource list of this resource.
|
|
func (r *Resource) ResourceList() corev1.ResourceList {
|
|
result := corev1.ResourceList{}
|
|
if r.MilliCPU > 0 {
|
|
result[corev1.ResourceCPU] = *resource.NewMilliQuantity(r.MilliCPU, resource.DecimalSI)
|
|
}
|
|
if r.Memory > 0 {
|
|
result[corev1.ResourceMemory] = *resource.NewQuantity(r.Memory, resource.BinarySI)
|
|
}
|
|
if r.EphemeralStorage > 0 {
|
|
result[corev1.ResourceEphemeralStorage] = *resource.NewQuantity(r.EphemeralStorage, resource.BinarySI)
|
|
}
|
|
if r.AllowedPodNumber > 0 {
|
|
result[corev1.ResourcePods] = *resource.NewQuantity(r.AllowedPodNumber, resource.DecimalSI)
|
|
}
|
|
for rName, rQuant := range r.ScalarResources {
|
|
if rQuant > 0 {
|
|
if lifted.IsHugePageResourceName(rName) {
|
|
result[rName] = *resource.NewQuantity(rQuant, resource.BinarySI)
|
|
} else {
|
|
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
|
|
}
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
// MaxDivided returns how many replicas that the resource can be divided.
|
|
func (r *Resource) MaxDivided(rl corev1.ResourceList) int64 {
|
|
res := int64(math.MaxInt64)
|
|
for rName, rQuant := range rl {
|
|
switch rName {
|
|
case corev1.ResourceCPU:
|
|
if cpu := rQuant.MilliValue(); cpu > 0 {
|
|
res = MinInt64(res, r.MilliCPU/cpu)
|
|
}
|
|
case corev1.ResourceMemory:
|
|
if mem := rQuant.Value(); mem > 0 {
|
|
res = MinInt64(res, r.Memory/mem)
|
|
}
|
|
case corev1.ResourceEphemeralStorage:
|
|
if ephemeralStorage := rQuant.Value(); ephemeralStorage > 0 {
|
|
res = MinInt64(res, r.EphemeralStorage/ephemeralStorage)
|
|
}
|
|
default:
|
|
if lifted.IsScalarResourceName(rName) {
|
|
rScalar := r.ScalarResources[rName]
|
|
if scalar := rQuant.Value(); scalar > 0 {
|
|
res = MinInt64(res, rScalar/scalar)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
res = MinInt64(res, r.AllowedPodNumber)
|
|
return res
|
|
}
|
|
|
|
// LessEqual returns whether all dimensions of resources in r are less than or equal with that of rr.
|
|
func (r *Resource) LessEqual(rr *Resource) bool {
|
|
lessEqualFunc := func(l, r int64) bool {
|
|
return l <= r
|
|
}
|
|
|
|
if !lessEqualFunc(r.MilliCPU, rr.MilliCPU) {
|
|
return false
|
|
}
|
|
if !lessEqualFunc(r.Memory, rr.Memory) {
|
|
return false
|
|
}
|
|
if !lessEqualFunc(r.EphemeralStorage, rr.EphemeralStorage) {
|
|
return false
|
|
}
|
|
if !lessEqualFunc(r.AllowedPodNumber, rr.AllowedPodNumber) {
|
|
return false
|
|
}
|
|
for rrName, rrQuant := range rr.ScalarResources {
|
|
rQuant := r.ScalarResources[rrName]
|
|
if !lessEqualFunc(rQuant, rrQuant) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// AddPodTemplateRequest add the effective request resource of a pod template to the origin resource.
|
|
// If pod container limits are specified, but requests are not, default requests to limits.
|
|
// The code logic is almost the same as kubernetes.
|
|
// https://github.com/kubernetes/kubernetes/blob/f7cdbe2c96cc12101226686df9e9819b4b007c5c/pkg/apis/core/v1/defaults.go#L147-L181
|
|
func (r *Resource) AddPodTemplateRequest(podSpec *corev1.PodSpec) *Resource {
|
|
// DeepCopy first because we may modify the Resources.Requests field.
|
|
podSpec = podSpec.DeepCopy()
|
|
for i := range podSpec.Containers {
|
|
// set requests to limits if requests are not specified, but limits are
|
|
if podSpec.Containers[i].Resources.Limits != nil {
|
|
if podSpec.Containers[i].Resources.Requests == nil {
|
|
podSpec.Containers[i].Resources.Requests = make(corev1.ResourceList)
|
|
}
|
|
for key, value := range podSpec.Containers[i].Resources.Limits {
|
|
if _, exists := podSpec.Containers[i].Resources.Requests[key]; !exists {
|
|
podSpec.Containers[i].Resources.Requests[key] = value.DeepCopy()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for i := range podSpec.InitContainers {
|
|
if podSpec.InitContainers[i].Resources.Limits != nil {
|
|
if podSpec.InitContainers[i].Resources.Requests == nil {
|
|
podSpec.InitContainers[i].Resources.Requests = make(corev1.ResourceList)
|
|
}
|
|
for key, value := range podSpec.InitContainers[i].Resources.Limits {
|
|
if _, exists := podSpec.InitContainers[i].Resources.Requests[key]; !exists {
|
|
podSpec.InitContainers[i].Resources.Requests[key] = value.DeepCopy()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return r.AddPodRequest(podSpec)
|
|
}
|
|
|
|
// AddPodRequest add the effective request resource of a pod to the origin resource.
|
|
// The Pod's effective request is the higher of:
|
|
// - the sum of all app containers(spec.Containers) request for a resource.
|
|
// - the effective init containers(spec.InitContainers) request for a resource.
|
|
// The effective init containers request is the highest request on all init containers.
|
|
func (r *Resource) AddPodRequest(podSpec *corev1.PodSpec) *Resource {
|
|
for _, container := range podSpec.Containers {
|
|
r.Add(container.Resources.Requests)
|
|
}
|
|
for _, container := range podSpec.InitContainers {
|
|
r.SetMaxResource(container.Resources.Requests)
|
|
}
|
|
// If Overhead is being utilized, add to the total requests for the pod.
|
|
// We assume the EnablePodOverhead feature gate of member cluster is set (it is on by default since 1.18).
|
|
if podSpec.Overhead != nil {
|
|
r.Add(podSpec.Overhead)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// AddResourcePods adds pod resources into the Resource.
|
|
// Notice that a pod request resource list does not contain a request for pod resources,
|
|
// this function helps to add the pod resources.
|
|
func (r *Resource) AddResourcePods(pods int64) {
|
|
r.Add(corev1.ResourceList{
|
|
corev1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
|
|
})
|
|
}
|
|
|
|
// MinInt64 returns the smaller of two int64 numbers.
|
|
func MinInt64(a, b int64) int64 {
|
|
if a <= b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|