Merge pull request #29034 from ivan4th/fix-init-container-update-validation

Automatic merge from submit-queue

Fix init container update validation for pods

Partial fix #26840

The remaining issues with `kubectl apply` on pods with init containers
are caused by temporary annotation-based representation and
will resolve themselves once init containers leave alpha state.
Also, this PR makes sure internal and external objects don't get mixed up by the
PATCH handler (see related issue #25106).

This PR is an alternative for #28557 which met criticism from @smarterclayton 
and @liggitt for working around the temporary issue with annotations.
#28557 is a full fix for #26840 and contains an e2e test that cannot pass
without the `VolumeMounts` workaround. As there appears to be no
good way to include an e2e test that's known to be failing in k8s source,
I've removed it from this PR.

Either this PR or #28557 should be applied, but not both.
This commit is contained in:
k8s-merge-robot 2016-07-20 22:56:00 -07:00 committed by GitHub
commit 165add8692
4 changed files with 121 additions and 20 deletions

View File

@ -1960,6 +1960,23 @@ func ValidatePodSecurityContext(securityContext *api.PodSecurityContext, spec *a
return allErrs
}
func validateContainerUpdates(newContainers, oldContainers []api.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) {
allErrs = field.ErrorList{}
if len(newContainers) != len(oldContainers) {
//TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff
allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers"))
return allErrs, true
}
// validate updated container images
for i, ctr := range newContainers {
if len(ctr.Image) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), ""))
}
}
return allErrs, false
}
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
// that cannot be changed.
func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList {
@ -1967,21 +1984,21 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList {
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
allErrs = append(allErrs, ValidatePodSpecificAnnotations(newPod.ObjectMeta.Annotations, fldPath.Child("annotations"))...)
specPath := field.NewPath("spec")
if len(newPod.Spec.Containers) != len(oldPod.Spec.Containers) {
//TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff
allErrs = append(allErrs, field.Forbidden(specPath.Child("containers"), "pod updates may not add or remove containers"))
return allErrs
}
// validate updateable fields:
// 1. containers[*].image
// 2. spec.activeDeadlineSeconds
// 2. initContainers[*].image
// 3. spec.activeDeadlineSeconds
// validate updated container images
for i, ctr := range newPod.Spec.Containers {
if len(ctr.Image) == 0 {
allErrs = append(allErrs, field.Required(specPath.Child("containers").Index(i).Child("image"), ""))
}
containerErrs, stop := validateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
allErrs = append(allErrs, containerErrs...)
if stop {
return allErrs
}
containerErrs, stop = validateContainerUpdates(newPod.Spec.InitContainers, oldPod.Spec.InitContainers, specPath.Child("initContainers"))
allErrs = append(allErrs, containerErrs...)
if stop {
return allErrs
}
// validate updated spec.activeDeadlineSeconds. two types of updates are allowed:
@ -2013,6 +2030,13 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList {
newContainers = append(newContainers, container)
}
mungedPod.Spec.Containers = newContainers
// munge initContainers[*].image
var newInitContainers []api.Container
for ix, container := range mungedPod.Spec.InitContainers {
container.Image = oldPod.Spec.InitContainers[ix].Image
newInitContainers = append(newInitContainers, container)
}
mungedPod.Spec.InitContainers = newInitContainers
// munge spec.activeDeadlineSeconds
mungedPod.Spec.ActiveDeadlineSeconds = nil
if oldPod.Spec.ActiveDeadlineSeconds != nil {

View File

@ -1907,8 +1907,9 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: map[string]string{
"key": "value",
},
@ -1999,6 +2000,12 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
"bad init container": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
"bad DNS policy": {
DNSPolicy: api.DNSPolicy("invalid"),
RestartPolicy: api.RestartPolicyAlways,
@ -3022,6 +3029,35 @@ func TestValidatePodUpdate(t *testing.T) {
false,
"more containers",
},
{
api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Image: "foo:V1",
},
},
},
},
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Image: "foo:V2",
},
{
Image: "bar:V2",
},
},
},
},
false,
"more init containers",
},
{
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
@ -3070,6 +3106,30 @@ func TestValidatePodUpdate(t *testing.T) {
true,
"image change",
},
{
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Image: "foo:V1",
},
},
},
},
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Image: "foo:V2",
},
},
},
},
true,
"init container image change",
},
{
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
@ -3092,6 +3152,28 @@ func TestValidatePodUpdate(t *testing.T) {
false,
"image change to empty",
},
{
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{},
},
},
},
api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Image: "foo:V2",
},
},
},
},
false,
"init container image change to empty",
},
{
api.Pod{
Spec: api.PodSpec{},

View File

@ -131,7 +131,6 @@ func addGrouplessTypes() {
api.Scheme.AddKnownTypes(grouplessGroupVersion,
&apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{},
&api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{})
api.Scheme.AddKnownTypes(grouplessGroupVersion, &api.Pod{})
api.Scheme.AddKnownTypes(grouplessInternalGroupVersion,
&apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &api.ListOptions{},
&apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{})

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/diff"
@ -186,12 +187,7 @@ func (tc *patchTestCase) Run(t *testing.T) {
namer := &testNamer{namespace, name}
copier := runtime.ObjectCopier(api.Scheme)
resource := unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
versionedObj, err := api.Scheme.ConvertToVersion(&api.Pod{}, unversioned.GroupVersion{Version: "v1"})
if err != nil {
t.Errorf("%s: unexpected error: %v", tc.name, err)
return
}
versionedObj := &v1.Pod{}
for _, patchType := range []api.PatchType{api.JSONPatchType, api.MergePatchType, api.StrategicMergePatchType} {
// TODO SUPPORT THIS!