Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webhook: support elasticquota enable update resource key #2323

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ const (
// to belong to the users and will not be preempted back.
ElasticQuotaGuaranteeUsage featuregate.Feature = "ElasticQuotaGuaranteeUsage"

// ElasticQuotaEnableUpdateResourceKey allows to update resource key in standard operation
// when delete resource type: from child to parent
// when add resource type: from parent to child
ElasticQuotaEnableUpdateResourceKey featuregate.Feature = "ElasticQuotaEnableUpdateResourceKey"

// DisableDefaultQuota disable default quota.
DisableDefaultQuota featuregate.Feature = "DisableDefaultQuota"

Expand All @@ -94,6 +99,7 @@ var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
MultiQuotaTree: {Default: false, PreRelease: featuregate.Alpha},
ElasticQuotaIgnorePodOverhead: {Default: false, PreRelease: featuregate.Alpha},
ElasticQuotaGuaranteeUsage: {Default: false, PreRelease: featuregate.Alpha},
ElasticQuotaEnableUpdateResourceKey: {Default: false, PreRelease: featuregate.Alpha},
DisableDefaultQuota: {Default: false, PreRelease: featuregate.Alpha},
SupportParentQuotaSubmitPod: {Default: false, PreRelease: featuregate.Alpha},
EnableQuotaAdmission: {Default: false, PreRelease: featuregate.Alpha},
Expand Down
22 changes: 15 additions & 7 deletions pkg/scheduler/plugins/elasticquota/core/group_quota_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,22 +563,31 @@ func (gqm *GroupQuotaManager) updateOneGroupSharedWeightNoLock(quotaInfo *QuotaI
gqm.runtimeQuotaCalculatorMap[quotaInfo.ParentName].updateOneGroupSharedWeight(quotaInfo)
}

// updateResourceKeyNoLock based on quotaInfo.CalculateInfo.Max of self
// Note: RootQuotaName need to be updated as allResourceKeys
func (gqm *GroupQuotaManager) updateResourceKeyNoLock() {
// collect all dimensions
resourceKeys := make(map[v1.ResourceName]struct{})
allResourceKeys := make(map[v1.ResourceName]struct{})
for quotaName, quotaInfo := range gqm.quotaInfoMap {
if quotaName == extension.DefaultQuotaName || quotaName == extension.SystemQuotaName {
if quotaName == extension.RootQuotaName || quotaName == extension.DefaultQuotaName || quotaName == extension.SystemQuotaName {
continue
}
resourceKeys := make(map[v1.ResourceName]struct{})
for resName := range quotaInfo.CalculateInfo.Max {
allResourceKeys[resName] = struct{}{}
resourceKeys[resName] = struct{}{}
}
// update right now
if runtimeQuotaCalculator, ok := gqm.runtimeQuotaCalculatorMap[quotaName]; ok && runtimeQuotaCalculator != nil && !reflect.DeepEqual(resourceKeys, runtimeQuotaCalculator.resourceKeys) {
runtimeQuotaCalculator.updateResourceKeys(resourceKeys)
}
}

if !reflect.DeepEqual(resourceKeys, gqm.resourceKeys) {
gqm.resourceKeys = resourceKeys
for _, runtimeQuotaCalculator := range gqm.runtimeQuotaCalculatorMap {
runtimeQuotaCalculator.updateResourceKeys(resourceKeys)
// keep special ones same
if !reflect.DeepEqual(allResourceKeys, gqm.resourceKeys) {
gqm.resourceKeys = allResourceKeys
if runtimeQuotaCalculator, ok := gqm.runtimeQuotaCalculatorMap[extension.RootQuotaName]; ok && runtimeQuotaCalculator != nil {
runtimeQuotaCalculator.updateResourceKeys(allResourceKeys)
}
}
}
Expand Down Expand Up @@ -1019,7 +1028,6 @@ func (gqm *GroupQuotaManager) updateQuotaInternalNoLock(newQuotaInfo, oldQuotaIn

// update resource keys
gqm.updateResourceKeyNoLock()

oldMin := v1.ResourceList{}
if oldQuotaInfo != nil {
oldMin = oldQuotaInfo.CalculateInfo.Min
Expand Down
189 changes: 184 additions & 5 deletions pkg/scheduler/plugins/elasticquota/core/group_quota_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import (
)

const (
GigaByte = 1024 * 1048576
GigaByte = 1024 * 1048576
ExtendedResourceKeyXCPU = "x-cpu"
)

func TestGroupQuotaManager_QuotaAdd(t *testing.T) {
Expand Down Expand Up @@ -151,6 +152,7 @@ func TestGroupQuotaManager_UpdateQuota(t *testing.T) {
}

func TestGroupQuotaManager_UpdateQuotaInternalAndRequest(t *testing.T) {
// add resource to node
gqm := NewGroupQuotaManagerForTest()
deltaRes := createResourceList(96, 160*GigaByte)
gqm.UpdateClusterTotalResource(deltaRes)
Expand All @@ -160,23 +162,136 @@ func TestGroupQuotaManager_UpdateQuotaInternalAndRequest(t *testing.T) {

AddQuotaToManager(t, gqm, "test1", extension.RootQuotaName, 96, 160*GigaByte, 50, 80*GigaByte, true, false)

// test1 request[120, 290] runtime == maxQuota
// request[120, 290] > maxQuota, runtime == maxQuota
request := createResourceList(120, 290*GigaByte)
gqm.updateGroupDeltaRequestNoLock("test1", request, request, 0)
runtime := gqm.RefreshRuntime("test1")
assert.Equal(t, deltaRes, runtime)
expectCurrentRuntime := deltaRes
assert.Equal(t, expectCurrentRuntime, runtime)

// update resourceKey
quota1 := CreateQuota("test1", extension.RootQuotaName, 64, 100*GigaByte, 60, 90*GigaByte, true, false)
quota1.Labels[extension.LabelQuotaIsParent] = "false"
err := gqm.UpdateQuota(quota1, false)
assert.Nil(t, err)
quotaInfo := gqm.GetQuotaInfoByName("test1")
assert.Equal(t, createResourceList(64, 100*GigaByte), quotaInfo.CalculateInfo.Max)
runtime = gqm.RefreshRuntime("test1")
expectCurrentRuntime = createResourceList(64, 100*GigaByte)
assert.Equal(t, expectCurrentRuntime, runtime)

// added max ExtendedResourceKeyXCPU without node resource added
// runtime.ExtendedResourceKeyXCPU = 0
request[ExtendedResourceKeyXCPU] = *resource.NewQuantity(80, resource.DecimalSI)
gqm.updateGroupDeltaRequestNoLock("test1", request, request, 0)
xCPUQuantity := resource.NewQuantity(100, resource.DecimalSI)
quota1.Spec.Max[ExtendedResourceKeyXCPU] = *xCPUQuantity
maxJson, err := json.Marshal(quota1.Spec.Max)
assert.Nil(t, err)
quota1.Annotations[extension.AnnotationSharedWeight] = string(maxJson)
gqm.UpdateQuota(quota1, false)
quotaInfo = gqm.quotaInfoMap["test1"]
assert.True(t, quotaInfo != nil)
assert.Equal(t, *xCPUQuantity, quotaInfo.CalculateInfo.Max[ExtendedResourceKeyXCPU])
runtime = gqm.RefreshRuntime("test1")
expectCurrentRuntime[ExtendedResourceKeyXCPU] = resource.Quantity{Format: resource.DecimalSI}
assert.Equal(t, expectCurrentRuntime, runtime)

// add ExtendedResourceKeyXCPU to node resource
deltaRes[ExtendedResourceKeyXCPU] = *xCPUQuantity
gqm.UpdateClusterTotalResource(deltaRes)
runtime = gqm.RefreshRuntime("test1")
assert.Equal(t, createResourceList(64, 100*GigaByte), runtime)
expectCurrentRuntime[ExtendedResourceKeyXCPU] = *resource.NewQuantity(80, resource.DecimalSI)
assert.Equal(t, expectCurrentRuntime, runtime)

// delete max ExtendedResourceKeyXCPU
delete(quota1.Spec.Max, ExtendedResourceKeyXCPU)
maxJson, err = json.Marshal(quota1.Spec.Max)
assert.Nil(t, err)
quota1.Annotations[extension.AnnotationSharedWeight] = string(maxJson)
gqm.UpdateQuota(quota1, false)
quotaInfo = gqm.quotaInfoMap["test1"]
assert.True(t, quotaInfo != nil)
assert.Equal(t, resource.Quantity{}, quotaInfo.CalculateInfo.Max[ExtendedResourceKeyXCPU])
runtime = gqm.RefreshRuntime("test1")
delete(expectCurrentRuntime, ExtendedResourceKeyXCPU)
assert.Equal(t, expectCurrentRuntime, runtime)
}

func TestGroupQuotaManager_UpdateResourceKeyNoLock(t *testing.T) {
gqm := NewGroupQuotaManagerForTest()
// add quotas
quota1Name := "test1"
quota1 := CreateQuota(quota1Name, extension.RootQuotaName, 64, 100*GigaByte, 60, 90*GigaByte, true, true)
quota1.Labels[extension.LabelQuotaIsParent] = "true"
quota1Info := NewQuotaInfoFromQuota(quota1)
gqm.quotaInfoMap[quota1Name] = quota1Info
gqm.runtimeQuotaCalculatorMap[quota1Name] = NewRuntimeQuotaCalculator(quota1Name)

quota2Name := "test2"
quota2 := CreateQuota(quota2Name, quota1Name, 64, 100*GigaByte, 60, 90*GigaByte, true, false)
quota2.Labels[extension.LabelQuotaIsParent] = "false"
quota2Info := NewQuotaInfoFromQuota(quota2)
gqm.quotaInfoMap[quota2Name] = quota2Info
gqm.runtimeQuotaCalculatorMap[quota2Name] = NewRuntimeQuotaCalculator(quota2Name)

resourceKeyCheckFunc := func(gqmRK, rootRK, quota1RK, quota2RK map[v1.ResourceName]struct{}) {
assert.Equal(t, gqmRK, gqm.resourceKeys)
assert.Equal(t, rootRK, gqm.runtimeQuotaCalculatorMap[extension.RootQuotaName].resourceKeys)
assert.Equal(t, quota1RK, gqm.runtimeQuotaCalculatorMap[quota1Name].resourceKeys)
assert.Equal(t, quota2RK, gqm.runtimeQuotaCalculatorMap[quota2Name].resourceKeys)
}
// before update firstly
resourceKeyCheckFunc(
map[v1.ResourceName]struct{}{},
map[v1.ResourceName]struct{}{},
map[v1.ResourceName]struct{}{},
map[v1.ResourceName]struct{}{},
)
// update firstly
gqm.updateResourceKeyNoLock()
resourceKeyCheckFunc(
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
)
// mock standard resourceKeys updated
// 1. quota1 add resourceKey
xCPUQuantity := resource.NewQuantity(100, resource.DecimalSI)
quota1.Spec.Max[ExtendedResourceKeyXCPU] = *xCPUQuantity
quota1Info = NewQuotaInfoFromQuota(quota1)
gqm.quotaInfoMap[quota1Name] = quota1Info
gqm.updateResourceKeyNoLock()
resourceKeyCheckFunc(
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
)
// 2.quota2 delete resourceKey
delete(quota2.Spec.Max, v1.ResourceCPU)
quota2Info = NewQuotaInfoFromQuota(quota2)
gqm.quotaInfoMap[quota2Name] = quota2Info
gqm.updateResourceKeyNoLock()
resourceKeyCheckFunc(
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}, ExtendedResourceKeyXCPU: {}},
map[v1.ResourceName]struct{}{v1.ResourceMemory: {}},
)
// 3.quota1 delete resourceKey
delete(quota1.Spec.Max, ExtendedResourceKeyXCPU)
quota1Info = NewQuotaInfoFromQuota(quota1)
gqm.quotaInfoMap[quota1Name] = quota1Info
gqm.updateResourceKeyNoLock()
resourceKeyCheckFunc(
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceCPU: {}, v1.ResourceMemory: {}},
map[v1.ResourceName]struct{}{v1.ResourceMemory: {}},
)
}
func TestGroupQuotaManager_DeleteOneGroup(t *testing.T) {
gqm := NewGroupQuotaManagerForTest()
gqm.UpdateClusterTotalResource(createResourceList(1000, 1000*GigaByte))
Expand Down Expand Up @@ -1903,6 +2018,70 @@ func TestGroupQuotaManager_RootQuotaRuntime(t *testing.T) {
assert.Equal(t, gqm.RefreshRuntime(extension.RootQuotaName), gqm.totalResourceExceptSystemAndDefaultUsed.DeepCopy())
}

func TestGroupQuotaManager_OnQuotaResourceKeyUpdateForGuarantee(t *testing.T) {
defer utilfeature.SetFeatureGateDuringTest(t, k8sfeature.DefaultFeatureGate, features.ElasticQuotaGuaranteeUsage, true)()
gqm := NewGroupQuotaManagerForTest()
gqm.scaleMinQuotaEnabled = true

rl := createResourceList(50, 50)
gqm.UpdateClusterTotalResource(rl)

// quota1 Max[40, 40] Min[20,20] request[0,0]
// |-- quota2 Max[40, 40] Min[10,10] request[0,0]
// |-- quota3 Max[40, 40] Min[5,5] request[0,0]
qi1 := CreateQuota("1", extension.RootQuotaName, 40, 40, 20, 20, true, true)
qi2 := CreateQuota("2", "1", 40, 40, 10, 10, true, false)
qi3 := CreateQuota("3", "1", 40, 40, 5, 5, true, false)
gqm.UpdateQuota(qi1, false)
gqm.UpdateQuota(qi2, false)
gqm.UpdateQuota(qi3, false)

// add pod1
pod1 := schetesting.MakePod().Name("1").Obj()
pod1.Spec.Containers = []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: createResourceList(5, 5),
},
},
}
pod1.Spec.NodeName = "node1"
gqm.OnPodAdd("2", pod1)
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetChildRequest())
assert.Equal(t, createResourceList(10, 10), gqm.GetQuotaInfoByName("2").GetRequest())
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetUsed())
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetAllocated())
assert.Equal(t, createResourceList(10, 10), gqm.GetQuotaInfoByName("2").GetGuaranteed())
// check parent info
// the parent should guarantee children min
assert.Equal(t, createResourceList(15, 15), gqm.GetQuotaInfoByName("1").GetAllocated())
assert.Equal(t, createResourceList(20, 20), gqm.GetQuotaInfoByName("1").GetGuaranteed())

// mock to standard delete resourceKeys
// delete resourceKey cpu from bottom to top
delete(qi1.Spec.Max, v1.ResourceCPU)
delete(qi1.Spec.Min, v1.ResourceCPU)
delete(qi2.Spec.Max, v1.ResourceCPU)
delete(qi2.Spec.Min, v1.ResourceCPU)
delete(qi3.Spec.Max, v1.ResourceCPU)
delete(qi3.Spec.Min, v1.ResourceCPU)
err := gqm.UpdateQuota(qi3, false)
assert.NoError(t, err)
err = gqm.UpdateQuota(qi2, false)
assert.NoError(t, err)
err = gqm.UpdateQuota(qi1, false)
assert.NoError(t, err)
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetChildRequest())
assert.Equal(t, createResourceList(5, 10), gqm.GetQuotaInfoByName("2").GetRequest())
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetUsed())
assert.Equal(t, createResourceList(5, 5), gqm.GetQuotaInfoByName("2").GetAllocated())
assert.Equal(t, createResourceList(5, 10), gqm.GetQuotaInfoByName("2").GetGuaranteed())
// check parent info
// the parent should guarantee children min, and keep resource requested already
assert.Equal(t, createResourceList(5, 15), gqm.GetQuotaInfoByName("1").GetAllocated())
assert.Equal(t, createResourceList(5, 20), gqm.GetQuotaInfoByName("1").GetGuaranteed())
}

