123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package validation
- import (
- "fmt"
- "strings"
- "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- "k8s.io/apimachinery/pkg/util/sets"
- "k8s.io/apimachinery/pkg/util/validation"
- "k8s.io/apimachinery/pkg/util/validation/field"
- "k8s.io/kubernetes/pkg/apis/core/helper"
- v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
- )
- const isNegativeErrorMsg string = `must be greater than or equal to 0`
- const isNotIntegerErrorMsg string = `must be an integer`
- // ValidateResourceRequirements will check if any of the resource
- // Limits/Requests are of a valid value. Any incorrect value will be added to
- // the ErrorList.
- func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- limPath := fldPath.Child("limits")
- reqPath := fldPath.Child("requests")
- for resourceName, quantity := range requirements.Limits {
- fldPath := limPath.Key(string(resourceName))
- // Validate resource name.
- allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
- // Validate resource quantity.
- allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
- }
- for resourceName, quantity := range requirements.Requests {
- fldPath := reqPath.Key(string(resourceName))
- // Validate resource name.
- allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
- // Validate resource quantity.
- allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
- // Check that request <= limit.
- limitQuantity, exists := requirements.Limits[resourceName]
- if exists {
- // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
- if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
- allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
- } else if quantity.Cmp(limitQuantity) > 0 {
- allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName)))
- }
- }
- }
- return allErrs
- }
- func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList {
- allErrs := validateResourceName(value, fldPath)
- if len(strings.Split(value, "/")) == 1 {
- if !helper.IsStandardContainerResourceName(value) {
- return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers"))
- }
- } else if !v1helper.IsNativeResource(v1.ResourceName(value)) {
- if !v1helper.IsExtendedResourceName(v1.ResourceName(value)) {
- return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard"))
- }
- }
- return allErrs
- }
- // ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource
- func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
- if helper.IsIntegerResourceName(resource) {
- if value.MilliValue()%int64(1000) != int64(0) {
- allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
- }
- }
- return allErrs
- }
- // ValidateNonnegativeQuantity checks that a Quantity is not negative.
- func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if value.Cmp(resource.Quantity{}) < 0 {
- allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg))
- }
- return allErrs
- }
- // Validate compute resource typename.
- // Refer to docs/design/resources.md for more details.
- func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- for _, msg := range validation.IsQualifiedName(value) {
- allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
- }
- if len(allErrs) != 0 {
- return allErrs
- }
- if len(strings.Split(value, "/")) == 1 {
- if !helper.IsStandardResourceName(value) {
- return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
- }
- }
- return allErrs
- }
- // ValidatePodLogOptions checks if options that are set are at the correct
- // value. Any incorrect value will be returned to the ErrorList.
- func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList {
- allErrs := field.ErrorList{}
- if opts.TailLines != nil && *opts.TailLines < 0 {
- allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg))
- }
- if opts.LimitBytes != nil && *opts.LimitBytes < 1 {
- allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0"))
- }
- switch {
- case opts.SinceSeconds != nil && opts.SinceTime != nil:
- allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified"))
- case opts.SinceSeconds != nil:
- if *opts.SinceSeconds < 1 {
- allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0"))
- }
- }
- return allErrs
- }
- // AccumulateUniqueHostPorts checks all the containers for duplicates ports. Any
- // duplicate port will be returned in the ErrorList.
- func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- for ci, ctr := range containers {
- idxPath := fldPath.Index(ci)
- portsPath := idxPath.Child("ports")
- for pi := range ctr.Ports {
- idxPath := portsPath.Index(pi)
- port := ctr.Ports[pi].HostPort
- if port == 0 {
- continue
- }
- str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol)
- if accumulator.Has(str) {
- allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str))
- } else {
- accumulator.Insert(str)
- }
- }
- }
- return allErrs
- }
|