diff --git a/pkg/operator/status.go b/pkg/operator/status.go index df77272306..054609bf01 100644 --- a/pkg/operator/status.go +++ b/pkg/operator/status.go @@ -6,7 +6,6 @@ import ( "fmt" "reflect" "sort" - "strconv" "strings" "time" @@ -255,13 +254,6 @@ func (optr *Operator) syncDegradedStatus(co *configv1.ClusterOperator, ierr sync cov1helpers.SetStatusCondition(&co.Status.Conditions, coDegradedCondition, clock.RealClock{}) } -const ( - skewUnchecked = "KubeletSkewUnchecked" - skewSupported = "KubeletSkewSupported" - skewUnsupported = "KubeletSkewUnsupported" - skewPresent = "KubeletSkewPresent" -) - // syncUpgradeableStatus applies the new condition to the mco's ClusterOperator object. func (optr *Operator) syncUpgradeableStatus(co *configv1.ClusterOperator) error { @@ -289,13 +281,8 @@ func (optr *Operator) syncUpgradeableStatus(co *configv1.ClusterOperator) error coStatusCondition.Reason = "ClusterOnCgroupV1" coStatusCondition.Message = "Cluster is using deprecated cgroup v1 and is not upgradable. Please update the `CgroupMode` in the `nodes.config.openshift.io` object to 'v2'. Once upgraded, the cluster cannot be changed back to cgroup v1" } - var updating, degraded, interrupted bool + var degraded, interrupted bool for _, pool := range pools { - // collect updating status but continue to check each pool to see if any pool is degraded - if isPoolStatusConditionTrue(pool, mcfgv1.MachineConfigPoolUpdating) { - updating = true - } - interrupted = isPoolStatusConditionTrue(pool, mcfgv1.MachineConfigPoolBuildInterrupted) degraded = isPoolStatusConditionTrue(pool, mcfgv1.MachineConfigPoolDegraded) @@ -315,39 +302,6 @@ func (optr *Operator) syncUpgradeableStatus(co *configv1.ClusterOperator) error } } - // don't overwrite status if updating or degraded - if !updating && !degraded && !interrupted { - skewStatus, status, err := optr.isKubeletSkewSupported(pools) - if err != nil { - klog.Errorf("Error checking version skew: %v, kubelet skew status: %v, status reason: %v, status message: %v", err, skewStatus, status.Reason, status.Message) - coStatusCondition.Reason = status.Reason - coStatusCondition.Message = status.Message - cov1helpers.SetStatusCondition(&co.Status.Conditions, coStatusCondition, clock.RealClock{}) - } - switch skewStatus { - case skewUnchecked: - coStatusCondition.Reason = status.Reason - coStatusCondition.Message = status.Message - cov1helpers.SetStatusCondition(&co.Status.Conditions, coStatusCondition, clock.RealClock{}) - case skewUnsupported: - coStatusCondition.Reason = status.Reason - coStatusCondition.Message = status.Message - mcoObjectRef := &corev1.ObjectReference{ - Kind: co.Kind, - Name: co.Name, - Namespace: co.Namespace, - UID: co.GetUID(), - } - klog.Infof("kubelet skew status: %v, status reason: %v", skewStatus, status.Reason) - optr.eventRecorder.Eventf(mcoObjectRef, corev1.EventTypeWarning, coStatusCondition.Reason, coStatusCondition.Message) - cov1helpers.SetStatusCondition(&co.Status.Conditions, coStatusCondition, clock.RealClock{}) - case skewPresent: - coStatusCondition.Reason = status.Reason - coStatusCondition.Message = status.Message - klog.Infof("kubelet skew status: %v, status reason: %v", skewStatus, status.Reason) - cov1helpers.SetStatusCondition(&co.Status.Conditions, coStatusCondition, clock.RealClock{}) - } - } cov1helpers.SetStatusCondition(&co.Status.Conditions, coStatusCondition, clock.RealClock{}) return nil } @@ -519,85 +473,6 @@ func (optr *Operator) cfeEvalCgroupsV1() (bool, error) { return nodeClusterConfig.Spec.CgroupMode == configv1.CgroupModeV1, nil } -// isKubeletSkewSupported checks the version skew of kube-apiserver and node kubelet version. -// Returns the skew status. version skew > 2 is not supported. -func (optr *Operator) isKubeletSkewSupported(pools []*mcfgv1.MachineConfigPool) (skewStatus string, coStatus configv1.ClusterOperatorStatusCondition, err error) { - coStatus = configv1.ClusterOperatorStatusCondition{} - kubeAPIServerStatus, err := optr.clusterOperatorLister.Get("kube-apiserver") - if err != nil { - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when checking kubelet version skew: %v", err) - return skewUnchecked, coStatus, err - } - // looks like - // - name: kube-apiserver - // version: 1.21.0-rc.0 - kubeAPIServerVersion := "" - for _, version := range kubeAPIServerStatus.Status.Versions { - if version.Name != "kube-apiserver" { - continue - } - kubeAPIServerVersion = version.Version - break - } - if kubeAPIServerVersion == "" { - err = fmt.Errorf("kube-apiserver does not yet have a version") - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when checking kubelet version skew: %v", err.Error()) - return skewUnchecked, coStatus, err - } - kubeAPIServerMinorVersion, err := getMinorKubeletVersion(kubeAPIServerVersion) - if err != nil { - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when checking kubelet version skew: %v", err) - return skewUnchecked, coStatus, err - } - var ( - lastError error - kubeletVersion string - ) - nodes, err := optr.GetAllManagedNodes(pools) - if err != nil { - err = fmt.Errorf("getting all managed nodes failed: %w", err) - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when getting all the managed nodes: %v", err.Error()) - } - for _, node := range nodes { - // looks like kubeletVersion: v1.21.0-rc.0+6143dea - kubeletVersion = node.Status.NodeInfo.KubeletVersion - if kubeletVersion == "" { - continue - } - nodeMinorVersion, err := getMinorKubeletVersion(kubeletVersion) - if err != nil { - lastError = err - continue - } - if nodeMinorVersion+2 < kubeAPIServerMinorVersion { - coStatus.Reason = skewUnsupported - coStatus.Message = fmt.Sprintf("One or more nodes have an unsupported kubelet version skew. Please see `oc get nodes` for details and upgrade all nodes so that they have a kubelet version of at least %v.", getMinimalSkewSupportNodeVersion(kubeAPIServerVersion)) - return skewUnsupported, coStatus, nil - } - if nodeMinorVersion+2 == kubeAPIServerMinorVersion { - coStatus.Reason = skewPresent - coStatus.Message = fmt.Sprintf("Current kubelet version %v will not be supported by newer kube-apiserver. Please upgrade the kubelet first if plan to upgrade the kube-apiserver", kubeletVersion) - return skewPresent, coStatus, nil - } - } - if kubeletVersion == "" { - err = fmt.Errorf("kubelet does not yet have a version") - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when checking kubelet version skew: %v", err.Error()) - return skewUnchecked, coStatus, err - } - if lastError != nil { - coStatus.Reason = skewUnchecked - coStatus.Message = fmt.Sprintf("An error occurred when checking kubelet version skew: %v", err) - return skewUnchecked, coStatus, lastError - } - return skewSupported, coStatus, nil -} - // GetAllManagedNodes returns the nodes managed by MCO func (optr *Operator) GetAllManagedNodes(pools []*mcfgv1.MachineConfigPool) ([]*corev1.Node, error) { nodes := []*corev1.Node{} @@ -615,40 +490,6 @@ func (optr *Operator) GetAllManagedNodes(pools []*mcfgv1.MachineConfigPool) ([]* return nodes, nil } -// getMinorKubeletVersion parses the minor version number of kubelet -func getMinorKubeletVersion(version string) (int, error) { - tokens := strings.Split(version, ".") - if len(tokens) < 2 { - return 0, fmt.Errorf("incorrect version syntax: %q", version) - } - minorVersion, err := strconv.ParseInt(tokens[1], 10, 32) - if err != nil { - return 0, err - } - return int(minorVersion), nil -} - -// getMinimalSkewSupportNodeVersion returns the minimal supported node kubelet version. -func getMinimalSkewSupportNodeVersion(version string) string { - // drop the pre-release and commit hash - idx := strings.Index(version, "-") - if idx >= 0 { - version = version[:idx] - } - - idx = strings.Index(version, "+") - if idx >= 0 { - version = version[:idx] - } - - tokens := strings.Split(version, ".") - if minorVersion, err := strconv.ParseInt(tokens[1], 10, 32); err == nil { - tokens[1] = strconv.Itoa(int(minorVersion) - 2) - return strings.Join(tokens, ".") - } - return version -} - func (optr *Operator) fetchClusterOperator() (*configv1.ClusterOperator, error) { co, err := optr.clusterOperatorLister.Get(optr.name) diff --git a/pkg/operator/status_test.go b/pkg/operator/status_test.go index aa842d9e59..2da8a66e84 100644 --- a/pkg/operator/status_test.go +++ b/pkg/operator/status_test.go @@ -8,7 +8,6 @@ import ( "testing" corelisterv1 "k8s.io/client-go/listers/core/v1" - clientgotesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" "k8s.io/utils/clock" @@ -802,353 +801,3 @@ func TestInClusterBringUpStayOnErr(t *testing.T) { assert.False(t, optr.inClusterBringup) } - -func TestKubeletSkewUnSupported(t *testing.T) { - kasOperator := &configv1.ClusterOperator{ - ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver"}, - Status: configv1.ClusterOperatorStatus{ - Versions: []configv1.OperandVersion{ - {Name: "kube-apiserver", Version: "1.21"}, - }, - }, - } - optr := &Operator{ - eventRecorder: &record.FakeRecorder{}, - fgAccessor: featuregates.NewHardcodedFeatureGateAccess( - []configv1.FeatureGateName{features.FeatureGatePinnedImages}, []configv1.FeatureGateName{}, - ), - } - optr.vStore = newVersionStore() - optr.vStore.Set("operator", "test-version") - optr.mcpLister = &mockMCPLister{ - pools: []*mcfgv1.MachineConfigPool{ - helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0"), - helpers.NewMachineConfigPool("workers", nil, helpers.WorkerSelector, "v0"), - }, - } - nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeLister = corelisterv1.NewNodeLister(nodeIndexer) - nodeIndexer.Add(&corev1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: "first-node", Labels: map[string]string{"node-role/worker": ""}}, - Status: corev1.NodeStatus{ - NodeInfo: corev1.NodeSystemInfo{ - KubeletVersion: "v1.18", - }, - }, - }) - - co := &configv1.ClusterOperator{} - configNode := &configv1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: ctrlcommon.ClusterNodeInstanceName}, - Spec: configv1.NodeSpec{}, - } - configNodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeClusterLister = configlistersv1.NewNodeLister(configNodeIndexer) - configNodeIndexer.Add(configNode) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorDegraded, Status: configv1.ConditionFalse}, clock.RealClock{}) - fakeClient := fakeconfigclientset.NewSimpleClientset(co, kasOperator) - optr.configClient = fakeClient - optr.inClusterBringup = true - - operatorIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.clusterOperatorLister = configlistersv1.NewClusterOperatorLister(operatorIndexer) - operatorIndexer.Add(co) - operatorIndexer.Add(kasOperator) - - configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.mcoCmLister = corelisterv1.NewConfigMapLister(configMapIndexer) - - fn1 := func(config *renderConfig, co *configv1.ClusterOperator) error { - return errors.New("mocked fn1") - } - err := optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.NotNil(t, err, "expected syncAll to fail") - - assert.True(t, optr.inClusterBringup) - - fn1 = func(config *renderConfig, co *configv1.ClusterOperator) error { - return nil - } - err = optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.Nil(t, err, "expected syncAll to pass") - - assert.False(t, optr.inClusterBringup) - - var lastUpdate clientgotesting.UpdateAction - for _, action := range fakeClient.Actions() { - if action.GetVerb() == "update" { - lastUpdate = action.(clientgotesting.UpdateAction) - } - } - if lastUpdate == nil { - t.Fatal("missing update") - } - operatorStatus := lastUpdate.GetObject().(*configv1.ClusterOperator) - var upgradeable *configv1.ClusterOperatorStatusCondition - for _, condition := range operatorStatus.Status.Conditions { - if condition.Type == configv1.OperatorUpgradeable { - upgradeable = &condition - break - } - } - if upgradeable == nil { - t.Fatal("missing condition") - } - if upgradeable.Status != configv1.ConditionTrue { - t.Fatal(upgradeable) - } - if upgradeable.Message != "One or more nodes have an unsupported kubelet version skew. Please see `oc get nodes` for details and upgrade all nodes so that they have a kubelet version of at least 1.19." { - t.Fatal(upgradeable) - } - if upgradeable.Reason != "KubeletSkewUnsupported" { - t.Fatal(upgradeable) - } -} - -func TestCustomPoolKubeletSkewUnSupported(t *testing.T) { - customSelector := metav1.AddLabelToSelector(&metav1.LabelSelector{}, "node-role/custom", "") - kasOperator := &configv1.ClusterOperator{ - ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver"}, - Status: configv1.ClusterOperatorStatus{ - Versions: []configv1.OperandVersion{ - {Name: "kube-apiserver", Version: "1.21"}, - }, - }, - } - optr := &Operator{ - eventRecorder: &record.FakeRecorder{}, - fgAccessor: featuregates.NewHardcodedFeatureGateAccess( - []configv1.FeatureGateName{features.FeatureGatePinnedImages}, []configv1.FeatureGateName{}, - ), - } - optr.vStore = newVersionStore() - optr.vStore.Set("operator", "test-version") - optr.mcpLister = &mockMCPLister{ - pools: []*mcfgv1.MachineConfigPool{ - helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0"), - helpers.NewMachineConfigPool("workers", nil, helpers.WorkerSelector, "v0"), - helpers.NewMachineConfigPool("custom", nil, customSelector, "v0"), - }, - } - nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeLister = corelisterv1.NewNodeLister(nodeIndexer) - nodeIndexer.Add(&corev1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: "custom", Labels: map[string]string{"node-role/custom": ""}}, - Status: corev1.NodeStatus{ - NodeInfo: corev1.NodeSystemInfo{ - KubeletVersion: "v1.18", - }, - }, - }) - - co := &configv1.ClusterOperator{} - configNode := &configv1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: ctrlcommon.ClusterNodeInstanceName}, - Spec: configv1.NodeSpec{}, - } - configNodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeClusterLister = configlistersv1.NewNodeLister(configNodeIndexer) - configNodeIndexer.Add(configNode) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorDegraded, Status: configv1.ConditionFalse}, clock.RealClock{}) - fakeClient := fakeconfigclientset.NewSimpleClientset(co, kasOperator) - optr.configClient = fakeClient - optr.inClusterBringup = true - - operatorIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.clusterOperatorLister = configlistersv1.NewClusterOperatorLister(operatorIndexer) - operatorIndexer.Add(co) - operatorIndexer.Add(kasOperator) - - configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.mcoCmLister = corelisterv1.NewConfigMapLister(configMapIndexer) - - fn1 := func(config *renderConfig, co *configv1.ClusterOperator) error { - return errors.New("mocked fn1") - } - err := optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.NotNil(t, err, "expected syncAll to fail") - - assert.True(t, optr.inClusterBringup) - - fn1 = func(config *renderConfig, co *configv1.ClusterOperator) error { - return nil - } - err = optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.Nil(t, err, "expected syncAll to pass") - - assert.False(t, optr.inClusterBringup) - - var lastUpdate clientgotesting.UpdateAction - for _, action := range fakeClient.Actions() { - if action.GetVerb() == "update" { - lastUpdate = action.(clientgotesting.UpdateAction) - } - } - if lastUpdate == nil { - t.Fatal("missing update") - } - operatorStatus := lastUpdate.GetObject().(*configv1.ClusterOperator) - var upgradeable *configv1.ClusterOperatorStatusCondition - for _, condition := range operatorStatus.Status.Conditions { - if condition.Type == configv1.OperatorUpgradeable { - upgradeable = &condition - break - } - } - if upgradeable == nil { - t.Fatal("missing condition") - } - if upgradeable.Status != configv1.ConditionTrue { - t.Fatal(upgradeable) - } - if upgradeable.Message != "One or more nodes have an unsupported kubelet version skew. Please see `oc get nodes` for details and upgrade all nodes so that they have a kubelet version of at least 1.19." { - t.Fatal(upgradeable) - } - if upgradeable.Reason != "KubeletSkewUnsupported" { - t.Fatal(upgradeable) - } -} - -func TestKubeletSkewSupported(t *testing.T) { - kasOperator := &configv1.ClusterOperator{ - ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver"}, - Status: configv1.ClusterOperatorStatus{ - Versions: []configv1.OperandVersion{ - {Name: "kube-apiserver", Version: "1.21"}, - }, - }, - } - optr := &Operator{ - eventRecorder: &record.FakeRecorder{}, - fgAccessor: featuregates.NewHardcodedFeatureGateAccess( - []configv1.FeatureGateName{features.FeatureGatePinnedImages}, []configv1.FeatureGateName{}, - ), - } - optr.vStore = newVersionStore() - optr.vStore.Set("operator", "test-version") - optr.mcpLister = &mockMCPLister{ - pools: []*mcfgv1.MachineConfigPool{ - helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0"), - helpers.NewMachineConfigPool("workers", nil, helpers.WorkerSelector, "v0"), - }, - } - nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeLister = corelisterv1.NewNodeLister(nodeIndexer) - nodeIndexer.Add(&corev1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: "first-node", Labels: map[string]string{"node-role/worker": ""}}, - Status: corev1.NodeStatus{ - NodeInfo: corev1.NodeSystemInfo{ - KubeletVersion: "v1.20", - }, - }, - }) - - co := &configv1.ClusterOperator{} - configNode := &configv1.Node{ - ObjectMeta: metav1.ObjectMeta{Name: ctrlcommon.ClusterNodeInstanceName}, - Spec: configv1.NodeSpec{}, - } - configNodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.nodeClusterLister = configlistersv1.NewNodeLister(configNodeIndexer) - configNodeIndexer.Add(configNode) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorAvailable, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorProgressing, Status: configv1.ConditionFalse}, clock.RealClock{}) - cov1helpers.SetStatusCondition(&co.Status.Conditions, configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorDegraded, Status: configv1.ConditionFalse}, clock.RealClock{}) - fakeClient := fakeconfigclientset.NewSimpleClientset(co, kasOperator) - optr.configClient = fakeClient - optr.inClusterBringup = true - - operatorIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.clusterOperatorLister = configlistersv1.NewClusterOperatorLister(operatorIndexer) - operatorIndexer.Add(co) - operatorIndexer.Add(kasOperator) - - configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - optr.mcoCmLister = corelisterv1.NewConfigMapLister(configMapIndexer) - - fn1 := func(config *renderConfig, co *configv1.ClusterOperator) error { - return errors.New("mocked fn1") - } - err := optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.NotNil(t, err, "expected syncAll to fail") - - assert.True(t, optr.inClusterBringup) - - fn1 = func(config *renderConfig, co *configv1.ClusterOperator) error { - return nil - } - err = optr.syncAll([]syncFunc{{name: "mock1", fn: fn1}}) - assert.Nil(t, err, "expected syncAll to pass") - - assert.False(t, optr.inClusterBringup) - - var lastUpdate clientgotesting.UpdateAction - for _, action := range fakeClient.Actions() { - if action.GetVerb() == "update" { - lastUpdate = action.(clientgotesting.UpdateAction) - } - } - if lastUpdate == nil { - t.Fatal("missing update") - } - operatorStatus := lastUpdate.GetObject().(*configv1.ClusterOperator) - var upgradeable *configv1.ClusterOperatorStatusCondition - for _, condition := range operatorStatus.Status.Conditions { - if condition.Type == configv1.OperatorUpgradeable { - upgradeable = &condition - break - } - } - if upgradeable == nil { - t.Fatal("missing condition") - } - if upgradeable.Status != configv1.ConditionTrue { - t.Fatal(upgradeable) - } - if upgradeable.Message != "" { - t.Fatal(upgradeable) - } - if upgradeable.Reason != "AsExpected" { - t.Fatal(upgradeable) - } -} - -func TestGetMinorKubeletVersion(t *testing.T) { - tcs := []struct { - version string - minor int - expectNilErr bool - }{ - {"v1.20.1", 20, true}, - {"v1.20.1+abc0", 20, true}, - {"v1.20.1+0123", 20, true}, - {"v1.20.1-rc", 20, true}, - {"v1.20.1-rc.1", 20, true}, - {"v1.20.1-rc+abc123", 20, true}, - {"v1.20.1-rc.0+abc123", 20, true}, - {"v1.20.1", 20, true}, - {"1.20.1", 20, true}, - {"1.20", 20, true}, - {"12", 0, false}, - {".xy", 0, false}, - {"1.xy.1", 0, false}, - } - for _, tc := range tcs { - minorV, err := getMinorKubeletVersion(tc.version) - if tc.expectNilErr && err != nil { - t.Errorf("test %q failed: unexpected error %v", tc.version, err) - continue - } - if !tc.expectNilErr && err == nil { - t.Errorf("test %q failed: expected error, got nil ", tc.version) - continue - } - if tc.expectNilErr { - assert.Equal(t, tc.minor, minorV, fmt.Sprintf("failed test %q", tc.version)) - } - } -}