Skip to content

Commit

Permalink
update condition logic
Browse files Browse the repository at this point in the history
  • Loading branch information
yaroslava-serdiuk committed Jan 18, 2024
1 parent b0c2524 commit b493ccc
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ func (o *WrapperOrchestrator) ScaleUp(
daemonSets []*appsv1.DaemonSet,
nodeInfos map[string]*schedulerframework.NodeInfo,
) (*status.ScaleUpStatus, errors.AutoscalerError) {

defer func() { o.scaleUpRegularPods = !o.scaleUpRegularPods }()

provReqPods, regularPods := splitOut(unschedulablePods)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
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 orchestrator

2 changes: 1 addition & 1 deletion cluster-autoscaler/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module k8s.io/autoscaler/cluster-autoscaler

go 1.21
go 1.21.3

require (
cloud.google.com/go/compute/metadata v0.2.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,13 @@ type Detail string
// The following constants list all currently available Conditions Type values.
// See: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition
const (
// CapacityAvailable indicates that all of the requested resources were
// already available in the cluster.
CapacityAvailable string = "CapacityAvailable"
// CapacityFound indicates that all of the requested resources were
// fount in the cluster.
CapacityFound string = "CapacityFound"
// Expired indicates that the ProvisioningRequest had CapacityAvailable condition before
// and the reservation time is expired or the ProvisioningRequest had Pending condition before
// and expiration time is expired.
Expired string = "Expired"
// Pending indicates that no capacity for ProvisioningRequest was found in the cluster
// and ClusterAutoscaler will try to find capacity later.
Pending string = "Pending"
// Provisioned indicates that all of the requested resources were created
// and are available in the cluster. CA will set this condition when the
// VM creation finishes successfully.
Expand Down
62 changes: 44 additions & 18 deletions cluster-autoscaler/provisioningrequest/checkcapacity/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,65 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/apis/autoscaling.x-k8s.io/v1beta1"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper"
"k8s.io/klog/v2"
)

const (
defaultReservationTime = 10 * time.Minute
defaultExpirationTime = 7 * 24 * time.Hour // 7 days
)

// HasBookCapacityCondition return if PR has BookCapacity condition
func HasBookCapacityCondition(pr *provreqwrapper.ProvisioningRequest) bool {
func bookCapacity(pr *provreqwrapper.ProvisioningRequest) bool {
if pr.V1Beta1().Spec.ProvisioningClassName != v1beta1.ProvisioningClassCheckCapacity {
return false
}
if pr.Conditions() == nil || len(pr.Conditions()) == 0 {
return false
}
condition := pr.Conditions()[len(pr.Conditions())-1]
if condition.Type == string(v1beta1.CapacityAvailable) && condition.Status == v1.ConditionTrue {
return true
book := false
for _, condition := range pr.Conditions() {
if checkConditionType(condition, v1beta1.Expired) || checkConditionType(condition, v1beta1.Failed) {
return false
}
if checkConditionType(condition, v1beta1.CapacityFound) {
book = true
}
}
return false
return book
}

func setCondition(pr *provreqwrapper.ProvisioningRequest, conditionType string, reason, message string) {
conditions := pr.Conditions()
conditions = []v1.Condition{
{
Type: conditionType,
Status: v1.ConditionTrue,
ObservedGeneration: pr.V1Beta1().GetObjectMeta().GetGeneration(),
LastTransitionTime: v1.Now(),
Reason: reason,
Message: message,
},
func setCondition(pr *provreqwrapper.ProvisioningRequest, conditionType string, conditionStatus v1.ConditionStatus, reason, message string) {
var newConditions []v1.Condition
newCondition := v1.Condition{
Type: conditionType,
Status: conditionStatus,
ObservedGeneration: pr.V1Beta1().GetObjectMeta().GetGeneration(),
LastTransitionTime: v1.Now(),
Reason: reason,
Message: message,
}
pr.SetConditions(conditions)
prevConditions := pr.Conditions()
switch conditionType {
case v1beta1.CapacityFound, v1beta1.Expired, v1beta1.Failed:
foundBefore := false
for _, condition := range prevConditions {
if condition.Type == conditionType {
foundBefore = true
newConditions = append(newConditions, newCondition)
} else {
newConditions = append(newConditions, condition)
}
}
if !foundBefore {
newConditions = append(prevConditions, newCondition)
}
default:
klog.Errorf("Unknown conditionType: %s", conditionType)
newConditions = prevConditions
}
pr.SetConditions(newConditions)
}

func checkConditionType(condition v1.Condition, conditionType string) bool {
return condition.Type == conditionType && condition.Status == v1.ConditionTrue
}
215 changes: 215 additions & 0 deletions cluster-autoscaler/provisioningrequest/checkcapacity/condition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
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 checkcapacity

import (
"testing"

v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/apis/autoscaling.x-k8s.io/v1beta1"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper"
)

func TestBookCapacity(t *testing.T) {
tests := []struct {
name string
prConditions []v1.Condition
want bool
}{
{
name: "Expired",
prConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
{
Type: v1beta1.Expired,
Status: v1.ConditionTrue,
},
},
want: false,
},
{
name: "Failed",
prConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
{
Type: v1beta1.Failed,
Status: v1.ConditionTrue,
},
},
want: false,
},
{
name: "empty conditions",
want: false,
},
{
name: "Capacity found and provisioned",
prConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
{
Type: v1beta1.Provisioned,
Status: v1.ConditionTrue,
},
},
want: true,
},
{
name: "Capacity is not found",
prConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionFalse,
},
},
want: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pr := provreqwrapper.NewV1Beta1ProvisioningRequest(
&v1beta1.ProvisioningRequest{
Spec: v1beta1.ProvisioningRequestSpec{
ProvisioningClassName: v1beta1.ProvisioningClassCheckCapacity,
},
Status: v1beta1.ProvisioningRequestStatus{
Conditions: test.prConditions,
},
}, nil)
got := bookCapacity(pr)
if got != test.want {
t.Errorf("Want: %v, got: %v", test.want, got)
}
})
}
}