func TestGroupQuotaManager_OnPodUpdateUsedForGuarantee(t *testing.T) {
defer utilfeature.SetFeatureGateDuringTest(t, k8sfeature.DefaultFeatureGate, features.ElasticQuotaGuaranteeUsage, true)()
gqm := NewGroupQuotaManagerForTest()
Expand Down Expand Up @@ -2039,7 +2218,7 @@ func TestUpdateQuotaInternalNoLock(t *testing.T) {
assert.Equal(t, createResourceList(25, 25), gqm.GetQuotaInfoByName("1").GetGuaranteed())
}

func TestUpdateQuotaInternalNoLock_ParenstSelf(t *testing.T) {
func TestUpdateQuotaInternalNoLock_ParentsSelf(t *testing.T) {
gqm := NewGroupQuotaManagerForTest()
gqm.scaleMinQuotaEnabled = true
gqm.UpdateClusterTotalResource(createResourceList(50, 50))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ func (qt *quotaTree) iterationForRedistribution(totalRes, totalSharedWeight int6
// if totalSharedWeight is not larger than 0, no need to iterate anymore.
return
}

needAdjustQuotaNodes := make([]*quotaNode, 0)
toPartitionResource, needAdjustTotalSharedWeight := int64(0), int64(0)
for _, node := range nodes {
Expand Down
Loading