1
16
17 package validation
18
19 import (
20 "fmt"
21 "strings"
22
23 v1 "k8s.io/api/core/v1"
24 "k8s.io/apimachinery/pkg/api/resource"
25 "k8s.io/apimachinery/pkg/util/sets"
26 "k8s.io/apimachinery/pkg/util/validation/field"
27 "k8s.io/kubernetes/pkg/apis/core"
28 "k8s.io/kubernetes/pkg/apis/core/helper"
29 v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
30 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
31 )
32
33 const isNegativeErrorMsg string = `must be greater than or equal to 0`
34 const isNotIntegerErrorMsg string = `must be an integer`
35
36
37
38
39 func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList {
40 allErrs := field.ErrorList{}
41 limPath := fldPath.Child("limits")
42 reqPath := fldPath.Child("requests")
43 for resourceName, quantity := range requirements.Limits {
44 fldPath := limPath.Key(string(resourceName))
45
46 allErrs = append(allErrs, ValidateContainerResourceName(core.ResourceName(resourceName), fldPath)...)
47
48
49 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceName(resourceName), quantity, fldPath)...)
50
51 }
52 for resourceName, quantity := range requirements.Requests {
53 fldPath := reqPath.Key(string(resourceName))
54
55 allErrs = append(allErrs, ValidateContainerResourceName(core.ResourceName(resourceName), fldPath)...)
56
57 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceName(resourceName), quantity, fldPath)...)
58
59
60 limitQuantity, exists := requirements.Limits[resourceName]
61 if exists {
62
63 if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
64 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String())))
65 } else if quantity.Cmp(limitQuantity) > 0 {
66 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String())))
67 }
68 }
69 }
70
71 return allErrs
72 }
73
74
75 func ValidateContainerResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList {
76 allErrs := validateResourceName(value, fldPath)
77 if len(strings.Split(string(value), "/")) == 1 {
78 if !helper.IsStandardContainerResourceName(value) {
79 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers"))
80 }
81 } else if !v1helper.IsNativeResource(v1.ResourceName(value)) {
82 if !v1helper.IsExtendedResourceName(v1.ResourceName(value)) {
83 return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard"))
84 }
85 }
86 return allErrs
87 }
88
89
90 func ValidateResourceQuantityValue(resource core.ResourceName, value resource.Quantity, fldPath *field.Path) field.ErrorList {
91 allErrs := field.ErrorList{}
92 allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
93 if helper.IsIntegerResourceName(resource) {
94 if value.MilliValue()%int64(1000) != int64(0) {
95 allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
96 }
97 }
98 return allErrs
99 }
100
101
102 func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList {
103 allErrs := field.ErrorList{}
104 if value.Cmp(resource.Quantity{}) < 0 {
105 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg))
106 }
107 return allErrs
108 }
109
110
111
112 func validateResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList {
113 allErrs := apivalidation.ValidateQualifiedName(string(value), fldPath)
114 if len(allErrs) != 0 {
115 return allErrs
116 }
117
118 if len(strings.Split(string(value), "/")) == 1 {
119 if !helper.IsStandardResourceName(value) {
120 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
121 }
122 }
123
124 return allErrs
125 }
126
127
128
129 func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList {
130 allErrs := field.ErrorList{}
131 if opts.TailLines != nil && *opts.TailLines < 0 {
132 allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg))
133 }
134 if opts.LimitBytes != nil && *opts.LimitBytes < 1 {
135 allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0"))
136 }
137 switch {
138 case opts.SinceSeconds != nil && opts.SinceTime != nil:
139 allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified"))
140 case opts.SinceSeconds != nil:
141 if *opts.SinceSeconds < 1 {
142 allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0"))
143 }
144 }
145 return allErrs
146 }
147
148
149
150 func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList {
151 allErrs := field.ErrorList{}
152
153 for ci, ctr := range containers {
154 idxPath := fldPath.Index(ci)
155 portsPath := idxPath.Child("ports")
156 for pi := range ctr.Ports {
157 idxPath := portsPath.Index(pi)
158 port := ctr.Ports[pi].HostPort
159 if port == 0 {
160 continue
161 }
162 str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol)
163 if accumulator.Has(str) {
164 allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str))
165 } else {
166 accumulator.Insert(str)
167 }
168 }
169 }
170 return allErrs
171 }
172
View as plain text