Skip to content

Commit 607e4fb

Browse files
committed
configobserver: add minimumkubeletversion
Signed-off-by: Peter Hunt <[email protected]>
1 parent 218530f commit 607e4fb

File tree

4 files changed

+198
-0
lines changed

4 files changed

+198
-0
lines changed

pkg/operator/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func NewConfigObserver(operatorClient v1helpers.StaticPodOperatorClient, kubeInf
157157
),
158158
},
159159
),
160+
node.NewMinimumKubeletVersionObserver(featureGateAccessor),
160161
proxy.NewProxyObserveFunc([]string{"targetconfigcontroller", "proxy"}),
161162
images.ObserveInternalRegistryHostname,
162163
images.ObserveExternalRegistryHostnames,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package node
2+
3+
import (
4+
"k8s.io/client-go/tools/cache"
5+
6+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
7+
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
8+
)
9+
10+
type testLister struct {
11+
nodeLister configlistersv1.NodeLister
12+
}
13+
14+
func (l testLister) NodeLister() configlistersv1.NodeLister {
15+
return l.nodeLister
16+
}
17+
18+
func (l testLister) ResourceSyncer() resourcesynccontroller.ResourceSyncer {
19+
return nil
20+
}
21+
22+
func (l testLister) PreRunHasSynced() []cache.InformerSynced {
23+
return nil
24+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package node
2+
3+
import (
4+
configv1 "github.com/openshift/api/config/v1"
5+
"github.com/openshift/api/features"
6+
"github.com/openshift/library-go/pkg/operator/configobserver"
7+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
8+
"github.com/openshift/library-go/pkg/operator/events"
9+
apierrors "k8s.io/apimachinery/pkg/api/errors"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/klog/v2"
12+
)
13+
14+
var minimumKubeletVersionConfigPath = "minimumKubeletVersion"
15+
16+
type minimumKubeletVersionObserver struct {
17+
featureGateAccessor featuregates.FeatureGateAccess
18+
}
19+
20+
func NewMinimumKubeletVersionObserver(featureGateAccessor featuregates.FeatureGateAccess) configobserver.ObserveConfigFunc {
21+
return (&minimumKubeletVersionObserver{
22+
featureGateAccessor: featureGateAccessor,
23+
}).ObserveMinimumKubeletVersion
24+
}
25+
26+
// ObserveKubeletMinimumVersion watches the node configuration and generates the minimumKubeletVersion
27+
func (o *minimumKubeletVersionObserver) ObserveMinimumKubeletVersion(genericListers configobserver.Listers, _ events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, errs []error) {
28+
defer func() {
29+
// Prune the observed config so that it only contains minimumKubeletVersion field.
30+
ret = configobserver.Pruned(ret, []string{minimumKubeletVersionConfigPath})
31+
}()
32+
33+
if !o.featureGateAccessor.AreInitialFeatureGatesObserved() {
34+
// if we haven't observed featuregates yet, return the existing
35+
return existingConfig, nil
36+
}
37+
38+
featureGates, err := o.featureGateAccessor.CurrentFeatureGates()
39+
if err != nil {
40+
return existingConfig, append(errs, err)
41+
}
42+
43+
if !featureGates.Enabled(features.FeatureGateMinimumKubeletVersion) {
44+
return existingConfig, nil
45+
}
46+
47+
nodeLister := genericListers.(NodeLister)
48+
configNode, err := nodeLister.NodeLister().Get("cluster")
49+
// we got an error so without the node object we are not able to determine minimumKubeletVersion
50+
if err != nil {
51+
// if config/v1/node/cluster object is not found, that can be treated as a non-error case, but raise a warning
52+
if apierrors.IsNotFound(err) {
53+
klog.Warningf("ObserveMinimumKubeletVersion: nodes.%s/cluster not found", configv1.GroupName)
54+
} else {
55+
errs = append(errs, err)
56+
}
57+
return existingConfig, errs
58+
}
59+
60+
ret = map[string]interface{}{}
61+
if configNode.Spec.MinimumKubeletVersion == "" {
62+
// in case minimum kubelet version is not set on cluster
63+
// return empty set of configs, this helps to unset the config
64+
// values related to the minimumKubeletVersion.
65+
// Also, ensures that this observer doesn't break cluster upgrades/downgrades
66+
return map[string]interface{}{}, errs
67+
}
68+
69+
if err := unstructured.SetNestedField(ret, configNode.Spec.MinimumKubeletVersion, minimumKubeletVersionConfigPath); err != nil {
70+
return existingConfig, append(errs, err)
71+
}
72+
73+
return ret, errs
74+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package node
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/go-cmp/cmp"
8+
configv1 "github.com/openshift/api/config/v1"
9+
"github.com/openshift/api/features"
10+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
11+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
12+
"github.com/openshift/library-go/pkg/operator/events"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/client-go/tools/cache"
15+
clocktesting "k8s.io/utils/clock/testing"
16+
)
17+
18+
func TestObserveKubeletMinimumVersion(t *testing.T) {
19+
type Test struct {
20+
name string
21+
existingConfig map[string]interface{}
22+
expectedObservedConfig map[string]interface{}
23+
minimumKubeletVersion string
24+
featureOn bool
25+
}
26+
tests := []Test{
27+
{
28+
name: "feature off",
29+
existingConfig: map[string]interface{}{},
30+
expectedObservedConfig: map[string]interface{}{},
31+
minimumKubeletVersion: "1.30.0",
32+
featureOn: false,
33+
},
34+
{
35+
name: "empty minimumKubeletVersion",
36+
expectedObservedConfig: map[string]interface{}{},
37+
minimumKubeletVersion: "",
38+
featureOn: true,
39+
},
40+
{
41+
name: "set minimumKubeletVersion",
42+
expectedObservedConfig: map[string]interface{}{
43+
"minimumKubeletVersion": string("1.30.0"),
44+
},
45+
minimumKubeletVersion: "1.30.0",
46+
featureOn: true,
47+
},
48+
{
49+
name: "existing minimumKubeletVersion",
50+
expectedObservedConfig: map[string]interface{}{
51+
"minimumKubeletVersion": string("1.30.0"),
52+
},
53+
existingConfig: map[string]interface{}{
54+
"minimumKubeletVersion": string("1.29.0"),
55+
},
56+
minimumKubeletVersion: "1.30.0",
57+
featureOn: true,
58+
},
59+
{
60+
name: "existing minimumKubeletVersion unset",
61+
expectedObservedConfig: map[string]interface{}{},
62+
existingConfig: map[string]interface{}{
63+
"minimumKubeletVersion": string("1.29.0"),
64+
},
65+
minimumKubeletVersion: "",
66+
featureOn: true,
67+
},
68+
}
69+
for _, test := range tests {
70+
t.Run(test.name, func(t *testing.T) {
71+
// test data
72+
eventRecorder := events.NewInMemoryRecorder("", clocktesting.NewFakePassiveClock(time.Now()))
73+
configNodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
74+
configNodeIndexer.Add(&configv1.Node{
75+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
76+
Spec: configv1.NodeSpec{MinimumKubeletVersion: test.minimumKubeletVersion},
77+
})
78+
listers := testLister{
79+
nodeLister: configlistersv1.NewNodeLister(configNodeIndexer),
80+
}
81+
82+
fg := featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{features.FeatureGateMinimumKubeletVersion}, []configv1.FeatureGateName{})
83+
if !test.featureOn {
84+
fg = featuregates.NewHardcodedFeatureGateAccess([]configv1.FeatureGateName{}, []configv1.FeatureGateName{features.FeatureGateMinimumKubeletVersion})
85+
}
86+
87+
// act
88+
actualObservedConfig, errs := NewMinimumKubeletVersionObserver(fg)(listers, eventRecorder, test.existingConfig)
89+
90+
// validate
91+
if len(errs) > 0 {
92+
t.Fatal(errs)
93+
}
94+
if diff := cmp.Diff(test.expectedObservedConfig, actualObservedConfig); diff != "" {
95+
t.Fatalf("unexpected configuration, diff = %v", diff)
96+
}
97+
})
98+
}
99+
}

0 commit comments

Comments
 (0)