@@ -2,16 +2,25 @@ package node
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"io"
7
8
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"
8
13
"k8s.io/apimachinery/pkg/api/validation"
14
+ "k8s.io/apimachinery/pkg/labels"
9
15
"k8s.io/apimachinery/pkg/runtime"
10
16
"k8s.io/apimachinery/pkg/runtime/schema"
11
17
"k8s.io/apimachinery/pkg/util/validation/field"
12
18
"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"
15
24
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation"
16
25
)
17
26
@@ -25,18 +34,28 @@ var rejectionScenarios = []struct {
25
34
{fromProfile : configv1 .LowUpdateSlowReaction , toProfile : configv1 .DefaultUpdateDefaultReaction },
26
35
}
27
36
28
- const PluginName = "config.openshift.io/RestrictExtremeWorkerLatencyProfile "
37
+ const PluginName = "config.openshift.io/ValidateConfigNodeV1 "
29
38
30
39
// Register registers a plugin
31
40
func Register (plugins * admission.Plugins ) {
32
41
plugins .Register (PluginName , func (config io.Reader ) (admission.Interface , error ) {
33
- return customresourcevalidation .NewValidator (
42
+ ret := & validateCustomResourceWithNodeLister {}
43
+ delegate , err := customresourcevalidation .NewValidator (
34
44
map [schema.GroupResource ]bool {
35
45
configv1 .Resource ("nodes" ): true ,
36
46
},
37
47
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
+ },
39
53
})
54
+ if err != nil {
55
+ return nil , err
56
+ }
57
+ ret .ValidationInterface = delegate
58
+ return ret , nil
40
59
})
41
60
}
42
61
@@ -57,7 +76,13 @@ func toConfigNodeV1(uncastObj runtime.Object) (*configv1.Node, field.ErrorList)
57
76
return obj , nil
58
77
}
59
78
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
+ }
61
86
62
87
func validateConfigNodeForExtremeLatencyProfile (obj , oldObj * configv1.Node ) * field.Error {
63
88
fromProfile := oldObj .Spec .WorkerLatencyProfile
@@ -78,18 +103,21 @@ func validateConfigNodeForExtremeLatencyProfile(obj, oldObj *configv1.Node) *fie
78
103
return nil
79
104
}
80
105
81
- func (configNodeV1 ) ValidateCreate (_ context.Context , uncastObj runtime.Object ) field.ErrorList {
106
+ func (c * configNodeV1 ) ValidateCreate (_ context.Context , uncastObj runtime.Object ) field.ErrorList {
82
107
obj , allErrs := toConfigNodeV1 (uncastObj )
83
108
if len (allErrs ) > 0 {
84
109
return allErrs
85
110
}
86
111
87
112
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
+ }
88
116
89
117
return allErrs
90
118
}
91
119
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 {
93
121
obj , allErrs := toConfigNodeV1 (uncastObj )
94
122
if len (allErrs ) > 0 {
95
123
return allErrs
@@ -103,11 +131,36 @@ func (configNodeV1) ValidateUpdate(_ context.Context, uncastObj runtime.Object,
103
131
if err := validateConfigNodeForExtremeLatencyProfile (obj , oldObj ); err != nil {
104
132
allErrs = append (allErrs , err )
105
133
}
134
+ if err := c .validateMinimumKubeletVersion (obj ); err != nil {
135
+ allErrs = append (allErrs , err )
136
+ }
106
137
107
138
return allErrs
108
139
}
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
+ }
109
148
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 {
111
164
obj , errs := toConfigNodeV1 (uncastObj )
112
165
if len (errs ) > 0 {
113
166
return errs
@@ -122,3 +175,34 @@ func (configNodeV1) ValidateStatusUpdate(_ context.Context, uncastObj runtime.Ob
122
175
123
176
return errs
124
177
}
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