mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
fix pod template spec validation missing in sts
Signed-off-by: joey <zchengjoey@gmail.com>
This commit is contained in:
@@ -38,6 +38,8 @@ import (
|
||||
type StatefulSetValidationOptions struct {
|
||||
// Allow invalid DNS1123 ServiceName
|
||||
AllowInvalidServiceName bool
|
||||
// Skip validating pod template spec, which is used for StatefulSet update
|
||||
SkipValidatePodTemplateSpec bool
|
||||
}
|
||||
|
||||
// ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.
|
||||
@@ -51,7 +53,8 @@ func ValidateStatefulSetName(name string, prefix bool) []string {
|
||||
}
|
||||
|
||||
// ValidatePodTemplateSpecForStatefulSet validates the given template and ensures that it is in accordance with the desired selector.
|
||||
func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path, opts apivalidation.PodValidationOptions,
|
||||
setOpts StatefulSetValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if template == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath, ""))
|
||||
@@ -63,10 +66,9 @@ func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, select
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
|
||||
}
|
||||
}
|
||||
// TODO: Add validation for PodSpec, currently this will check volumes, which we know will
|
||||
// fail. We should really check that the union of the given volumes and volumeClaims match
|
||||
// volume mounts in the containers.
|
||||
// allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...)
|
||||
if !setOpts.SkipValidatePodTemplateSpec {
|
||||
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath, opts)...)
|
||||
}
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, &template.Spec, fldPath.Child("annotations"), opts)...)
|
||||
@@ -94,6 +96,22 @@ func ValidatePersistentVolumeClaimRetentionPolicy(policy *apps.StatefulSetPersis
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func volumesToAddForTemplates(spec *apps.StatefulSetSpec) map[string]api.Volume {
|
||||
volumes := make(map[string]api.Volume)
|
||||
templates := spec.VolumeClaimTemplates
|
||||
for i := range templates {
|
||||
volumes[templates[i].Name] = api.Volume{
|
||||
Name: templates[i].Name,
|
||||
VolumeSource: api.VolumeSource{
|
||||
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: templates[i].Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return volumes
|
||||
}
|
||||
|
||||
// ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set.
|
||||
func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions, setOpts StatefulSetValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
@@ -158,7 +176,28 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, op
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"), opts)...)
|
||||
templateToValidate := &spec.Template
|
||||
if len(spec.Template.Spec.Subdomain) > 0 || len(spec.Template.Spec.Hostname) > 0 || len(spec.VolumeClaimTemplates) > 0 {
|
||||
templateToValidate = templateToValidate.DeepCopy()
|
||||
// overwritten by controller to spec.ServiceName in initIdentity, don't validate
|
||||
templateToValidate.Spec.Subdomain = ""
|
||||
// overwritten by controller to pod.Name in initIdentity, don't validate
|
||||
templateToValidate.Spec.Hostname = ""
|
||||
if len(spec.VolumeClaimTemplates) > 0 {
|
||||
templateVolumes := volumesToAddForTemplates(spec)
|
||||
newVolumes := make([]api.Volume, 0, len(templateVolumes))
|
||||
for _, v := range templateVolumes {
|
||||
newVolumes = append(newVolumes, v)
|
||||
}
|
||||
for _, v := range templateToValidate.Spec.Volumes {
|
||||
if _, ok := templateVolumes[v.Name]; !ok {
|
||||
newVolumes = append(newVolumes, v)
|
||||
}
|
||||
}
|
||||
templateToValidate.Spec.Volumes = newVolumes
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(templateToValidate, selector, fldPath.Child("template"), opts, setOpts)...)
|
||||
}
|
||||
|
||||
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
|
||||
@@ -193,6 +232,10 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, op
|
||||
setOpts := StatefulSetValidationOptions{
|
||||
AllowInvalidServiceName: true, // serviceName is immutable, tolerate existing invalid names on update
|
||||
}
|
||||
// In order to tolerate the existing sts, we choose to skip the validation error of old sts podTemplateSpec.
|
||||
if len(ValidateStatefulSetSpec(&oldStatefulSet.Spec, nil, opts, setOpts)) > 0 {
|
||||
setOpts.SkipValidatePodTemplateSpec = true
|
||||
}
|
||||
allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"), opts, setOpts)...)
|
||||
|
||||
// statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this
|
||||
|
||||
@@ -141,6 +141,12 @@ func tweakPVCTemplate(pvc ...api.PersistentVolumeClaim) statefulSetTweak {
|
||||
}
|
||||
}
|
||||
|
||||
func tweakVolumes(volumes ...api.Volume) statefulSetTweak {
|
||||
return func(ss *apps.StatefulSet) {
|
||||
ss.Spec.Template.Spec.Volumes = volumes
|
||||
}
|
||||
}
|
||||
|
||||
func tweakUpdateStrategyType(t apps.StatefulSetUpdateStrategyType) statefulSetTweak {
|
||||
return func(ss *apps.StatefulSet) {
|
||||
ss.Spec.UpdateStrategy.Type = t
|
||||
@@ -201,6 +207,18 @@ func tweakServiceName(name string) statefulSetTweak {
|
||||
}
|
||||
}
|
||||
|
||||
func tweakSubdomain(name string) statefulSetTweak {
|
||||
return func(ss *apps.StatefulSet) {
|
||||
ss.Spec.Template.Spec.Subdomain = name
|
||||
}
|
||||
}
|
||||
|
||||
func tweakHostname(name string) statefulSetTweak {
|
||||
return func(ss *apps.StatefulSet) {
|
||||
ss.Spec.Template.Spec.Hostname = name
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStatefulSet(t *testing.T) {
|
||||
validLabels := map[string]string{"a": "b"}
|
||||
validPodTemplate := api.PodTemplate{
|
||||
@@ -209,9 +227,10 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
Labels: validLabels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -235,7 +254,9 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
ContainerPort: 12345,
|
||||
Protocol: api.ProtocolTCP,
|
||||
}},
|
||||
TerminationMessagePolicy: api.TerminationMessageReadFile,
|
||||
}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -253,6 +274,33 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
podTemplateWithVolumeMounts := api.PodTemplate{
|
||||
Template: api.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: validLabels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{
|
||||
Name: "abc",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
Ports: []api.ContainerPort{{
|
||||
ContainerPort: 12345,
|
||||
Protocol: api.ProtocolTCP,
|
||||
}},
|
||||
VolumeMounts: []api.VolumeMount{{
|
||||
Name: "test",
|
||||
MountPath: "/test",
|
||||
}},
|
||||
TerminationMessagePolicy: api.TerminationMessageReadFile,
|
||||
}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
invalidTime := int64(60)
|
||||
invalidPodTemplate2 := api.PodTemplate{
|
||||
Template: api.PodTemplateSpec{
|
||||
@@ -260,9 +308,10 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
Labels: validLabels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
ActiveDeadlineSeconds: &invalidTime,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
ActiveDeadlineSeconds: &invalidTime,
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -317,6 +366,104 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
tweakReplicas(3),
|
||||
tweakOrdinalsStart(2),
|
||||
),
|
||||
}, {
|
||||
name: "invalid subdomain",
|
||||
set: mkStatefulSet(&validPodTemplate,
|
||||
tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "invalid hostname",
|
||||
set: mkStatefulSet(&validPodTemplate,
|
||||
tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "both invalid subdomain and hostname",
|
||||
set: mkStatefulSet(&validPodTemplate,
|
||||
tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "volume template in pod volumeMounts",
|
||||
set: mkStatefulSet(&podTemplateWithVolumeMounts,
|
||||
tweakPVCTemplate(api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}, {
|
||||
name: "volume templates already present in volumes",
|
||||
set: mkStatefulSet(&podTemplateWithVolumeMounts,
|
||||
tweakVolumes([]api.Volume{
|
||||
{
|
||||
Name: "test1",
|
||||
VolumeSource: api.VolumeSource{
|
||||
EmptyDir: &api.EmptyDirVolumeSource{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
VolumeSource: api.VolumeSource{
|
||||
EmptyDir: &api.EmptyDirVolumeSource{},
|
||||
},
|
||||
},
|
||||
}...),
|
||||
tweakPVCTemplate([]api.PersistentVolumeClaim{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test1"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test2"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}}...),
|
||||
),
|
||||
}, {
|
||||
name: "volume templates present in volumes",
|
||||
set: mkStatefulSet(&podTemplateWithVolumeMounts,
|
||||
tweakVolumes([]api.Volume{
|
||||
{
|
||||
Name: "test",
|
||||
VolumeSource: api.VolumeSource{
|
||||
EmptyDir: &api.EmptyDirVolumeSource{},
|
||||
},
|
||||
}}...),
|
||||
tweakPVCTemplate(api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -407,6 +554,7 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")),
|
||||
errs: field.ErrorList{
|
||||
field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
|
||||
field.Required(field.NewPath("spec", "template", "spec", "restartPolicy"), ""),
|
||||
},
|
||||
}, {
|
||||
name: "invalid update strategy",
|
||||
@@ -465,6 +613,7 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
set: mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)),
|
||||
errs: field.ErrorList{
|
||||
field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""),
|
||||
field.Required(field.NewPath("spec", "template", "spec", "containers"), ""),
|
||||
},
|
||||
}, {
|
||||
name: "empty PersistentVolumeClaimRetentionPolicy",
|
||||
@@ -534,6 +683,48 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
errs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "serviceName"), "Invalid.Name", ""),
|
||||
},
|
||||
}, {
|
||||
name: "missing volume in pod volumeMounts",
|
||||
set: mkStatefulSet(&podTemplateWithVolumeMounts),
|
||||
errs: field.ErrorList{
|
||||
field.NotFound(field.NewPath("spec", "template", "spec", "containers[0].volumeMounts[0].name"), "test"),
|
||||
},
|
||||
}, {
|
||||
name: "want test volumeMount but have test1 volumeTemplate",
|
||||
set: mkStatefulSet(&podTemplateWithVolumeMounts,
|
||||
tweakPVCTemplate(api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test1"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: api.VolumeResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
errs: field.ErrorList{
|
||||
field.NotFound(field.NewPath("spec", "template", "spec", "containers[0].volumeMounts[0].name"), "test"),
|
||||
},
|
||||
}, {
|
||||
name: "duplicate volumes",
|
||||
set: mkStatefulSet(&validPodTemplate,
|
||||
tweakVolumes([]api.Volume{
|
||||
{
|
||||
Name: "test1",
|
||||
VolumeSource: api.VolumeSource{
|
||||
EmptyDir: &api.EmptyDirVolumeSource{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test1",
|
||||
VolumeSource: api.VolumeSource{
|
||||
EmptyDir: &api.EmptyDirVolumeSource{},
|
||||
},
|
||||
},
|
||||
}...)),
|
||||
errs: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec", "template", "spec", "volumes[1]", "name"), "test1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -566,9 +757,10 @@ func generateStatefulSetSpec(minSeconds int32) *apps.StatefulSetSpec {
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -758,9 +950,10 @@ func TestValidateStatefulSetUpdate(t *testing.T) {
|
||||
Labels: validLabels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -769,10 +962,42 @@ func TestValidateStatefulSetUpdate(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: validLabels2,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
validPodTemplateWithVolumeMounts := api.PodTemplate{
|
||||
Template: api.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: validLabels,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
Containers: []api.Container{{
|
||||
Name: "abc",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: api.TerminationMessageReadFile,
|
||||
VolumeMounts: []api.VolumeMount{{
|
||||
Name: "pvc-abc",
|
||||
MountPath: "/test",
|
||||
}},
|
||||
}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
|
||||
invalidPodTemplate := api.PodTemplate{
|
||||
Template: api.PodTemplateSpec{
|
||||
Spec: podtest.MakePodSpec(podtest.SetContainers()),
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: invalidLabels,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -815,7 +1040,7 @@ func TestValidateStatefulSetUpdate(t *testing.T) {
|
||||
|
||||
addContainersValidTemplate := validPodTemplate.DeepCopy()
|
||||
addContainersValidTemplate.Template.Spec.Containers = append(addContainersValidTemplate.Template.Spec.Containers,
|
||||
api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent"})
|
||||
api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile})
|
||||
if len(addContainersValidTemplate.Template.Spec.Containers) != len(validPodTemplate.Template.Spec.Containers)+1 {
|
||||
t.Errorf("failure during test setup: template %v should have more containers than template %v", addContainersValidTemplate, validPodTemplate)
|
||||
}
|
||||
@@ -882,6 +1107,38 @@ func TestValidateStatefulSetUpdate(t *testing.T) {
|
||||
name: "update with invalid .spec.serviceName",
|
||||
old: mkStatefulSet(&validPodTemplate, tweakServiceName("Invalid.Name")),
|
||||
update: mkStatefulSet(&validPodTemplate, tweakServiceName("Invalid.Name"), tweakReplicas(3)),
|
||||
}, {
|
||||
name: "invalid old spec (missing volume template) should skip validation",
|
||||
old: mkStatefulSet(&validPodTemplateWithVolumeMounts),
|
||||
update: mkStatefulSet(&validPodTemplateWithVolumeMounts, tweakReplicas(3)),
|
||||
}, {
|
||||
name: "volume mount ",
|
||||
old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
|
||||
update: mkStatefulSet(&validPodTemplateWithVolumeMounts, tweakPVCTemplate(validPVCTemplate), tweakReplicas(3)),
|
||||
}, {
|
||||
name: "valid old spec, invalid subdomain in new spec",
|
||||
old: mkStatefulSet(&validPodTemplate),
|
||||
update: mkStatefulSet(&validPodTemplate,
|
||||
tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "valid old spec, invalid hostname in new spec",
|
||||
old: mkStatefulSet(&validPodTemplate),
|
||||
update: mkStatefulSet(&validPodTemplate,
|
||||
tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "both invalid subdomain",
|
||||
old: mkStatefulSet(&validPodTemplate, tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals")),
|
||||
update: mkStatefulSet(&validPodTemplate,
|
||||
tweakSubdomain("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
}, {
|
||||
name: "both invalid hostname",
|
||||
old: mkStatefulSet(&validPodTemplate, tweakHostname("NoUppercaseOrSpecialCharsLike=Equals")),
|
||||
update: mkStatefulSet(&validPodTemplate,
|
||||
tweakHostname("NoUppercaseOrSpecialCharsLike=Equals"),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -950,6 +1207,19 @@ func TestValidateStatefulSetUpdate(t *testing.T) {
|
||||
errs: field.ErrorList{
|
||||
field.Forbidden(field.NewPath("spec"), ""),
|
||||
},
|
||||
}, {
|
||||
name: "valid old spec but invalid new spec",
|
||||
old: mkStatefulSet(&validPodTemplate),
|
||||
update: mkStatefulSet(&invalidPodTemplate),
|
||||
errs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "template", "labels"),
|
||||
"NoUppercaseOrSpecialCharsLike=Equals", ""),
|
||||
field.Invalid(field.NewPath("spec", "template", "labels"),
|
||||
"map[string]string{\\\"NoUppercaseOrSpecialCharsLike=Equals\\\":\\\"b\\\"}", ""),
|
||||
field.Invalid(field.NewPath("spec", "template", "metadata", "labels"),
|
||||
"map[string]string{\\\"NoUppercaseOrSpecialCharsLike=Equals\\\":\\\"b\\\"}", ""),
|
||||
field.Required(field.NewPath("spec", "template", "spec", "containers"), ""),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// TODO: allow for global factory override
|
||||
@@ -68,13 +69,15 @@ func validNewStatefulSet() *apps.StatefulSet {
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Image: "test_image",
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
Name: "test",
|
||||
Image: "test_image",
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
TerminationMessagePolicy: api.TerminationMessageReadFile,
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
Replicas: 7,
|
||||
|
||||
@@ -48,9 +48,10 @@ func TestStatefulSetStrategy(t *testing.T) {
|
||||
Labels: validSelector,
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||
TerminationGracePeriodSeconds: ptr.To[int64](30),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func GetEtcdStorageDataForNamespaceServedAt(namespace string, v string, isEmulat
|
||||
IntroducedVersion: "1.9",
|
||||
},
|
||||
gvr("apps", "v1", "statefulsets"): {
|
||||
Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
|
||||
Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"restartPolicy": "Always", "terminationGracePeriodSeconds": 30, "containers": [{"image": "` + image + `", "name": "container6", "terminationMessagePolicy": "File"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
|
||||
IntroducedVersion: "1.9",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user