mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #13286 from nikhiljindal/validateDeployment
Auto commit by PR queue bot
This commit is contained in:
commit
e5ac413311
@ -53,13 +53,19 @@ var portNameErrorMsg string = fmt.Sprintf(`must be an IANA_SVC_NAME (at most 15
|
||||
|
||||
const totalAnnotationSizeLimitB int = 64 * (1 << 10) // 64 kB
|
||||
|
||||
func ValidateLabelName(labelName, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if !util.IsQualifiedName(labelName) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, labelName, qualifiedNameErrorMsg))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateLabels validates that a set of labels are correctly defined.
|
||||
func ValidateLabels(labels map[string]string, field string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
for k, v := range labels {
|
||||
if !util.IsQualifiedName(k) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(field, k, qualifiedNameErrorMsg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateLabelName(k, field)...)
|
||||
if !util.IsValidLabelValue(v) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(field, v, labelValueErrorMsg))
|
||||
}
|
||||
@ -202,6 +208,15 @@ func NameIsDNS952Label(name string, prefix bool) (bool, string) {
|
||||
return false, DNS952LabelErrorMsg
|
||||
}
|
||||
|
||||
// Validates that given value is not negative.
|
||||
func ValidatePositiveField(value int64, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if value < 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, value, isNegativeErrorMsg))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
|
||||
// been performed.
|
||||
func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc) errs.ValidationErrorList {
|
||||
@ -224,9 +239,7 @@ func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn Val
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("name", meta.Name, qualifier))
|
||||
}
|
||||
}
|
||||
if meta.Generation < 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("generation", meta.Generation, isNegativeErrorMsg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidatePositiveField(meta.Generation, "generation")...)
|
||||
if requiresNamespace {
|
||||
if len(meta.Namespace) == 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("namespace"))
|
||||
@ -1206,34 +1219,49 @@ func ValidateReplicationControllerUpdate(oldController, controller *api.Replicat
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// Validates that the given selector is non-empty.
|
||||
func ValidateNonEmptySelector(selectorMap map[string]string, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
selector := labels.Set(selectorMap).AsSelector()
|
||||
if selector.Empty() {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired(fieldName))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// Validates the given template and ensures that it is in accordance with the desrired selector and replicas.
|
||||
func ValidatePodTemplateSpecForRC(template *api.PodTemplateSpec, selectorMap map[string]string, replicas int, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if template == nil {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired(fieldName))
|
||||
} else {
|
||||
selector := labels.Set(selectorMap).AsSelector()
|
||||
if !selector.Empty() {
|
||||
// Verify that the RC selector matches the labels in template.
|
||||
labels := labels.Set(template.Labels)
|
||||
if !selector.Matches(labels) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName+".metadata.labels", template.Labels, "selector does not match labels in "+fieldName))
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidatePodTemplateSpec(template).Prefix(fieldName)...)
|
||||
if replicas > 1 {
|
||||
allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(template.Spec.Volumes).Prefix(fieldName+".spec.volumes")...)
|
||||
}
|
||||
// RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
|
||||
if template.Spec.RestartPolicy != api.RestartPolicyAlways {
|
||||
allErrs = append(allErrs, errs.NewFieldValueNotSupported(fieldName+".spec.restartPolicy", template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateReplicationControllerSpec tests if required fields in the replication controller spec are set.
|
||||
func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
|
||||
selector := labels.Set(spec.Selector).AsSelector()
|
||||
if selector.Empty() {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("selector"))
|
||||
}
|
||||
if spec.Replicas < 0 {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("replicas", spec.Replicas, isNegativeErrorMsg))
|
||||
}
|
||||
|
||||
if spec.Template == nil {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("template"))
|
||||
} else {
|
||||
labels := labels.Set(spec.Template.Labels)
|
||||
if !selector.Matches(labels) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("template.metadata.labels", spec.Template.Labels, "selector does not match template"))
|
||||
}
|
||||
allErrs = append(allErrs, ValidatePodTemplateSpec(spec.Template).Prefix("template")...)
|
||||
if spec.Replicas > 1 {
|
||||
allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes).Prefix("template.spec.volumes")...)
|
||||
}
|
||||
// RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
|
||||
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
|
||||
allErrs = append(allErrs, errs.NewFieldValueNotSupported("template.spec.restartPolicy", spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidateNonEmptySelector(spec.Selector, "selector")...)
|
||||
allErrs = append(allErrs, ValidatePositiveField(int64(spec.Replicas), "replicas")...)
|
||||
allErrs = append(allErrs, ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, "template")...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -234,7 +234,6 @@ func TestValidateAnnotations(t *testing.T) {
|
||||
}
|
||||
|
||||
func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume {
|
||||
|
||||
objMeta := api.ObjectMeta{Name: name}
|
||||
if namespace != "" {
|
||||
objMeta.Namespace = namespace
|
||||
@ -247,7 +246,6 @@ func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *a
|
||||
}
|
||||
|
||||
func TestValidatePersistentVolumes(t *testing.T) {
|
||||
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
volume *api.PersistentVolume
|
||||
@ -358,7 +356,6 @@ func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeCla
|
||||
}
|
||||
|
||||
func TestValidatePersistentVolumeClaim(t *testing.T) {
|
||||
|
||||
scenarios := map[string]struct {
|
||||
isExpectedFailure bool
|
||||
claim *api.PersistentVolumeClaim
|
||||
@ -802,7 +799,6 @@ func TestValidatePullPolicy(t *testing.T) {
|
||||
t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getResourceLimits(cpu, memory string) api.ResourceList {
|
||||
@ -2144,7 +2140,6 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
|
||||
t.Errorf("expected failure: %s", testName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestValidateReplicationController(t *testing.T) {
|
||||
|
@ -787,12 +787,7 @@ func deepCopy_expapi_DeploymentList(in DeploymentList, out *DeploymentList, c *c
|
||||
}
|
||||
|
||||
func deepCopy_expapi_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *conversion.Cloner) error {
|
||||
if in.Replicas != nil {
|
||||
out.Replicas = new(int)
|
||||
*out.Replicas = *in.Replicas
|
||||
} else {
|
||||
out.Replicas = nil
|
||||
}
|
||||
out.Replicas = in.Replicas
|
||||
if in.Selector != nil {
|
||||
out.Selector = make(map[string]string)
|
||||
for key, val := range in.Selector {
|
||||
|
@ -179,9 +179,8 @@ type Deployment struct {
|
||||
}
|
||||
|
||||
type DeploymentSpec struct {
|
||||
// Number of desired pods. This is a pointer to distinguish between explicit
|
||||
// zero and not specified. Defaults to 1.
|
||||
Replicas *int `json:"replicas,omitempty"`
|
||||
// Number of desired pods.
|
||||
Replicas int `json:"replicas,omitempty"`
|
||||
|
||||
// Label selector for pods. Existing ReplicationControllers whose pods are
|
||||
// selected by this will be scaled down.
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/conversion"
|
||||
"k8s.io/kubernetes/pkg/expapi"
|
||||
)
|
||||
|
||||
func addConversionFuncs() {
|
||||
@ -29,6 +30,9 @@ func addConversionFuncs() {
|
||||
err := api.Scheme.AddConversionFuncs(
|
||||
convert_api_PodSpec_To_v1_PodSpec,
|
||||
convert_v1_PodSpec_To_api_PodSpec,
|
||||
convert_expapi_DeploymentSpec_To_v1_DeploymentSpec,
|
||||
convert_v1_DeploymentSpec_To_expapi_DeploymentSpec,
|
||||
convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy,
|
||||
)
|
||||
if err != nil {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
@ -169,3 +173,86 @@ func convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conve
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_expapi_DeploymentSpec_To_v1_DeploymentSpec(in *expapi.DeploymentSpec, out *DeploymentSpec, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*expapi.DeploymentSpec))(in)
|
||||
}
|
||||
out.Replicas = new(int)
|
||||
*out.Replicas = in.Replicas
|
||||
if in.Selector != nil {
|
||||
out.Selector = make(map[string]string)
|
||||
for key, val := range in.Selector {
|
||||
out.Selector[key] = val
|
||||
}
|
||||
} else {
|
||||
out.Selector = nil
|
||||
}
|
||||
if in.Template != nil {
|
||||
out.Template = new(v1.PodTemplateSpec)
|
||||
if err := convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Template = nil
|
||||
}
|
||||
if err := convert_expapi_DeploymentStrategy_To_v1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.UniqueLabelKey != nil {
|
||||
out.UniqueLabelKey = new(string)
|
||||
*out.UniqueLabelKey = *in.UniqueLabelKey
|
||||
} else {
|
||||
out.UniqueLabelKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_DeploymentSpec_To_expapi_DeploymentSpec(in *DeploymentSpec, out *expapi.DeploymentSpec, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*DeploymentSpec))(in)
|
||||
}
|
||||
out.Replicas = *in.Replicas
|
||||
if in.Selector != nil {
|
||||
out.Selector = make(map[string]string)
|
||||
for key, val := range in.Selector {
|
||||
out.Selector[key] = val
|
||||
}
|
||||
} else {
|
||||
out.Selector = nil
|
||||
}
|
||||
if in.Template != nil {
|
||||
out.Template = new(api.PodTemplateSpec)
|
||||
if err := convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Template = nil
|
||||
}
|
||||
if err := convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.UniqueLabelKey != nil {
|
||||
out.UniqueLabelKey = new(string)
|
||||
*out.UniqueLabelKey = *in.UniqueLabelKey
|
||||
} else {
|
||||
out.UniqueLabelKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy(in *DeploymentStrategy, out *expapi.DeploymentStrategy, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*DeploymentStrategy))(in)
|
||||
}
|
||||
out.Type = expapi.DeploymentType(in.Type)
|
||||
if in.RollingUpdate != nil {
|
||||
out.RollingUpdate = new(expapi.RollingUpdateDeployment)
|
||||
if err := convert_v1_RollingUpdateDeployment_To_expapi_RollingUpdateDeployment(in.RollingUpdate, out.RollingUpdate, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.RollingUpdate = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1512,44 +1512,6 @@ func convert_expapi_DeploymentList_To_v1_DeploymentList(in *expapi.DeploymentLis
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_expapi_DeploymentSpec_To_v1_DeploymentSpec(in *expapi.DeploymentSpec, out *DeploymentSpec, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*expapi.DeploymentSpec))(in)
|
||||
}
|
||||
if in.Replicas != nil {
|
||||
out.Replicas = new(int)
|
||||
*out.Replicas = *in.Replicas
|
||||
} else {
|
||||
out.Replicas = nil
|
||||
}
|
||||
if in.Selector != nil {
|
||||
out.Selector = make(map[string]string)
|
||||
for key, val := range in.Selector {
|
||||
out.Selector[key] = val
|
||||
}
|
||||
} else {
|
||||
out.Selector = nil
|
||||
}
|
||||
if in.Template != nil {
|
||||
out.Template = new(v1.PodTemplateSpec)
|
||||
if err := convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Template = nil
|
||||
}
|
||||
if err := convert_expapi_DeploymentStrategy_To_v1_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.UniqueLabelKey != nil {
|
||||
out.UniqueLabelKey = new(string)
|
||||
*out.UniqueLabelKey = *in.UniqueLabelKey
|
||||
} else {
|
||||
out.UniqueLabelKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_expapi_DeploymentStatus_To_v1_DeploymentStatus(in *expapi.DeploymentStatus, out *DeploymentStatus, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*expapi.DeploymentStatus))(in)
|
||||
@ -1929,44 +1891,6 @@ func convert_v1_DeploymentList_To_expapi_DeploymentList(in *DeploymentList, out
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_DeploymentSpec_To_expapi_DeploymentSpec(in *DeploymentSpec, out *expapi.DeploymentSpec, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*DeploymentSpec))(in)
|
||||
}
|
||||
if in.Replicas != nil {
|
||||
out.Replicas = new(int)
|
||||
*out.Replicas = *in.Replicas
|
||||
} else {
|
||||
out.Replicas = nil
|
||||
}
|
||||
if in.Selector != nil {
|
||||
out.Selector = make(map[string]string)
|
||||
for key, val := range in.Selector {
|
||||
out.Selector[key] = val
|
||||
}
|
||||
} else {
|
||||
out.Selector = nil
|
||||
}
|
||||
if in.Template != nil {
|
||||
out.Template = new(api.PodTemplateSpec)
|
||||
if err := convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Template = nil
|
||||
}
|
||||
if err := convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy(&in.Strategy, &out.Strategy, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.UniqueLabelKey != nil {
|
||||
out.UniqueLabelKey = new(string)
|
||||
*out.UniqueLabelKey = *in.UniqueLabelKey
|
||||
} else {
|
||||
out.UniqueLabelKey = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_DeploymentStatus_To_expapi_DeploymentStatus(in *DeploymentStatus, out *expapi.DeploymentStatus, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*DeploymentStatus))(in)
|
||||
@ -1976,22 +1900,6 @@ func convert_v1_DeploymentStatus_To_expapi_DeploymentStatus(in *DeploymentStatus
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy(in *DeploymentStrategy, out *expapi.DeploymentStrategy, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*DeploymentStrategy))(in)
|
||||
}
|
||||
out.Type = expapi.DeploymentType(in.Type)
|
||||
if in.RollingUpdate != nil {
|
||||
out.RollingUpdate = new(expapi.RollingUpdateDeployment)
|
||||
if err := convert_v1_RollingUpdateDeployment_To_expapi_RollingUpdateDeployment(in.RollingUpdate, out.RollingUpdate, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.RollingUpdate = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_v1_HorizontalPodAutoscaler_To_expapi_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *expapi.HorizontalPodAutoscaler, s conversion.Scope) error {
|
||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||
defaulting.(func(*HorizontalPodAutoscaler))(in)
|
||||
@ -2262,7 +2170,6 @@ func init() {
|
||||
convert_expapi_DaemonStatus_To_v1_DaemonStatus,
|
||||
convert_expapi_Daemon_To_v1_Daemon,
|
||||
convert_expapi_DeploymentList_To_v1_DeploymentList,
|
||||
convert_expapi_DeploymentSpec_To_v1_DeploymentSpec,
|
||||
convert_expapi_DeploymentStatus_To_v1_DeploymentStatus,
|
||||
convert_expapi_DeploymentStrategy_To_v1_DeploymentStrategy,
|
||||
convert_expapi_Deployment_To_v1_Deployment,
|
||||
@ -2289,9 +2196,7 @@ func init() {
|
||||
convert_v1_DaemonStatus_To_expapi_DaemonStatus,
|
||||
convert_v1_Daemon_To_expapi_Daemon,
|
||||
convert_v1_DeploymentList_To_expapi_DeploymentList,
|
||||
convert_v1_DeploymentSpec_To_expapi_DeploymentSpec,
|
||||
convert_v1_DeploymentStatus_To_expapi_DeploymentStatus,
|
||||
convert_v1_DeploymentStrategy_To_expapi_DeploymentStrategy,
|
||||
convert_v1_Deployment_To_expapi_Deployment,
|
||||
convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource,
|
||||
convert_v1_EnvVarSource_To_api_EnvVarSource,
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apivalidation "k8s.io/kubernetes/pkg/api/validation"
|
||||
"k8s.io/kubernetes/pkg/expapi"
|
||||
@ -156,3 +158,102 @@ func ValidateDaemonSpec(spec *expapi.DaemonSpec) errs.ValidationErrorList {
|
||||
func ValidateDaemonName(name string, prefix bool) (bool, string) {
|
||||
return apivalidation.NameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
// Validates that the given name can be used as a deployment name.
|
||||
func ValidateDeploymentName(name string, prefix bool) (bool, string) {
|
||||
return apivalidation.NameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
func ValidatePositiveIntOrPercent(intOrPercent util.IntOrString, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if intOrPercent.Kind == util.IntstrString {
|
||||
if !util.IsValidPercent(intOrPercent.StrVal) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, intOrPercent, "value should be int(5) or percentage(5%)"))
|
||||
}
|
||||
|
||||
} else if intOrPercent.Kind == util.IntstrInt {
|
||||
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(intOrPercent.IntVal), fieldName)...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func getPercentValue(intOrStringValue util.IntOrString) (int, bool) {
|
||||
if intOrStringValue.Kind != util.IntstrString || !util.IsValidPercent(intOrStringValue.StrVal) {
|
||||
return 0, false
|
||||
}
|
||||
value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1])
|
||||
return value, true
|
||||
}
|
||||
|
||||
func getIntOrPercentValue(intOrStringValue util.IntOrString) int {
|
||||
value, isPercent := getPercentValue(intOrStringValue)
|
||||
if isPercent {
|
||||
return value
|
||||
}
|
||||
return intOrStringValue.IntVal
|
||||
}
|
||||
|
||||
func IsNotMoreThan100Percent(intOrStringValue util.IntOrString, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
value, isPercent := getPercentValue(intOrStringValue)
|
||||
if !isPercent || value <= 100 {
|
||||
return nil
|
||||
}
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName, intOrStringValue, "should not be more than 100%"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateRollingUpdateDeployment(rollingUpdate *expapi.RollingUpdateDeployment, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fieldName+"maxUnavailable")...)
|
||||
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fieldName+".maxSurge")...)
|
||||
if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 {
|
||||
// Both MaxSurge and MaxUnavailable cannot be zero.
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid(fieldName+".maxUnavailable", rollingUpdate.MaxUnavailable, "cannot be 0 when maxSurge is 0 as well"))
|
||||
}
|
||||
// Validate that MaxUnavailable is not more than 100%.
|
||||
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fieldName+".maxUnavailable")...)
|
||||
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(rollingUpdate.MinReadySeconds), fieldName+".minReadySeconds")...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateDeploymentStrategy(strategy *expapi.DeploymentStrategy, fieldName string) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if strategy.RollingUpdate == nil {
|
||||
return allErrs
|
||||
}
|
||||
switch strategy.Type {
|
||||
case expapi.DeploymentRecreate:
|
||||
allErrs = append(allErrs, errs.NewFieldForbidden("rollingUpdate", "rollingUpdate should be nil when strategy type is "+expapi.DeploymentRecreate))
|
||||
case expapi.DeploymentRollingUpdate:
|
||||
allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, "rollingUpdate")...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// Validates given deployment spec.
|
||||
func ValidateDeploymentSpec(spec *expapi.DeploymentSpec) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonEmptySelector(spec.Selector, "selector")...)
|
||||
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), "replicas")...)
|
||||
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, "template")...)
|
||||
allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...)
|
||||
if spec.UniqueLabelKey != nil {
|
||||
allErrs = append(allErrs, apivalidation.ValidateLabelName(*spec.UniqueLabelKey, "uniqueLabel")...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateDeploymentUpdate(old, update *expapi.Deployment) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta).Prefix("metadata")...)
|
||||
allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec).Prefix("spec")...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateDeployment(obj *expapi.Deployment) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName).Prefix("metadata")...)
|
||||
allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...)
|
||||
return allErrs
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/expapi"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
errors "k8s.io/kubernetes/pkg/util/fielderrors"
|
||||
)
|
||||
|
||||
@ -540,3 +541,120 @@ func TestValidateDaemon(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validDeployment() *expapi.Deployment {
|
||||
return &expapi.Deployment{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Spec: expapi.DeploymentSpec{
|
||||
Selector: map[string]string{
|
||||
"name": "abc",
|
||||
},
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"name": "abc",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSDefault,
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "image",
|
||||
ImagePullPolicy: api.PullNever,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDeployment(t *testing.T) {
|
||||
successCases := []*expapi.Deployment{
|
||||
validDeployment(),
|
||||
}
|
||||
for _, successCase := range successCases {
|
||||
if errs := ValidateDeployment(successCase); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]*expapi.Deployment{}
|
||||
errorCases["metadata.name: required value"] = &expapi.Deployment{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
}
|
||||
// selector should match the labels in pod template.
|
||||
invalidSelectorDeployment := validDeployment()
|
||||
invalidSelectorDeployment.Spec.Selector = map[string]string{
|
||||
"name": "def",
|
||||
}
|
||||
errorCases["selector does not match labels"] = invalidSelectorDeployment
|
||||
|
||||
// RestartPolicy should be always.
|
||||
invalidRestartPolicyDeployment := validDeployment()
|
||||
invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever
|
||||
errorCases["unsupported value 'Never'"] = invalidRestartPolicyDeployment
|
||||
|
||||
// invalid unique label key.
|
||||
invalidUniqueLabelDeployment := validDeployment()
|
||||
invalidUniqueLabel := "abc/def/ghi"
|
||||
invalidUniqueLabelDeployment.Spec.UniqueLabelKey = &invalidUniqueLabel
|
||||
errorCases["spec.uniqueLabel: invalid value"] = invalidUniqueLabelDeployment
|
||||
|
||||
// rollingUpdate should be nil for recreate.
|
||||
invalidRecreateDeployment := validDeployment()
|
||||
invalidRecreateDeployment.Spec.Strategy = expapi.DeploymentStrategy{
|
||||
Type: expapi.DeploymentRecreate,
|
||||
RollingUpdate: &expapi.RollingUpdateDeployment{},
|
||||
}
|
||||
errorCases["rollingUpdate should be nil when strategy type is Recreate"] = invalidRecreateDeployment
|
||||
|
||||
// MaxSurge should be in the form of 20%.
|
||||
invalidMaxSurgeDeployment := validDeployment()
|
||||
invalidMaxSurgeDeployment.Spec.Strategy = expapi.DeploymentStrategy{
|
||||
Type: expapi.DeploymentRollingUpdate,
|
||||
RollingUpdate: &expapi.RollingUpdateDeployment{
|
||||
MaxSurge: util.NewIntOrStringFromString("20Percent"),
|
||||
},
|
||||
}
|
||||
errorCases["value should be int(5) or percentage(5%)"] = invalidMaxSurgeDeployment
|
||||
|
||||
// MaxSurge and MaxUnavailable cannot both be zero.
|
||||
invalidRollingUpdateDeployment := validDeployment()
|
||||
invalidRollingUpdateDeployment.Spec.Strategy = expapi.DeploymentStrategy{
|
||||
Type: expapi.DeploymentRollingUpdate,
|
||||
RollingUpdate: &expapi.RollingUpdateDeployment{
|
||||
MaxSurge: util.NewIntOrStringFromString("0%"),
|
||||
MaxUnavailable: util.NewIntOrStringFromInt(0),
|
||||
},
|
||||
}
|
||||
errorCases["cannot be 0 when maxSurge is 0 as well"] = invalidRollingUpdateDeployment
|
||||
|
||||
// MaxUnavailable should not be more than 100%.
|
||||
invalidMaxUnavailableDeployment := validDeployment()
|
||||
invalidMaxUnavailableDeployment.Spec.Strategy = expapi.DeploymentStrategy{
|
||||
Type: expapi.DeploymentRollingUpdate,
|
||||
RollingUpdate: &expapi.RollingUpdateDeployment{
|
||||
MaxUnavailable: util.NewIntOrStringFromString("110%"),
|
||||
},
|
||||
}
|
||||
errorCases["should not be more than 100%"] = invalidMaxUnavailableDeployment
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateDeployment(v)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
} else if !strings.Contains(errs[0].Error(), k) {
|
||||
t.Errorf("unexpected error: %v, expected: %s", errs[0], k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,3 +139,11 @@ func IsValidPortName(port string) bool {
|
||||
func IsValidIPv4(value string) bool {
|
||||
return net.ParseIP(value) != nil && net.ParseIP(value).To4() != nil
|
||||
}
|
||||
|
||||
const percentFmt string = "[0-9]+%"
|
||||
|
||||
var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
|
||||
|
||||
func IsValidPercent(percent string) bool {
|
||||
return percentRegexp.MatchString(percent)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user