Skip to content

Commit f9dd6fa

Browse files
committed
UPSTREAM: <carry>: admission: validate minimumKubeletVersion
Signed-off-by: Peter Hunt <[email protected]>
1 parent c415839 commit f9dd6fa

File tree

2 files changed

+312
-9
lines changed

2 files changed

+312
-9
lines changed

openshift-kube-apiserver/admission/customresourcevalidation/node/validate_node_config.go

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@ package node
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78

9+
configv1 "github.com/openshift/api/config/v1"
10+
nodelib "github.com/openshift/library-go/pkg/apiserver/node"
11+
12+
openshiftfeatures "github.com/openshift/api/features"
813
"k8s.io/apimachinery/pkg/api/validation"
14+
"k8s.io/apimachinery/pkg/labels"
915
"k8s.io/apimachinery/pkg/runtime"
1016
"k8s.io/apimachinery/pkg/runtime/schema"
1117
"k8s.io/apimachinery/pkg/util/validation/field"
1218
"k8s.io/apiserver/pkg/admission"
13-
14-
configv1 "github.com/openshift/api/config/v1"
19+
"k8s.io/apiserver/pkg/admission/initializer"
20+
"k8s.io/apiserver/pkg/util/feature"
21+
"k8s.io/client-go/informers"
22+
corev1listers "k8s.io/client-go/listers/core/v1"
23+
"k8s.io/component-base/featuregate"
1524
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation"
1625
)
1726

@@ -25,18 +34,28 @@ var rejectionScenarios = []struct {
2534
{fromProfile: configv1.LowUpdateSlowReaction, toProfile: configv1.DefaultUpdateDefaultReaction},
2635
}
2736

28-
const PluginName = "config.openshift.io/RestrictExtremeWorkerLatencyProfile"
37+
const PluginName = "config.openshift.io/ValidateConfigNodeV1"
2938

3039
// Register registers a plugin
3140
func Register(plugins *admission.Plugins) {
3241
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
33-
return customresourcevalidation.NewValidator(
42+
ret := &validateCustomResourceWithNodeLister{}
43+
delegate, err := customresourcevalidation.NewValidator(
3444
map[schema.GroupResource]bool{
3545
configv1.Resource("nodes"): true,
3646
},
3747
map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{
38-
configv1.GroupVersion.WithKind("Node"): configNodeV1{},
48+
configv1.GroupVersion.WithKind("Node"): &configNodeV1{
49+
nodeLister: ret.getNodeLister,
50+
waitForReady: ret.getWaitForReady,
51+
minimumKubeletVersionEnabled: feature.DefaultMutableFeatureGate.Enabled(featuregate.Feature(openshiftfeatures.FeatureGateMinimumKubeletVersion)),
52+
},
3953
})
54+
if err != nil {
55+
return nil, err
56+
}
57+
ret.ValidationInterface = delegate
58+
return ret, nil
4059
})
4160
}
4261

@@ -57,7 +76,13 @@ func toConfigNodeV1(uncastObj runtime.Object) (*configv1.Node, field.ErrorList)
5776
return obj, nil
5877
}
5978

60-
type configNodeV1 struct{}
79+
type configNodeV1 struct {
80+
admission.ValidationInterface
81+
82+
nodeLister func() corev1listers.NodeLister
83+
waitForReady func() func() bool
84+
minimumKubeletVersionEnabled bool
85+
}
6186

6287
func validateConfigNodeForExtremeLatencyProfile(obj, oldObj *configv1.Node) *field.Error {
6388
fromProfile := oldObj.Spec.WorkerLatencyProfile
@@ -78,18 +103,21 @@ func validateConfigNodeForExtremeLatencyProfile(obj, oldObj *configv1.Node) *fie
78103
return nil
79104
}
80105

