1
16
17 package validation
18
19 import (
20 "fmt"
21 "strings"
22
23 v1 "k8s.io/api/core/v1"
24 metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
25 "k8s.io/apimachinery/pkg/util/errors"
26 "k8s.io/apimachinery/pkg/util/sets"
27 "k8s.io/apimachinery/pkg/util/validation/field"
28 utilfeature "k8s.io/apiserver/pkg/util/feature"
29 "k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
30 "k8s.io/kubernetes/pkg/features"
31 "k8s.io/kubernetes/pkg/scheduler/apis/config"
32 )
33
34
35 var supportedScoringStrategyTypes = sets.New(
36 string(config.LeastAllocated),
37 string(config.MostAllocated),
38 string(config.RequestedToCapacityRatio),
39 )
40
41
42 func ValidateDefaultPreemptionArgs(path *field.Path, args *config.DefaultPreemptionArgs) error {
43 var allErrs field.ErrorList
44 percentagePath := path.Child("minCandidateNodesPercentage")
45 absolutePath := path.Child("minCandidateNodesAbsolute")
46 if err := validateMinCandidateNodesPercentage(args.MinCandidateNodesPercentage, percentagePath); err != nil {
47 allErrs = append(allErrs, err)
48 }
49 if err := validateMinCandidateNodesAbsolute(args.MinCandidateNodesAbsolute, absolutePath); err != nil {
50 allErrs = append(allErrs, err)
51 }
52 if args.MinCandidateNodesPercentage == 0 && args.MinCandidateNodesAbsolute == 0 {
53 allErrs = append(allErrs,
54 field.Invalid(percentagePath, args.MinCandidateNodesPercentage, "cannot be zero at the same time as minCandidateNodesAbsolute"),
55 field.Invalid(absolutePath, args.MinCandidateNodesAbsolute, "cannot be zero at the same time as minCandidateNodesPercentage"))
56 }
57 return allErrs.ToAggregate()
58 }
59
60
61
62 func validateMinCandidateNodesPercentage(minCandidateNodesPercentage int32, p *field.Path) *field.Error {
63 if minCandidateNodesPercentage < 0 || minCandidateNodesPercentage > 100 {
64 return field.Invalid(p, minCandidateNodesPercentage, "not in valid range [0, 100]")
65 }
66 return nil
67 }
68
69
70
71 func validateMinCandidateNodesAbsolute(minCandidateNodesAbsolute int32, p *field.Path) *field.Error {
72 if minCandidateNodesAbsolute < 0 {
73 return field.Invalid(p, minCandidateNodesAbsolute, "not in valid range [0, inf)")
74 }
75 return nil
76 }
77
78
79 func ValidateInterPodAffinityArgs(path *field.Path, args *config.InterPodAffinityArgs) error {
80 return validateHardPodAffinityWeight(path.Child("hardPodAffinityWeight"), args.HardPodAffinityWeight)
81 }
82
83
84 func validateHardPodAffinityWeight(path *field.Path, w int32) error {
85 const (
86 minHardPodAffinityWeight = 0
87 maxHardPodAffinityWeight = 100
88 )
89
90 if w < minHardPodAffinityWeight || w > maxHardPodAffinityWeight {
91 msg := fmt.Sprintf("not in valid range [%d, %d]", minHardPodAffinityWeight, maxHardPodAffinityWeight)
92 return field.Invalid(path, w, msg)
93 }
94 return nil
95 }
96
97
98
99
100 func ValidatePodTopologySpreadArgs(path *field.Path, args *config.PodTopologySpreadArgs) error {
101 var allErrs field.ErrorList
102 if err := validateDefaultingType(path.Child("defaultingType"), args.DefaultingType, args.DefaultConstraints); err != nil {
103 allErrs = append(allErrs, err)
104 }
105
106 defaultConstraintsPath := path.Child("defaultConstraints")
107 for i, c := range args.DefaultConstraints {
108 p := defaultConstraintsPath.Index(i)
109 if c.MaxSkew <= 0 {
110 f := p.Child("maxSkew")
111 allErrs = append(allErrs, field.Invalid(f, c.MaxSkew, "not in valid range (0, inf)"))
112 }
113 allErrs = append(allErrs, validateTopologyKey(p.Child("topologyKey"), c.TopologyKey)...)
114 if err := validateWhenUnsatisfiable(p.Child("whenUnsatisfiable"), c.WhenUnsatisfiable); err != nil {
115 allErrs = append(allErrs, err)
116 }
117 if c.LabelSelector != nil {
118 f := field.Forbidden(p.Child("labelSelector"), "constraint must not define a selector, as they deduced for each pod")
119 allErrs = append(allErrs, f)
120 }
121 if err := validateConstraintNotRepeat(defaultConstraintsPath, args.DefaultConstraints, i); err != nil {
122 allErrs = append(allErrs, err)
123 }
124 }
125 if len(allErrs) == 0 {
126 return nil
127 }
128 return allErrs.ToAggregate()
129 }
130
131 func validateDefaultingType(p *field.Path, v config.PodTopologySpreadConstraintsDefaulting, constraints []v1.TopologySpreadConstraint) *field.Error {
132 if v != config.SystemDefaulting && v != config.ListDefaulting {
133 return field.NotSupported(p, v, []string{string(config.SystemDefaulting), string(config.ListDefaulting)})
134 }
135 if v == config.SystemDefaulting && len(constraints) > 0 {
136 return field.Invalid(p, v, "when .defaultConstraints are not empty")
137 }
138 return nil
139 }
140
141 func validateTopologyKey(p *field.Path, v string) field.ErrorList {
142 var allErrs field.ErrorList
143 if len(v) == 0 {
144 allErrs = append(allErrs, field.Required(p, "can not be empty"))
145 } else {
146 allErrs = append(allErrs, metav1validation.ValidateLabelName(v, p)...)
147 }
148 return allErrs
149 }
150
151 func validateWhenUnsatisfiable(p *field.Path, v v1.UnsatisfiableConstraintAction) *field.Error {
152 supportedScheduleActions := sets.New(string(v1.DoNotSchedule), string(v1.ScheduleAnyway))
153
154 if len(v) == 0 {
155 return field.Required(p, "can not be empty")
156 }
157 if !supportedScheduleActions.Has(string(v)) {
158 return field.NotSupported(p, v, sets.List(supportedScheduleActions))
159 }
160 return nil
161 }
162
163 func validateConstraintNotRepeat(path *field.Path, constraints []v1.TopologySpreadConstraint, idx int) *field.Error {
164 c := &constraints[idx]
165 for i := range constraints[:idx] {
166 other := &constraints[i]
167 if c.TopologyKey == other.TopologyKey && c.WhenUnsatisfiable == other.WhenUnsatisfiable {
168 return field.Duplicate(path.Index(idx), fmt.Sprintf("{%v, %v}", c.TopologyKey, c.WhenUnsatisfiable))
169 }
170 }
171 return nil
172 }
173
174 func validateFunctionShape(shape []config.UtilizationShapePoint, path *field.Path) field.ErrorList {
175 const (
176 minUtilization = 0
177 maxUtilization = 100
178 minScore = 0
179 maxScore = int32(config.MaxCustomPriorityScore)
180 )
181
182 var allErrs field.ErrorList
183
184 if len(shape) == 0 {
185 allErrs = append(allErrs, field.Required(path, "at least one point must be specified"))
186 return allErrs
187 }
188
189 for i := 1; i < len(shape); i++ {
190 if shape[i-1].Utilization >= shape[i].Utilization {
191 allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), shape[i].Utilization, "utilization values must be sorted in increasing order"))
192 break
193 }
194 }
195
196 for i, point := range shape {
197 if point.Utilization < minUtilization || point.Utilization > maxUtilization {
198 msg := fmt.Sprintf("not in valid range [%d, %d]", minUtilization, maxUtilization)
199 allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), point.Utilization, msg))
200 }
201
202 if point.Score < minScore || point.Score > maxScore {
203 msg := fmt.Sprintf("not in valid range [%d, %d]", minScore, maxScore)
204 allErrs = append(allErrs, field.Invalid(path.Index(i).Child("score"), point.Score, msg))
205 }
206 }
207
208 return allErrs
209 }
210
211 func validateResources(resources []config.ResourceSpec, p *field.Path) field.ErrorList {
212 var allErrs field.ErrorList
213 for i, resource := range resources {
214 if resource.Weight <= 0 || resource.Weight > 100 {
215 msg := fmt.Sprintf("resource weight of %v not in valid range (0, 100]", resource.Name)
216 allErrs = append(allErrs, field.Invalid(p.Index(i).Child("weight"), resource.Weight, msg))
217 }
218 }
219 return allErrs
220 }
221
222
223 func ValidateNodeResourcesBalancedAllocationArgs(path *field.Path, args *config.NodeResourcesBalancedAllocationArgs) error {
224 var allErrs field.ErrorList
225 seenResources := sets.New[string]()
226 for i, resource := range args.Resources {
227 if seenResources.Has(resource.Name) {
228 allErrs = append(allErrs, field.Duplicate(path.Child("resources").Index(i).Child("name"), resource.Name))
229 } else {
230 seenResources.Insert(resource.Name)
231 }
232 if resource.Weight != 1 {
233 allErrs = append(allErrs, field.Invalid(path.Child("resources").Index(i).Child("weight"), resource.Weight, "must be 1"))
234 }
235 }
236 return allErrs.ToAggregate()
237 }
238
239
240 func ValidateNodeAffinityArgs(path *field.Path, args *config.NodeAffinityArgs) error {
241 if args.AddedAffinity == nil {
242 return nil
243 }
244 affinity := args.AddedAffinity
245 var errs []error
246 if ns := affinity.RequiredDuringSchedulingIgnoredDuringExecution; ns != nil {
247 _, err := nodeaffinity.NewNodeSelector(ns, field.WithPath(path.Child("addedAffinity", "requiredDuringSchedulingIgnoredDuringExecution")))
248 if err != nil {
249 errs = append(errs, err)
250 }
251 }
252
253 if terms := affinity.PreferredDuringSchedulingIgnoredDuringExecution; len(terms) != 0 {
254 _, err := nodeaffinity.NewPreferredSchedulingTerms(terms, field.WithPath(path.Child("addedAffinity", "preferredDuringSchedulingIgnoredDuringExecution")))
255 if err != nil {
256 errs = append(errs, err)
257 }
258 }
259 return errors.Flatten(errors.NewAggregate(errs))
260 }
261
262
263 type VolumeBindingArgsValidationOptions struct {
264 AllowVolumeCapacityPriority bool
265 }
266
267
268 func ValidateVolumeBindingArgs(path *field.Path, args *config.VolumeBindingArgs) error {
269 return ValidateVolumeBindingArgsWithOptions(path, args, VolumeBindingArgsValidationOptions{
270 AllowVolumeCapacityPriority: utilfeature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
271 })
272 }
273
274
275 func ValidateVolumeBindingArgsWithOptions(path *field.Path, args *config.VolumeBindingArgs, opts VolumeBindingArgsValidationOptions) error {
276 var allErrs field.ErrorList
277
278 if args.BindTimeoutSeconds < 0 {
279 allErrs = append(allErrs, field.Invalid(path.Child("bindTimeoutSeconds"), args.BindTimeoutSeconds, "invalid BindTimeoutSeconds, should not be a negative value"))
280 }
281
282 if opts.AllowVolumeCapacityPriority {
283 allErrs = append(allErrs, validateFunctionShape(args.Shape, path.Child("shape"))...)
284 } else if args.Shape != nil {
285
286
287
288 allErrs = append(allErrs, field.Invalid(path.Child("shape"), args.Shape, "unexpected field `shape`, remove it or turn on the feature gate VolumeCapacityPriority"))
289 }
290 return allErrs.ToAggregate()
291 }
292
293 func ValidateNodeResourcesFitArgs(path *field.Path, args *config.NodeResourcesFitArgs) error {
294 var allErrs field.ErrorList
295 resPath := path.Child("ignoredResources")
296 for i, res := range args.IgnoredResources {
297 path := resPath.Index(i)
298 if errs := metav1validation.ValidateLabelName(res, path); len(errs) != 0 {
299 allErrs = append(allErrs, errs...)
300 }
301 }
302
303 groupPath := path.Child("ignoredResourceGroups")
304 for i, group := range args.IgnoredResourceGroups {
305 path := groupPath.Index(i)
306 if strings.Contains(group, "/") {
307 allErrs = append(allErrs, field.Invalid(path, group, "resource group name can't contain '/'"))
308 }
309 if errs := metav1validation.ValidateLabelName(group, path); len(errs) != 0 {
310 allErrs = append(allErrs, errs...)
311 }
312 }
313
314 strategyPath := path.Child("scoringStrategy")
315 if args.ScoringStrategy != nil {
316 if !supportedScoringStrategyTypes.Has(string(args.ScoringStrategy.Type)) {
317 allErrs = append(allErrs, field.NotSupported(strategyPath.Child("type"), args.ScoringStrategy.Type, sets.List(supportedScoringStrategyTypes)))
318 }
319 allErrs = append(allErrs, validateResources(args.ScoringStrategy.Resources, strategyPath.Child("resources"))...)
320 if args.ScoringStrategy.RequestedToCapacityRatio != nil {
321 allErrs = append(allErrs, validateFunctionShape(args.ScoringStrategy.RequestedToCapacityRatio.Shape, strategyPath.Child("shape"))...)
322 }
323 }
324
325 if len(allErrs) == 0 {
326 return nil
327 }
328 return allErrs.ToAggregate()
329 }
330
View as plain text