func TestSetCondition(t *testing.T) {
tests := []struct {
name string
oldConditions []v1.Condition
newType string
newStatus v1.ConditionStatus
want []v1.Condition
}{
{
name: "CapacityFound added, empty conditions before",
newType: v1beta1.CapacityFound,
newStatus: v1.ConditionTrue,
want: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
},
},
{
name: "CapacityFound updated",
oldConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionFalse,
},
},
newType: v1beta1.CapacityFound,
newStatus: v1.ConditionTrue,
want: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
},
},
{
name: "Failed added, non-empty conditions before",
oldConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
},
newType: v1beta1.Failed,
newStatus: v1.ConditionTrue,
want: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
{
Type: v1beta1.Failed,
Status: v1.ConditionTrue,
},
},
},
{
name: "Expired, empty conditions before",
oldConditions: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
},
newType: v1beta1.Provisioned,
newStatus: v1.ConditionFalse,
want: []v1.Condition{
{
Type: v1beta1.CapacityFound,
Status: v1.ConditionTrue,
},
},
},
{
name: "Unknown type, conditions are not updated",
newType: v1beta1.Expired,
newStatus: v1.ConditionFalse,
want: []v1.Condition{
{
Type: v1beta1.Expired,
Status: v1.ConditionFalse,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pr := provreqwrapper.NewV1Beta1ProvisioningRequest(
&v1beta1.ProvisioningRequest{
Status: v1beta1.ProvisioningRequestStatus{
Conditions: test.oldConditions,
},
}, nil)
setCondition(pr, test.newType, test.newStatus, "", "")
got := pr.Conditions()
if len(got) > 2 || len(got) != len(test.want) || got[0].Type != test.want[0].Type || got[0].Status != test.want[0].Status {
t.Errorf("want %v, got: %v", test.want, got)
}
if len(got) == 2 {
if got[1].Type != test.want[1].Type || got[1].Status != test.want[1].Status {
t.Errorf("want %v, got: %v", test.want, got)
}
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/autoscaler/cluster-autoscaler/clusterstate"
"k8s.io/autoscaler/cluster-autoscaler/context"
"k8s.io/autoscaler/cluster-autoscaler/processors/status"
Expand Down Expand Up @@ -117,13 +118,13 @@ func (o *provReqOrchestrator) bookCapacity() error {
}
podsToCreate := []*apiv1.Pod{}
for _, provReq := range provReqs {
if HasBookCapacityCondition(provReq) {
if bookCapacity(provReq) {
pods, err := provreq_pods.PodsForProvisioningRequest(provReq)
if err != nil {
// ClusterAutoscaler was able to create pods before, so we shouldn't have error here.
// If there is an error, mark PR as invalid, because we won't be able to book capacity
// for it anyway.
setCondition(provReq, v1beta1.Failed, "Couldn't book capacity", fmt.Sprintf("couldn't create pods, err: %v", err))
setCondition(provReq, v1beta1.Failed, metav1.ConditionTrue, "Couldn't book capacity", fmt.Sprintf("couldn't create pods, err: %v", err))
continue
}
podsToCreate = append(podsToCreate, pods...)
Expand All @@ -147,10 +148,10 @@ func (o *provReqOrchestrator) scaleUp(unschedulablePods []*apiv1.Pod) (bool, err
}
st, _, err := o.injector.TrySchedulePods(o.context.ClusterSnapshot, unschedulablePods, scheduling.ScheduleAnywhere, true)
if len(st) < len(unschedulablePods) || err != nil {
setCondition(provReq, v1beta1.Pending, "Capacity is not found", "")
setCondition(provReq, v1beta1.CapacityFound, metav1.ConditionFalse, "Capacity is not found, CA will try to find later", "")
return false, err
}
setCondition(provReq, v1beta1.CapacityAvailable, "Capacity is found", "")
setCondition(provReq, v1beta1.CapacityFound, metav1.ConditionTrue, "Capacity is found", "")
return true, nil
}

Expand Down
Loading

0 comments on commit b493ccc

Please sign in to comment.