81-
func (configNodeV1) ValidateCreate(_ context.Context, uncastObj runtime.Object) field.ErrorList {
106+
func (c *configNodeV1) ValidateCreate(_ context.Context, uncastObj runtime.Object) field.ErrorList {
82107
obj, allErrs := toConfigNodeV1(uncastObj)
83108
if len(allErrs) > 0 {
84109
return allErrs
85110
}
86111

87112
allErrs = append(allErrs, validation.ValidateObjectMeta(&obj.ObjectMeta, false, customresourcevalidation.RequireNameCluster, field.NewPath("metadata"))...)
113+
if err := c.validateMinimumKubeletVersion(obj); err != nil {
114+
allErrs = append(allErrs, err)
115+
}
88116

89117
return allErrs
90118
}
91119

92-
func (configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
120+
func (c *configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
93121
obj, allErrs := toConfigNodeV1(uncastObj)
94122
if len(allErrs) > 0 {
95123
return allErrs
@@ -103,11 +131,36 @@ func (configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object,
103131
if err := validateConfigNodeForExtremeLatencyProfile(obj, oldObj); err != nil {
104132
allErrs = append(allErrs, err)
105133
}
134+
if err := c.validateMinimumKubeletVersion(obj); err != nil {
135+
allErrs = append(allErrs, err)
136+
}
106137

107138
return allErrs
108139
}
140+
func (c *configNodeV1) validateMinimumKubeletVersion(obj *configv1.Node) *field.Error {
141+
if !c.minimumKubeletVersionEnabled {
142+
return nil
143+
}
144+
fieldPath := field.NewPath("spec", "minimumKubeletVersion")
145+
if !c.waitForReady()() {
146+
return field.InternalError(fieldPath, fmt.Errorf("caches not synchronized, cannot validate minimumKubeletVersion"))
147+
}
109148

110-
func (configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
149+
nodes, err := c.nodeLister().List(labels.Everything())
150+
if err != nil {
151+
return field.Forbidden(fieldPath, fmt.Sprintf("Getting nodes to compare minimum version %v", err.Error()))
152+
}
153+
154+
if err := nodelib.ValidateMinimumKubeletVersion(nodes, obj.Spec.MinimumKubeletVersion); err != nil {
155+
if errors.Is(err, nodelib.ErrKubeletOutdated) {
156+
return field.Forbidden(fieldPath, err.Error())
157+
}
158+
return field.Invalid(fieldPath, obj.Spec.MinimumKubeletVersion, err.Error())
159+
}
160+
return nil
161+
}
162+
163+
func (*configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList {
111164
obj, errs := toConfigNodeV1(uncastObj)
112165
if len(errs) > 0 {
113166
return errs
@@ -122,3 +175,34 @@ func (configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Ob
122175

123176
return errs
124177
}
178+
179+
type validateCustomResourceWithNodeLister struct {
180+
nodeLister corev1listers.NodeLister
181+
182+
handler admission.Handler
183+
admission.ValidationInterface
184+
}
185+
186+
var _ = initializer.WantsExternalKubeInformerFactory(&validateCustomResourceWithNodeLister{})
187+
188+
func (c *validateCustomResourceWithNodeLister) SetExternalKubeInformerFactory(kubeInformers informers.SharedInformerFactory) {
189+
nodeInformer := kubeInformers.Core().V1().Nodes()
190+
c.nodeLister = nodeInformer.Lister()
191+
c.handler.SetReadyFunc(nodeInformer.Informer().HasSynced)
192+
}
193+
194+
func (c *validateCustomResourceWithNodeLister) ValidateInitialization() error {
195+
if c.nodeLister == nil {
196+
return fmt.Errorf("%s needs a nodes", PluginName)
197+
}
198+
199+
return nil
200+
}
201+
202+
func (c *validateCustomResourceWithNodeLister) getNodeLister() corev1listers.NodeLister {
203+
return c.nodeLister
204+
}
205+
206+
func (c *validateCustomResourceWithNodeLister) getWaitForReady() func() bool {
207+
return c.handler.WaitForReady
208+
}

0 commit comments

Comments
 (0)