208 lines
9.8 KiB
Go
208 lines
9.8 KiB
Go
/*
|
|
Copyright 2024 The Kubernetes 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 provreq
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1"
|
|
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest"
|
|
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqclient"
|
|
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper"
|
|
clock "k8s.io/utils/clock/testing"
|
|
"k8s.io/utils/lru"
|
|
)
|
|
|
|
func TestProvisioningRequestPodsInjector(t *testing.T) {
|
|
now := time.Now()
|
|
minAgo := now.Add(-1 * time.Minute).Add(-1 * time.Second)
|
|
hourAgo := now.Add(-1 * time.Hour)
|
|
|
|
accepted := metav1.Condition{
|
|
Type: v1.Accepted,
|
|
Status: metav1.ConditionTrue,
|
|
LastTransitionTime: metav1.NewTime(minAgo),
|
|
}
|
|
failed := metav1.Condition{
|
|
Type: v1.Failed,
|
|
Status: metav1.ConditionTrue,
|
|
LastTransitionTime: metav1.NewTime(hourAgo),
|
|
}
|
|
provisioned := metav1.Condition{
|
|
Type: v1.Provisioned,
|
|
Status: metav1.ConditionTrue,
|
|
LastTransitionTime: metav1.NewTime(hourAgo),
|
|
}
|
|
notProvisioned := metav1.Condition{
|
|
Type: v1.Provisioned,
|
|
Status: metav1.ConditionFalse,
|
|
LastTransitionTime: metav1.NewTime(hourAgo),
|
|
}
|
|
unknownProvisioned := metav1.Condition{
|
|
Type: v1.Provisioned,
|
|
Status: metav1.ConditionUnknown,
|
|
LastTransitionTime: metav1.NewTime(hourAgo),
|
|
}
|
|
notProvisionedRecently := metav1.Condition{
|
|
Type: v1.Provisioned,
|
|
Status: metav1.ConditionFalse,
|
|
LastTransitionTime: metav1.NewTime(minAgo),
|
|
}
|
|
|
|
podsA := 10
|
|
newProvReqA := testProvisioningRequestWithCondition("new", podsA, v1.ProvisioningClassCheckCapacity)
|
|
newAcceptedProvReqA := testProvisioningRequestWithCondition("new-accepted", podsA, v1.ProvisioningClassCheckCapacity, accepted)
|
|
newProvReqAWithInstance := testProvisioningRequestWithCondition("new-instance", podsA, v1.ProvisioningClassCheckCapacity)
|
|
newProvReqAWithInstance.Spec.Parameters = map[string]v1.Parameter{
|
|
provisioningrequest.CheckCapacityProcessorInstanceKey: "test-instance",
|
|
}
|
|
newProvReqAPrefixed := testProvisioningRequestWithCondition("new-prefixed", podsA, "test-prefix.check-capacity.autoscaling.x-k8s.io")
|
|
|
|
podsB := 20
|
|
notProvisionedAcceptedProvReqB := testProvisioningRequestWithCondition("provisioned-false-B", podsB, v1.ProvisioningClassBestEffortAtomicScaleUp, notProvisioned, accepted)
|
|
provisionedAcceptedProvReqB := testProvisioningRequestWithCondition("provisioned-and-accepted", podsB, v1.ProvisioningClassBestEffortAtomicScaleUp, provisioned, accepted)
|
|
failedProvReq := testProvisioningRequestWithCondition("failed", podsA, v1.ProvisioningClassBestEffortAtomicScaleUp, failed)
|
|
notProvisionedRecentlyProvReqB := testProvisioningRequestWithCondition("provisioned-false-recently-B", podsB, v1.ProvisioningClassBestEffortAtomicScaleUp, notProvisionedRecently)
|
|
unknownProvisionedProvReqB := testProvisioningRequestWithCondition("provisioned-unknown-B", podsB, v1.ProvisioningClassBestEffortAtomicScaleUp, unknownProvisioned)
|
|
unknownClass := testProvisioningRequestWithCondition("new-accepted", podsA, "unknown-class", accepted)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
provReqs []*provreqwrapper.ProvisioningRequest
|
|
existingUnsUnschedulablePodCount int
|
|
checkCapacityBatchProcessing bool
|
|
checkCapacityProcessorInstance string
|
|
wantUnscheduledPodCount int
|
|
wantUpdatedConditionName string
|
|
}{
|
|
{
|
|
name: "New ProvisioningRequest, pods are injected and Accepted condition is added",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB},
|
|
wantUnscheduledPodCount: podsA,
|
|
wantUpdatedConditionName: newProvReqA.Name,
|
|
},
|
|
{
|
|
name: "New check capacity ProvisioningRequest with batch processing, pods are injected and Accepted condition is not added",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB},
|
|
checkCapacityBatchProcessing: true,
|
|
wantUnscheduledPodCount: podsA,
|
|
wantUpdatedConditionName: newProvReqA.Name,
|
|
},
|
|
{
|
|
name: "New ProvisioningRequest, pods are injected and Accepted condition is updated",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newAcceptedProvReqA, provisionedAcceptedProvReqB},
|
|
wantUnscheduledPodCount: podsA,
|
|
wantUpdatedConditionName: newAcceptedProvReqA.Name,
|
|
},
|
|
{
|
|
name: "New ProvisioningRequest with not matching custom prefix, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed},
|
|
},
|
|
{
|
|
name: "New ProvisioningRequest with not matching processor instance, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB},
|
|
checkCapacityProcessorInstance: "test-instance",
|
|
},
|
|
{
|
|
name: "New check capacity ProvisioningRequest with matching processor instance, pods are injected and Accepted condition is added",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAWithInstance, provisionedAcceptedProvReqB},
|
|
checkCapacityProcessorInstance: "test-instance",
|
|
wantUnscheduledPodCount: podsA,
|
|
wantUpdatedConditionName: newProvReqAWithInstance.Name,
|
|
},
|
|
{
|
|
name: "New ProvisioningRequest with not matching prefix, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB},
|
|
checkCapacityProcessorInstance: "test-prefix.",
|
|
},
|
|
{
|
|
name: "New check capacity ProvisioningRequest with matching prefix, pods are injected and Accepted condition is added",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed, provisionedAcceptedProvReqB},
|
|
checkCapacityProcessorInstance: "test-prefix.",
|
|
wantUnscheduledPodCount: podsA,
|
|
wantUpdatedConditionName: newProvReqAPrefixed.Name,
|
|
},
|
|
{
|
|
name: "Provisioned=False, pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{notProvisionedAcceptedProvReqB, failedProvReq},
|
|
wantUnscheduledPodCount: podsB,
|
|
wantUpdatedConditionName: notProvisionedAcceptedProvReqB.Name,
|
|
},
|
|
{
|
|
name: "Provisioned=True, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{provisionedAcceptedProvReqB, failedProvReq},
|
|
},
|
|
{
|
|
name: "Provisioned=False, ProvReq is backed off, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{notProvisionedRecentlyProvReqB},
|
|
},
|
|
{
|
|
name: "Provisioned=Unknown, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{unknownProvisionedProvReqB, failedProvReq},
|
|
},
|
|
{
|
|
name: "ProvisionedClass is unknown, no pods are injected",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{unknownClass, failedProvReq},
|
|
},
|
|
{
|
|
name: "Provisioned=False, pods are injected but unschedulable pod list is not overwriten",
|
|
provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA},
|
|
existingUnsUnschedulablePodCount: 50,
|
|
wantUnscheduledPodCount: podsA + 50,
|
|
wantUpdatedConditionName: newProvReqA.Name,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
client := provreqclient.NewFakeProvisioningRequestClient(context.Background(), t, tc.provReqs...)
|
|
backoffTime := lru.New(100)
|
|
backoffTime.Add(key(notProvisionedRecentlyProvReqB), 2*time.Minute)
|
|
injector := ProvisioningRequestPodsInjector{1 * time.Minute, 10 * time.Minute, backoffTime, clock.NewFakePassiveClock(now), client, now, tc.checkCapacityBatchProcessing, tc.checkCapacityProcessorInstance}
|
|
getUnscheduledPods, err := injector.Process(nil, provreqwrapper.BuildTestPods("ns", "pod", tc.existingUnsUnschedulablePodCount))
|
|
if err != nil {
|
|
t.Errorf("%s failed: injector.Process return error %v", tc.name, err)
|
|
}
|
|
if len(getUnscheduledPods) != tc.wantUnscheduledPodCount {
|
|
t.Errorf("%s failed: injector.Process return %d unscheduled pods, want %d", tc.name, len(getUnscheduledPods), tc.wantUnscheduledPodCount)
|
|
}
|
|
if tc.wantUpdatedConditionName == "" {
|
|
continue
|
|
}
|
|
pr, _ := client.ProvisioningRequestNoCache("ns", tc.wantUpdatedConditionName)
|
|
accepted := apimeta.FindStatusCondition(pr.Status.Conditions, v1.Accepted)
|
|
if tc.checkCapacityBatchProcessing {
|
|
if accepted != nil {
|
|
t.Errorf("%s: injector.Process updated accepted condition for ProvisioningRequest %s, but shouldn't for batch processing", tc.name, tc.wantUpdatedConditionName)
|
|
}
|
|
} else {
|
|
if accepted == nil || accepted.LastTransitionTime != metav1.NewTime(now) {
|
|
t.Errorf("%s: injector.Process hasn't update accepted condition for ProvisioningRequest %s", tc.name, tc.wantUpdatedConditionName)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func testProvisioningRequestWithCondition(name string, podCount int, class string, conditions ...metav1.Condition) *provreqwrapper.ProvisioningRequest {
|
|
pr := provreqwrapper.BuildTestProvisioningRequest("ns", name, "10", "100", "", int32(podCount), false, time.Now(), class)
|
|
pr.Status.Conditions = conditions
|
|
return pr
|
|
}
|