diff --git a/cluster-autoscaler/provisioningrequest/orchestrator/orchestrator_test.go b/cluster-autoscaler/provisioningrequest/orchestrator/orchestrator_test.go index 2cb99b21bd75..44625effbb8e 100644 --- a/cluster-autoscaler/provisioningrequest/orchestrator/orchestrator_test.go +++ b/cluster-autoscaler/provisioningrequest/orchestrator/orchestrator_test.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "testing" - "time" "github.com/stretchr/testify/assert" v1 "k8s.io/api/apps/v1" @@ -43,6 +42,9 @@ import ( ) func TestScaleUp(t *testing.T) { + // Set up a cluster with 200 nodes: + // - 100 nodes with high cpu, low memory + // - 100 nodes with high memory, low cpu allNodes := []*apiv1.Node{} for i := 0; i < 100; i++ { name := fmt.Sprintf("test-cpu-node-%d", i) @@ -54,13 +56,68 @@ func TestScaleUp(t *testing.T) { node := BuildTestNode(name, 1, 1000) allNodes = append(allNodes, node) } - newCpuProvReq := provreqwrapper.BuildTestProvisioningRequest("ns", "newCpuProvReq", "5m", "5", "", int32(100), false, time.Now(), v1beta1.ProvisioningClassCheckCapacity) - newMemProvReq := provreqwrapper.BuildTestProvisioningRequest("ns", "newMemProvReq", "1m", "100", "", int32(100), false, time.Now(), v1beta1.ProvisioningClassCheckCapacity) - bookedCapacityProvReq := provreqwrapper.BuildTestProvisioningRequest("ns", "bookedCapacity", "1m", "200", "", int32(100), false, time.Now(), v1beta1.ProvisioningClassCheckCapacity) + + // Active check capacity requests. + newCheckCapacityCpuProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "newCheckCapacityCpuProvReq", + CPU: "5m", + Memory: "5", + PodCount: int32(100), + Class: v1beta1.ProvisioningClassCheckCapacity, + }) + + newCheckCapacityMemProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "newCheckCapacityMemProvReq", + CPU: "1m", + Memory: "100", + PodCount: int32(100), + Class: v1beta1.ProvisioningClassCheckCapacity, + }) + + // Active atomic scale up request. + atomicScaleUpProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "atomicScaleUpProvReq", + CPU: "1", + Memory: "1", + PodCount: int32(5), + Class: v1beta1.ProvisioningClassAtomicScaleUp, + }) + + // Already provisioned provisioning request - capacity should be booked before processing a new request. + bookedCapacityProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "bookedCapacityProvReq", + CPU: "1m", + Memory: "200", + PodCount: int32(100), + Class: v1beta1.ProvisioningClassCheckCapacity, + }) bookedCapacityProvReq.SetConditions([]metav1.Condition{{Type: v1beta1.Provisioned, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now()}}) - expiredProvReq := provreqwrapper.BuildTestProvisioningRequest("ns", "bookedCapacity", "1m", "200", "", int32(100), false, time.Now(), v1beta1.ProvisioningClassCheckCapacity) + + // Expired provisioning request - should be ignored. + expiredProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "expiredProvReq", + CPU: "1m", + Memory: "200", + PodCount: int32(100), + Class: v1beta1.ProvisioningClassCheckCapacity, + }) expiredProvReq.SetConditions([]metav1.Condition{{Type: v1beta1.BookingExpired, Status: metav1.ConditionTrue, LastTransitionTime: metav1.Now()}}) - differentProvReqClass := provreqwrapper.BuildTestProvisioningRequest("ns", "differentProvReqClass", "1", "1", "", int32(5), false, time.Now(), v1beta1.ProvisioningClassAtomicScaleUp) + + // Unsupported provisioning request - should be ignored. + unsupportedProvReq := provreqwrapper.BuildValidTestProvisioningRequestFromOptions( + provreqwrapper.TestProvReqOptions{ + Name: "unsupportedProvReq", + CPU: "1", + Memory: "1", + PodCount: int32(5), + Class: "very much unsupported", + }) + testCases := []struct { name string provReqs []*provreqwrapper.ProvisioningRequest @@ -74,27 +131,27 @@ func TestScaleUp(t *testing.T) { scaleUpResult: status.ScaleUpNotTried, }, { - name: "one ProvisioningRequest", - provReqs: []*provreqwrapper.ProvisioningRequest{newCpuProvReq}, - provReqToScaleUp: newCpuProvReq, + name: "one ProvisioningRequest of check capacity class", + provReqs: []*provreqwrapper.ProvisioningRequest{newCheckCapacityCpuProvReq}, + provReqToScaleUp: newCheckCapacityCpuProvReq, scaleUpResult: status.ScaleUpSuccessful, }, { name: "capacity in the cluster is booked", - provReqs: []*provreqwrapper.ProvisioningRequest{newMemProvReq, bookedCapacityProvReq}, - provReqToScaleUp: newMemProvReq, + provReqs: []*provreqwrapper.ProvisioningRequest{newCheckCapacityMemProvReq, bookedCapacityProvReq}, + provReqToScaleUp: newCheckCapacityMemProvReq, scaleUpResult: status.ScaleUpNoOptionsAvailable, }, { - name: "pods from different ProvisioningRequest class", - provReqs: []*provreqwrapper.ProvisioningRequest{newCpuProvReq, bookedCapacityProvReq, differentProvReqClass}, - provReqToScaleUp: differentProvReqClass, + name: "unsupported ProvisioningRequest is ignored", + provReqs: []*provreqwrapper.ProvisioningRequest{newCheckCapacityCpuProvReq, bookedCapacityProvReq, atomicScaleUpProvReq, unsupportedProvReq}, + provReqToScaleUp: unsupportedProvReq, scaleUpResult: status.ScaleUpNotTried, }, { - name: "some capacity is booked, succesfull ScaleUp", - provReqs: []*provreqwrapper.ProvisioningRequest{newCpuProvReq, bookedCapacityProvReq, differentProvReqClass}, - provReqToScaleUp: newCpuProvReq, + name: "some capacity is pre-booked, successful capacity check", + provReqs: []*provreqwrapper.ProvisioningRequest{newCheckCapacityCpuProvReq, bookedCapacityProvReq, atomicScaleUpProvReq}, + provReqToScaleUp: newCheckCapacityCpuProvReq, scaleUpResult: status.ScaleUpSuccessful, }, } @@ -106,9 +163,11 @@ func TestScaleUp(t *testing.T) { provider := testprovider.NewTestCloudProvider(nil, nil) autoscalingContext, err := NewScaleTestAutoscalingContext(config.AutoscalingOptions{}, &fake.Clientset{}, nil, provider, nil, nil) assert.NoError(t, err) + clustersnapshot.InitializeClusterSnapshotOrDie(t, autoscalingContext.ClusterSnapshot, allNodes, nil) prPods, err := pods.PodsForProvisioningRequest(tc.provReqToScaleUp) assert.NoError(t, err) + client := provreqclient.NewFakeProvisioningRequestClient(context.Background(), t, tc.provReqs...) orchestrator := &provReqOrchestrator{ client: client, diff --git a/cluster-autoscaler/provisioningrequest/provreqwrapper/testutils.go b/cluster-autoscaler/provisioningrequest/provreqwrapper/testutils.go index f0d356122277..1954978e3f52 100644 --- a/cluster-autoscaler/provisioningrequest/provreqwrapper/testutils.go +++ b/cluster-autoscaler/provisioningrequest/provreqwrapper/testutils.go @@ -27,9 +27,39 @@ import ( "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1beta1" ) +// TestProvReqOptions is a helper struct to make constructing test ProvisioningRequest object easier. +type TestProvReqOptions struct { + Namespace string + Name string + CPU string + Memory string + GPU string + PodCount int32 + AntiAffinity bool + CreationTimestamp time.Time + Class string +} + +func applyDefaults(o TestProvReqOptions) TestProvReqOptions { + if o.Namespace == "" { + o.Namespace = "default" + } + if o.CreationTimestamp.IsZero() { + o.CreationTimestamp = time.Now() + } + return o +} + +// BuildValidTestProvisioningRequestFromOptions fills in commonly omitted fields to generate a valid ProvisioningRequest object. +// Simplifies test code. +func BuildValidTestProvisioningRequestFromOptions(o TestProvReqOptions) *ProvisioningRequest { + o = applyDefaults(o) + return BuildTestProvisioningRequest(o.Namespace, o.Name, o.CPU, o.Memory, o.GPU, o.PodCount, o.AntiAffinity, o.CreationTimestamp, o.Class) +} + // BuildTestProvisioningRequest builds ProvisioningRequest wrapper. func BuildTestProvisioningRequest(namespace, name, cpu, memory, gpu string, podCount int32, - antiAffinity bool, creationTimestamp time.Time, provisioningRequestClass string) *ProvisioningRequest { + antiAffinity bool, creationTimestamp time.Time, class string) *ProvisioningRequest { gpuResource := resource.Quantity{} tolerations := []apiv1.Toleration{} if len(gpu) > 0 { @@ -64,7 +94,7 @@ func BuildTestProvisioningRequest(namespace, name, cpu, memory, gpu string, podC CreationTimestamp: v1.NewTime(creationTimestamp), }, Spec: v1beta1.ProvisioningRequestSpec{ - ProvisioningClassName: provisioningRequestClass, + ProvisioningClassName: class, PodSets: []v1beta1.PodSet{ { PodTemplateRef: v1beta1.Reference{Name: fmt.Sprintf("%s-template-name", name)},