-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add autosizednodes reconciler
Introduce autosizednodes reconciler which watches aro cluster object feature flags for ReconcileAutoSizedNodes. When feature flag is present new KubeletConfig is created enabling the AutoSizingReserver feature which auto computes the system reserved for nodes.
- Loading branch information
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
pkg/operator/controllers/autosizednodes/autosizednodes_controller.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package autosizednodes | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/coreos/ignition/v2/config/util" | ||
mcv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" | ||
"github.com/sirupsen/logrus" | ||
kerrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/builder" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/predicate" | ||
|
||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" | ||
) | ||
|
||
type Reconciler struct { | ||
client client.Client | ||
|
||
log *logrus.Entry | ||
} | ||
|
||
const ( | ||
ControllerName = "AutoSizedNodes" | ||
|
||
ControllerEnabled = "aro.autosizednodes.enabled" | ||
configName = "dynamic-node" | ||
) | ||
|
||
func NewReconciler(log *logrus.Entry, mgr ctrl.Manager) *Reconciler { | ||
return &Reconciler{ | ||
client: mgr.GetClient(), | ||
|
||
log: log, | ||
} | ||
} | ||
|
||
func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { | ||
var aro arov1alpha1.Cluster | ||
var err error | ||
|
||
err = r.client.Get(ctx, request.NamespacedName, &aro) | ||
if err != nil { | ||
err = fmt.Errorf("unable to fetch aro cluster: %w", err) | ||
return ctrl.Result{}, client.IgnoreNotFound(err) | ||
} | ||
|
||
r.log.Infof("Config changed, autoSize: %t\n", aro.Spec.OperatorFlags.GetSimpleBoolean(ControllerEnabled)) | ||
|
||
// key is used to locate the object in the etcd | ||
key := types.NamespacedName{ | ||
Name: configName, | ||
} | ||
|
||
if !aro.Spec.OperatorFlags.GetSimpleBoolean(ControllerEnabled) { | ||
// defaults to deleting the config | ||
config := mcv1.KubeletConfig{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: configName, | ||
}, | ||
} | ||
err = r.client.Delete(ctx, &config, &client.DeleteOptions{}) | ||
if err != nil { | ||
err = fmt.Errorf("could not delete KubeletConfig: %w", err) | ||
} | ||
return ctrl.Result{}, client.IgnoreNotFound(err) | ||
} | ||
|
||
defaultConfig := makeConfig() | ||
|
||
var config mcv1.KubeletConfig | ||
err = r.client.Get(ctx, key, &config) | ||
if kerrors.IsNotFound(err) { | ||
// If config doesn't exist, create a new one | ||
err := r.client.Create(ctx, &defaultConfig, &client.CreateOptions{}) | ||
if err != nil { | ||
err = fmt.Errorf("could not create KubeletConfig: %w", err) | ||
} | ||
return ctrl.Result{}, err | ||
} | ||
if err != nil { | ||
// If error, return it (controller-runtime will requeue for a retry) | ||
return ctrl.Result{}, fmt.Errorf("could not fetch KubeletConfig: %w", err) | ||
} | ||
|
||
// If already exists, update the spec | ||
config.Spec = defaultConfig.Spec | ||
err = r.client.Update(ctx, &config, &client.UpdateOptions{}) | ||
if err != nil { | ||
err = fmt.Errorf("could not update KubeletConfig: %w", err) | ||
} | ||
return ctrl.Result{}, err | ||
} | ||
|
||
// SetupWithManager prepares the controller with info who to watch | ||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
clusterPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool { | ||
name := o.GetName() | ||
return strings.EqualFold(arov1alpha1.SingletonClusterName, name) | ||
}) | ||
|
||
b := ctrl.NewControllerManagedBy(mgr). | ||
For(&arov1alpha1.Cluster{}, builder.WithPredicates(clusterPredicate)) | ||
|
||
// Controller adds ControllerManagedBy to KubeletConfit created by this controller. | ||
// Any changes will trigger reconcile, but only for that config. | ||
return b. | ||
Named(ControllerName). | ||
Owns(&mcv1.KubeletConfig{}). | ||
Complete(r) | ||
} | ||
|
||
func makeConfig() mcv1.KubeletConfig { | ||
return mcv1.KubeletConfig{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: configName, | ||
}, | ||
Spec: mcv1.KubeletConfigSpec{ | ||
AutoSizingReserved: util.BoolToPtr(true), | ||
MachineConfigPoolSelector: &metav1.LabelSelector{ | ||
MatchLabels: map[string]string{ | ||
"pools.operator.machineconfiguration.openshift.io/worker": "", | ||
}, | ||
}, | ||
}, | ||
} | ||
} |
129 changes: 129 additions & 0 deletions
129
pkg/operator/controllers/autosizednodes/autosizednodes_controller_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package autosizednodes | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
import ( | ||
"context" | ||
"reflect" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/coreos/ignition/v2/config/util" | ||
"github.com/google/go-cmp/cmp" | ||
mcv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" | ||
"github.com/sirupsen/logrus" | ||
kerrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
// This "_" import is counterintuitive but is required to initialize the scheme | ||
// ARO unfortunately relies on implicit import and its side effect for this | ||
arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1" | ||
_ "github.com/Azure/ARO-RP/pkg/util/scheme" | ||
) | ||
|
||
func TestAutosizednodesReconciler(t *testing.T) { | ||
aro := func(autoSizeEnabled bool) *arov1alpha1.Cluster { | ||
return &arov1alpha1.Cluster{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "aro", | ||
Namespace: "openshift-azure-operator", | ||
}, | ||
Spec: arov1alpha1.ClusterSpec{ | ||
OperatorFlags: arov1alpha1.OperatorFlags{ | ||
ControllerEnabled: strconv.FormatBool(autoSizeEnabled), | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
emptyConfig := mcv1.KubeletConfig{} | ||
config := makeConfig() | ||
|
||
tests := []struct { | ||
name string | ||
wantGetErr error | ||
client client.Client | ||
wantConfig *mcv1.KubeletConfig | ||
}{ | ||
{ | ||
name: "is not needed", | ||
client: fake.NewClientBuilder().WithRuntimeObjects(aro(false)).Build(), | ||
wantConfig: &emptyConfig, | ||
wantGetErr: kerrors.NewNotFound(mcv1.Resource("kubeletconfigs"), "dynamic-node"), | ||
}, | ||
{ | ||
name: "is needed and not present already", | ||
client: fake.NewClientBuilder().WithRuntimeObjects(aro(true)).Build(), | ||
wantConfig: &config, | ||
wantGetErr: nil, | ||
}, | ||
{ | ||
name: "is needed and present already", | ||
client: fake.NewClientBuilder().WithRuntimeObjects(aro(true), &config).Build(), | ||
wantConfig: &config, | ||
}, | ||
{ | ||
name: "is not needed and is present", | ||
client: fake.NewClientBuilder().WithRuntimeObjects(aro(false), &config).Build(), | ||
wantConfig: &emptyConfig, | ||
wantGetErr: kerrors.NewNotFound(mcv1.Resource("kubeletconfigs"), "dynamic-node"), | ||
}, | ||
{ | ||
name: "is needed and config got modified", | ||
client: fake.NewClientBuilder().WithRuntimeObjects( | ||
aro(true), | ||
&mcv1.KubeletConfig{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: configName, | ||
}, | ||
Spec: mcv1.KubeletConfigSpec{ | ||
AutoSizingReserved: util.BoolToPtr(false), | ||
MachineConfigPoolSelector: &metav1.LabelSelector{ | ||
MatchLabels: map[string]string{ | ||
"pools.operator.machineconfiguration.openshift.io/worker": "", | ||
}, | ||
}, | ||
}, | ||
}).Build(), | ||
wantConfig: &config, | ||
wantGetErr: nil, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
ctx := context.Background() | ||
r := Reconciler{ | ||
client: test.client, | ||
log: logrus.NewEntry(logrus.StandardLogger()), | ||
} | ||
result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "openshift-azure-operator", Name: "aro"}}) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
key := types.NamespacedName{ | ||
Name: configName, | ||
} | ||
var c mcv1.KubeletConfig | ||
|
||
err = r.client.Get(ctx, key, &c) | ||
if err != nil && err.Error() != test.wantGetErr.Error() || err == nil && test.wantGetErr != nil { | ||
t.Error(err) | ||
} | ||
|
||
if !reflect.DeepEqual(test.wantConfig.Spec, c.Spec) { | ||
t.Error(cmp.Diff(test.wantConfig.Spec, c.Spec)) | ||
} | ||
|
||
if result != (ctrl.Result{}) { | ||
t.Error("reconcile returned an unexpected result") | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package autosizednodes | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
// autosizednodes monitors/creates/removes "dynamic-node" KubeletConfig | ||
// that tells machine-config-operator to turn on auto sized nodes feature | ||
// the code that is executed by the mco: | ||
// - https://github.com/openshift/machine-config-operator/blob/fbc4d8e46a7746442f4de3651113d2181d458b12/templates/common/_base/files/kubelet-auto-sizing.yaml |