Skip to content

Commit

Permalink
MachinePool annotation for externally managed autoscaler
Browse files Browse the repository at this point in the history
  • Loading branch information
jackfrancis committed Oct 20, 2022
1 parent e575705 commit 1580c60
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 9 deletions.
6 changes: 6 additions & 0 deletions api/v1beta1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ const (
// any changes to the actual object because it is a dry run) and the topology controller
// will receive the resulting object.
TopologyDryRunAnnotation = "topology.cluster.x-k8s.io/dry-run"

// ReplicasManagedByExternalAutoscalerAnnotation is an annotation that indicates an external autoscaler manages infra scaling.
// The practical effect of this is that the capi "replica" count should be passively derived from the number of observed infra machines,
// instead of being a source of truth for eventual consistency.
// This annotation can be used to inform MachinePool status during in-progress scaling scenarios.
ReplicasManagedByExternalAutoscalerAnnotation = "cluster.x-k8s.io/replicas-managed-by-external-autoscaler"
)

const (
Expand Down
10 changes: 5 additions & 5 deletions cmd/clusterctl/client/tree/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const (

// GetMetaName returns the object meta name that should be used for the object in the presentation layer, if defined.
func GetMetaName(obj client.Object) string {
if val, ok := getAnnotation(obj, ObjectMetaNameAnnotation); ok {
if val, ok := getAnnotationValue(obj, ObjectMetaNameAnnotation); ok {
return val
}
return ""
Expand All @@ -85,15 +85,15 @@ func IsGroupObject(obj client.Object) bool {

// GetGroupItems returns the list of names for the objects included in a group object.
func GetGroupItems(obj client.Object) string {
if val, ok := getAnnotation(obj, GroupItemsAnnotation); ok {
if val, ok := getAnnotationValue(obj, GroupItemsAnnotation); ok {
return val
}
return ""
}

// GetZOrder return the zOrder of the object. Objects with no zOrder have a default zOrder of 0.
func GetZOrder(obj client.Object) int {
if val, ok := getAnnotation(obj, ObjectZOrderAnnotation); ok {
if val, ok := getAnnotationValue(obj, ObjectZOrderAnnotation); ok {
if zOrder, err := strconv.ParseInt(val, 10, 0); err == nil {
return int(zOrder)
}
Expand All @@ -118,7 +118,7 @@ func IsShowConditionsObject(obj client.Object) bool {
return false
}

func getAnnotation(obj client.Object, annotation string) (string, bool) {
func getAnnotationValue(obj client.Object, annotation string) (string, bool) {
if obj == nil {
return "", false
}
Expand All @@ -127,7 +127,7 @@ func getAnnotation(obj client.Object, annotation string) (string, bool) {
}

func getBoolAnnotation(obj client.Object, annotation string) (bool, bool) {
val, ok := getAnnotation(obj, annotation)
val, ok := getAnnotationValue(obj, annotation)
if ok {
if boolVal, err := strconv.ParseBool(val); err == nil {
return boolVal, true
Expand Down
20 changes: 16 additions & 4 deletions exp/internal/controllers/machinepool_controller_phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,26 @@ func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) {
mp.Status.SetTypedPhase(expv1.MachinePoolPhaseRunning)
}

// Set the phase to "scalingUp" if the infrastructure is scaling up.
// Set the appropriate phase in response to the MachinePool replica count being greater than the observed infrastructure replicas.
if mp.Status.InfrastructureReady && *mp.Spec.Replicas > mp.Status.ReadyReplicas {
mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingUp)
// If we are being managed by an external autoscaler and can't predict scaling direction, set to "Pending".
if annotations.ReplicasManagedByExternalAutoscaler(mp) {
mp.Status.SetTypedPhase(expv1.MachinePoolPhasePending)
} else {
// Set the phase to "ScalingUp" if we are actively scaling the infrastructure out.
mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingUp)
}
}

// Set the phase to "scalingDown" if the infrastructure is scaling down.
// Set the appropriate phase in response to the MachinePool replica count being less than the observed infrastructure replicas.
if mp.Status.InfrastructureReady && *mp.Spec.Replicas < mp.Status.ReadyReplicas {
mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingDown)
// If we are being managed by an external autoscaler and can't predict scaling direction, set to "Pending".
if annotations.ReplicasManagedByExternalAutoscaler(mp) {
mp.Status.SetTypedPhase(expv1.MachinePoolPhasePending)
} else {
// Set the phase to "ScalingDown" if we are actively scaling the infrastructure in.
mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScalingDown)
}
}

// Set the phase to "failed" if any of Status.FailureReason or Status.FailureMessage is not-nil.
Expand Down
17 changes: 17 additions & 0 deletions util/annotations/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func HasWithPrefix(prefix string, annotations map[string]string) bool {
return false
}

// ReplicasManagedByExternalAutoscaler returns true if the standard annotation for external autoscaler is present.
func ReplicasManagedByExternalAutoscaler(o metav1.Object) bool {
return hasTruthyAnnotation(o, clusterv1.ReplicasManagedByExternalAutoscalerAnnotation)
}

// AddAnnotations sets the desired annotations on the object and returns true if the annotations have changed.
func AddAnnotations(o metav1.Object, desired map[string]string) bool {
if len(desired) == 0 {
Expand Down Expand Up @@ -87,3 +92,15 @@ func hasAnnotation(o metav1.Object, annotation string) bool {
_, ok := annotations[annotation]
return ok
}

// hasTruthyAnnotation evaluates truthiness permissively, only return false if
// 1) the annotation isn't present at all
// 2) the annotation is explicitly set to "false".
// All other value permutations are ignored and considered valid.
// tl;dr We're looking for boolean-type annotations but respecting that some folks might want to explicitly indicate "false".
func hasTruthyAnnotation(o metav1.Object, annotation string) bool {
if val, ok := o.GetAnnotations()[annotation]; ok {
return val != "false"
}
return false
}
78 changes: 78 additions & 0 deletions util/annotations/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,81 @@ func TestAddAnnotations(t *testing.T) {
})
}
}

func TestHasTruthyAnnotation(t *testing.T) {
tests := []struct {
name string
obj metav1.Object
annotationKey string
expected bool
}{
{
name: "no val",
obj: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"cluster.x-k8s.io/replicas-managed-by-autoscaler": "",
},
},
Spec: corev1.NodeSpec{},
Status: corev1.NodeStatus{},
},
annotationKey: "cluster.x-k8s.io/replicas-managed-by-autoscaler",
expected: true,
},
{
name: "no val",
obj: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"cluster.x-k8s.io/replicas-managed-by-autoscaler": "true",
},
},
Spec: corev1.NodeSpec{},
Status: corev1.NodeStatus{},
},
annotationKey: "cluster.x-k8s.io/replicas-managed-by-autoscaler",
expected: true,
},
{
name: "no val",
obj: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"cluster.x-k8s.io/replicas-managed-by-autoscaler": "foo",
},
},
Spec: corev1.NodeSpec{},
Status: corev1.NodeStatus{},
},
annotationKey: "cluster.x-k8s.io/replicas-managed-by-autoscaler",
expected: true,
},
{
name: "no val",
obj: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"cluster.x-k8s.io/replicas-managed-by-autoscaler": "false",
},
},
Spec: corev1.NodeSpec{},
Status: corev1.NodeStatus{},
},
annotationKey: "cluster.x-k8s.io/replicas-managed-by-autoscaler",
expected: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
ret := hasTruthyAnnotation(tt.obj, tt.annotationKey)
if tt.expected {
g.Expect(ret).To(BeTrue())
} else {
g.Expect(ret).To(BeFalse())
}
})
}
}

0 comments on commit 1580c60

Please sign in to comment.