Skip to content

Commit

Permalink
configobserver: add minimumkubeletversion
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Hunt <[email protected]>
  • Loading branch information
haircommander committed Nov 26, 2024
1 parent 93e98b5 commit 49beb0d
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func NewConfigObserver(operatorClient v1helpers.StaticPodOperatorClient, kubeInf
),
},
),
node.NewMinimumKubeletVersionObserver(featureGateAccessor),
proxy.NewProxyObserveFunc([]string{"targetconfigcontroller", "proxy"}),
images.ObserveInternalRegistryHostname,
images.ObserveExternalRegistryHostnames,
Expand Down
9 changes: 9 additions & 0 deletions pkg/operator/configobservation/node/listers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package node

import (
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
)

type NodeLister interface {
NodeLister() configlistersv1.NodeLister
}
24 changes: 24 additions & 0 deletions pkg/operator/configobservation/node/listers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package node

import (
"k8s.io/client-go/tools/cache"

configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
)

type testLister struct {
nodeLister configlistersv1.NodeLister
}

func (l testLister) NodeLister() configlistersv1.NodeLister {
return l.nodeLister
}

func (l testLister) ResourceSyncer() resourcesynccontroller.ResourceSyncer {
return nil
}

func (l testLister) PreRunHasSynced() []cache.InformerSynced {
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package node

import (
configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/api/features"
"github.com/openshift/library-go/pkg/operator/configobserver"
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
"github.com/openshift/library-go/pkg/operator/events"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/klog/v2"
)

var minimumKubeletVersionConfigPath = "minimumKubeletVersion"

type minimumKubeletVersionObserver struct {
featureGateAccessor featuregates.FeatureGateAccess
}

func NewMinimumKubeletVersionObserver(featureGateAccessor featuregates.FeatureGateAccess) configobserver.ObserveConfigFunc {
return (&minimumKubeletVersionObserver{
featureGateAccessor: featureGateAccessor,
}).ObserveMinimumKubeletVersion
}

// ObserveKubeletMinimumVersion watches the node configuration and generates the minimumKubeletVersion
func (o *minimumKubeletVersionObserver) ObserveMinimumKubeletVersion(genericListers configobserver.Listers, _ events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, errs []error) {
if !o.featureGateAccessor.AreInitialFeatureGatesObserved() {
// if we haven't observed featuregates yet, return the existing
return existingConfig, nil
}

featureGates, err := o.featureGateAccessor.CurrentFeatureGates()
if err != nil {
return existingConfig, append(errs, err)
}

if !featureGates.Enabled(features.FeatureGateMinimumKubeletVersion) {
return existingConfig, nil
}

defer func() {
// Prune the observed config so that it only contains minimumKubeletVersion field.
ret = configobserver.Pruned(ret, []string{minimumKubeletVersionConfigPath})
}()
nodeLister := genericListers.(NodeLister)
configNode, err := nodeLister.NodeLister().Get("cluster")
// we got an error so without the node object we are not able to determine minimumKubeletVersion
if err != nil {
// if config/v1/node/cluster object is not found, that can be treated as a non-error case, but raise a warning
if apierrors.IsNotFound(err) {
klog.Warningf("ObserveMinimumKubeletVersion: nodes.%s/cluster not found", configv1.GroupName)
} else {
errs = append(errs, err)
}
return existingConfig, errs
}

if configNode.Spec.MinimumKubeletVersion == "" {
// in case minimum kubelet version is not set on cluster
// return empty set of configs, this helps to unset the config
// values related to the minimumKubeletVersion.
// Also, ensures that this observer doesn't break cluster upgrades/downgrades
return map[string]interface{}{}, errs
}

ret = map[string]interface{}{}
if err := unstructured.SetNestedField(ret, configNode.Spec.MinimumKubeletVersion, minimumKubeletVersionConfigPath); err != nil {
errs = append(errs, err)
}

return ret, errs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package node

import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/api/features"
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
"github.com/openshift/library-go/pkg/operator/events"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
clocktesting "k8s.io/utils/clock/testing"
)

func TestObserveKubeletMinimumVersion(t *testing.T) {
type Test struct {
name string
existingConfig map[string]interface{}
expectedObservedConfig map[string]interface{}
minimumKubeletVersion string
featureOn bool
}
tests := []Test{
{
name: "feature off",
existingConfig: map[string]interface{}{},
expectedObservedConfig: map[string]interface{}{},
minimumKubeletVersion: "1.30.0",
featureOn: false,
},
{
name: "empty minimumKubeletVersion",
expectedObservedConfig: map[string]interface{}{},
minimumKubeletVersion: "",
featureOn: true,
},
{
name: "set minimumKubeletVersion",
expectedObservedConfig: map[string]interface{}{
"minimumKubeletVersion": string("1.30.0"),
},
minimumKubeletVersion: "1.30.0",
featureOn: true,
},
{
name: "existing minimumKubeletVersion",
expectedObservedConfig: map[string]interface{}{
"minimumKubeletVersion": string("1.30.0"),
},
existingConfig: map[string]interface{}{
"minimumKubeletVersion": string("1.29.0"),
},
minimumKubeletVersion: "1.30.0",
featureOn: true,
},
{
name: "existing minimumKubeletVersion unset",
expectedObservedConfig: map[string]interface{}{},
existingConfig: map[string]interface{}{
"minimumKubeletVersion": string("1.29.0"),
},
minimumKubeletVersion: "",
featureOn: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// test data
eventRecorder := events.NewInMemoryRecorder("", clocktesting.NewFakePassiveClock(time.Now()))
configNodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
configNodeIndexer.Add(&configv1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
Spec: configv1.NodeSpec{MinimumKubeletVersion: test.minimumKubeletVersion},
})
listers := testLister{
nodeLister: configlistersv1.NewNodeLister(configNodeIndexer),
}

fg := featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{features.FeatureGateMinimumKubeletVersion}, []configv1.FeatureGateName{})
if !test.featureOn {
fg = featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{}, []configv1.FeatureGateName{features.FeatureGateMinimumKubeletVersion})
}

// act
actualObservedConfig, errs := NewMinimumKubeletVersionObserver(fg)(listers, eventRecorder, test.existingConfig)

// validate
if len(errs) > 0 {
t.Fatal(errs)
}
if diff := cmp.Diff(test.expectedObservedConfig, actualObservedConfig); diff != "" {
t.Fatalf("unexpected configuration, diff = %v", diff)
}
})
}
}

0 comments on commit 49beb0d

Please sign in to comment.