k8s.io/kubernetes@v1.29.3/test/integration/pods/pods_test.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package pods
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    29  	clientset "k8s.io/client-go/kubernetes"
    30  	typedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
    31  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    32  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    33  	"k8s.io/kubernetes/pkg/features"
    34  	"k8s.io/kubernetes/test/integration"
    35  	"k8s.io/kubernetes/test/integration/framework"
    36  )
    37  
    38  func TestPodUpdateActiveDeadlineSeconds(t *testing.T) {
    39  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
    40  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
    41  	defer server.TearDownFn()
    42  
    43  	client := clientset.NewForConfigOrDie(server.ClientConfig)
    44  
    45  	ns := framework.CreateNamespaceOrDie(client, "pod-activedeadline-update", t)
    46  	defer framework.DeleteNamespaceOrDie(client, ns, t)
    47  
    48  	var (
    49  		iZero = int64(0)
    50  		i30   = int64(30)
    51  		i60   = int64(60)
    52  		iNeg  = int64(-1)
    53  	)
    54  
    55  	prototypePod := func() *v1.Pod {
    56  		return &v1.Pod{
    57  			ObjectMeta: metav1.ObjectMeta{
    58  				Name: "xxx",
    59  			},
    60  			Spec: v1.PodSpec{
    61  				Containers: []v1.Container{
    62  					{
    63  						Name:  "fake-name",
    64  						Image: "fakeimage",
    65  					},
    66  				},
    67  			},
    68  		}
    69  	}
    70  
    71  	cases := []struct {
    72  		name     string
    73  		original *int64
    74  		update   *int64
    75  		valid    bool
    76  	}{
    77  		{
    78  			name:     "no change, nil",
    79  			original: nil,
    80  			update:   nil,
    81  			valid:    true,
    82  		},
    83  		{
    84  			name:     "no change, set",
    85  			original: &i30,
    86  			update:   &i30,
    87  			valid:    true,
    88  		},
    89  		{
    90  			name:     "change to positive from nil",
    91  			original: nil,
    92  			update:   &i60,
    93  			valid:    true,
    94  		},
    95  		{
    96  			name:     "change to smaller positive",
    97  			original: &i60,
    98  			update:   &i30,
    99  			valid:    true,
   100  		},
   101  		{
   102  			name:     "change to larger positive",
   103  			original: &i30,
   104  			update:   &i60,
   105  			valid:    false,
   106  		},
   107  		{
   108  			name:     "change to negative from positive",
   109  			original: &i30,
   110  			update:   &iNeg,
   111  			valid:    false,
   112  		},
   113  		{
   114  			name:     "change to negative from nil",
   115  			original: nil,
   116  			update:   &iNeg,
   117  			valid:    false,
   118  		},
   119  		// zero is not allowed, must be a positive integer
   120  		{
   121  			name:     "change to zero from positive",
   122  			original: &i30,
   123  			update:   &iZero,
   124  			valid:    false,
   125  		},
   126  		{
   127  			name:     "change to nil from positive",
   128  			original: &i30,
   129  			update:   nil,
   130  			valid:    false,
   131  		},
   132  	}
   133  
   134  	for i, tc := range cases {
   135  		pod := prototypePod()
   136  		pod.Spec.ActiveDeadlineSeconds = tc.original
   137  		pod.ObjectMeta.Name = fmt.Sprintf("activedeadlineseconds-test-%v", i)
   138  
   139  		if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
   140  			t.Errorf("Failed to create pod: %v", err)
   141  		}
   142  
   143  		pod.Spec.ActiveDeadlineSeconds = tc.update
   144  
   145  		_, err := client.CoreV1().Pods(ns.Name).Update(context.TODO(), pod, metav1.UpdateOptions{})
   146  		if tc.valid && err != nil {
   147  			t.Errorf("%v: failed to update pod: %v", tc.name, err)
   148  		} else if !tc.valid && err == nil {
   149  			t.Errorf("%v: unexpected allowed update to pod", tc.name)
   150  		}
   151  
   152  		integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
   153  	}
   154  }
   155  
   156  func TestPodReadOnlyFilesystem(t *testing.T) {
   157  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   158  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   159  	defer server.TearDownFn()
   160  
   161  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   162  
   163  	isReadOnly := true
   164  	ns := framework.CreateNamespaceOrDie(client, "pod-readonly-root", t)
   165  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   166  
   167  	pod := &v1.Pod{
   168  		ObjectMeta: metav1.ObjectMeta{
   169  			Name: "xxx",
   170  		},
   171  		Spec: v1.PodSpec{
   172  			Containers: []v1.Container{
   173  				{
   174  					Name:  "fake-name",
   175  					Image: "fakeimage",
   176  					SecurityContext: &v1.SecurityContext{
   177  						ReadOnlyRootFilesystem: &isReadOnly,
   178  					},
   179  				},
   180  			},
   181  		},
   182  	}
   183  
   184  	if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
   185  		t.Errorf("Failed to create pod: %v", err)
   186  	}
   187  
   188  	integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
   189  }
   190  
   191  func TestPodCreateEphemeralContainers(t *testing.T) {
   192  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   193  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   194  	defer server.TearDownFn()
   195  
   196  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   197  
   198  	ns := framework.CreateNamespaceOrDie(client, "pod-create-ephemeral-containers", t)
   199  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   200  
   201  	pod := &v1.Pod{
   202  		ObjectMeta: metav1.ObjectMeta{
   203  			Name: "xxx",
   204  		},
   205  		Spec: v1.PodSpec{
   206  			Containers: []v1.Container{
   207  				{
   208  					Name:                     "fake-name",
   209  					Image:                    "fakeimage",
   210  					ImagePullPolicy:          "Always",
   211  					TerminationMessagePolicy: "File",
   212  				},
   213  			},
   214  			EphemeralContainers: []v1.EphemeralContainer{
   215  				{
   216  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   217  						Name:                     "debugger",
   218  						Image:                    "debugimage",
   219  						ImagePullPolicy:          "Always",
   220  						TerminationMessagePolicy: "File",
   221  					},
   222  				},
   223  			},
   224  		},
   225  	}
   226  
   227  	if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err == nil {
   228  		t.Errorf("Unexpected allowed creation of pod with ephemeral containers")
   229  		integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
   230  	} else if !strings.HasSuffix(err.Error(), "spec.ephemeralContainers: Forbidden: cannot be set on create") {
   231  		t.Errorf("Unexpected error when creating pod with ephemeral containers: %v", err)
   232  	}
   233  }
   234  
   235  // setUpEphemeralContainers creates a pod that has Ephemeral Containers. This is a two step
   236  // process because Ephemeral Containers are not allowed during pod creation.
   237  func setUpEphemeralContainers(podsClient typedv1.PodInterface, pod *v1.Pod, containers []v1.EphemeralContainer) (*v1.Pod, error) {
   238  	result, err := podsClient.Create(context.TODO(), pod, metav1.CreateOptions{})
   239  	if err != nil {
   240  		return nil, fmt.Errorf("failed to create pod: %v", err)
   241  	}
   242  
   243  	if len(containers) == 0 {
   244  		return result, nil
   245  	}
   246  
   247  	pod.Spec.EphemeralContainers = containers
   248  	if _, err := podsClient.Update(context.TODO(), pod, metav1.UpdateOptions{}); err == nil {
   249  		return nil, fmt.Errorf("unexpected allowed direct update of ephemeral containers during set up: %v", err)
   250  	}
   251  
   252  	result, err = podsClient.UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{})
   253  	if err != nil {
   254  		return nil, fmt.Errorf("failed to update ephemeral containers for test case set up: %v", err)
   255  	}
   256  
   257  	return result, nil
   258  }
   259  
   260  func TestPodPatchEphemeralContainers(t *testing.T) {
   261  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   262  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   263  	defer server.TearDownFn()
   264  
   265  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   266  
   267  	ns := framework.CreateNamespaceOrDie(client, "pod-patch-ephemeral-containers", t)
   268  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   269  
   270  	testPod := func(name string) *v1.Pod {
   271  		return &v1.Pod{
   272  			ObjectMeta: metav1.ObjectMeta{
   273  				Name: name,
   274  			},
   275  			Spec: v1.PodSpec{
   276  				Containers: []v1.Container{
   277  					{
   278  						Name:                     "fake-name",
   279  						Image:                    "fakeimage",
   280  						ImagePullPolicy:          "Always",
   281  						TerminationMessagePolicy: "File",
   282  					},
   283  				},
   284  			},
   285  		}
   286  	}
   287  
   288  	cases := []struct {
   289  		name      string
   290  		original  []v1.EphemeralContainer
   291  		patchType types.PatchType
   292  		patchBody []byte
   293  		valid     bool
   294  	}{
   295  		{
   296  			name:      "create single container (strategic)",
   297  			original:  nil,
   298  			patchType: types.StrategicMergePatchType,
   299  			patchBody: []byte(`{
   300  				"spec": {
   301  					"ephemeralContainers": [{
   302  						"name": "debugger1",
   303  						"image": "debugimage",
   304  						"imagePullPolicy": "Always",
   305  						"terminationMessagePolicy": "File"
   306  					}]
   307  				}
   308  			}`),
   309  			valid: true,
   310  		},
   311  		{
   312  			name:      "create single container (merge)",
   313  			original:  nil,
   314  			patchType: types.MergePatchType,
   315  			patchBody: []byte(`{
   316  				"spec": {
   317  					"ephemeralContainers":[{
   318  						"name": "debugger1",
   319  						"image": "debugimage",
   320  						"imagePullPolicy": "Always",
   321  						"terminationMessagePolicy": "File"
   322  					}]
   323  				}
   324  			}`),
   325  			valid: true,
   326  		},
   327  		{
   328  			name:      "create single container (JSON)",
   329  			original:  nil,
   330  			patchType: types.JSONPatchType,
   331  			// Because ephemeralContainers is optional, a JSON patch of an empty ephemeralContainers must add the
   332  			// list rather than simply appending to it.
   333  			patchBody: []byte(`[{
   334  				"op":"add",
   335  				"path":"/spec/ephemeralContainers",
   336  				"value":[{
   337  					"name":"debugger1",
   338  					"image":"debugimage",
   339  					"imagePullPolicy": "Always",
   340  					"terminationMessagePolicy": "File"
   341  				}]
   342  			}]`),
   343  			valid: true,
   344  		},
   345  		{
   346  			name: "add single container (strategic)",
   347  			original: []v1.EphemeralContainer{
   348  				{
   349  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   350  						Name:                     "debugger1",
   351  						Image:                    "debugimage",
   352  						ImagePullPolicy:          "Always",
   353  						TerminationMessagePolicy: "File",
   354  					},
   355  				},
   356  			},
   357  			patchType: types.StrategicMergePatchType,
   358  			patchBody: []byte(`{
   359  				"spec": {
   360  					"ephemeralContainers":[{
   361  						"name": "debugger2",
   362  						"image": "debugimage",
   363  						"imagePullPolicy": "Always",
   364  						"terminationMessagePolicy": "File"
   365  					}]
   366  				}
   367  			}`),
   368  			valid: true,
   369  		},
   370  		{
   371  			name: "add single container (merge)",
   372  			original: []v1.EphemeralContainer{
   373  				{
   374  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   375  						Name:                     "debugger1",
   376  						Image:                    "debugimage",
   377  						ImagePullPolicy:          "Always",
   378  						TerminationMessagePolicy: "File",
   379  					},
   380  				},
   381  			},
   382  			patchType: types.MergePatchType,
   383  			patchBody: []byte(`{
   384  				"spec": {
   385  					"ephemeralContainers":[{
   386  						"name": "debugger1",
   387  						"image": "debugimage",
   388  						"imagePullPolicy": "Always",
   389  						"terminationMessagePolicy": "File"
   390  					},{
   391  						"name": "debugger2",
   392  						"image": "debugimage",
   393  						"imagePullPolicy": "Always",
   394  						"terminationMessagePolicy": "File"
   395  					}]
   396  				} 
   397  			}`),
   398  			valid: true,
   399  		},
   400  		{
   401  			name: "add single container (JSON)",
   402  			original: []v1.EphemeralContainer{
   403  				{
   404  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   405  						Name:                     "debugger1",
   406  						Image:                    "debugimage",
   407  						ImagePullPolicy:          "Always",
   408  						TerminationMessagePolicy: "File",
   409  					},
   410  				},
   411  			},
   412  			patchType: types.JSONPatchType,
   413  			patchBody: []byte(`[{
   414  				"op":"add",
   415  				"path":"/spec/ephemeralContainers/-",
   416  				"value":{
   417  					"name":"debugger2",
   418  					"image":"debugimage",
   419  					"imagePullPolicy": "Always",
   420  					"terminationMessagePolicy": "File"
   421  				}
   422  			}]`),
   423  			valid: true,
   424  		},
   425  		{
   426  			name: "remove all containers (merge)",
   427  			original: []v1.EphemeralContainer{
   428  				{
   429  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   430  						Name:                     "debugger1",
   431  						Image:                    "debugimage",
   432  						ImagePullPolicy:          "Always",
   433  						TerminationMessagePolicy: "File",
   434  					},
   435  				},
   436  			},
   437  			patchType: types.MergePatchType,
   438  			patchBody: []byte(`{"spec": {"ephemeralContainers":[]}}`),
   439  			valid:     false,
   440  		},
   441  		{
   442  			name: "remove the single container (JSON)",
   443  			original: []v1.EphemeralContainer{
   444  				{
   445  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   446  						Name:                     "debugger1",
   447  						Image:                    "debugimage",
   448  						ImagePullPolicy:          "Always",
   449  						TerminationMessagePolicy: "File",
   450  					},
   451  				},
   452  			},
   453  			patchType: types.JSONPatchType,
   454  			patchBody: []byte(`[{"op":"remove","path":"/spec/ephemeralContainers/0"}]`),
   455  			valid:     false, // disallowed by policy rather than patch semantics
   456  		},
   457  		{
   458  			name: "remove all containers (JSON)",
   459  			original: []v1.EphemeralContainer{
   460  				{
   461  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   462  						Name:                     "debugger1",
   463  						Image:                    "debugimage",
   464  						ImagePullPolicy:          "Always",
   465  						TerminationMessagePolicy: "File",
   466  					},
   467  				},
   468  			},
   469  			patchType: types.JSONPatchType,
   470  			patchBody: []byte(`[{"op":"remove","path":"/spec/ephemeralContainers"}]`),
   471  			valid:     false, // disallowed by policy rather than patch semantics
   472  		},
   473  	}
   474  
   475  	for i, tc := range cases {
   476  		pod := testPod(fmt.Sprintf("ephemeral-container-test-%v", i))
   477  		if _, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, tc.original); err != nil {
   478  			t.Errorf("%v: %v", tc.name, err)
   479  		}
   480  
   481  		if _, err := client.CoreV1().Pods(ns.Name).Patch(context.TODO(), pod.Name, tc.patchType, tc.patchBody, metav1.PatchOptions{}, "ephemeralcontainers"); tc.valid && err != nil {
   482  			t.Errorf("%v: failed to update ephemeral containers: %v", tc.name, err)
   483  		} else if !tc.valid && err == nil {
   484  			t.Errorf("%v: unexpected allowed update to ephemeral containers", tc.name)
   485  		}
   486  
   487  		integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
   488  	}
   489  }
   490  
   491  func TestPodUpdateEphemeralContainers(t *testing.T) {
   492  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   493  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   494  	defer server.TearDownFn()
   495  
   496  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   497  
   498  	ns := framework.CreateNamespaceOrDie(client, "pod-update-ephemeral-containers", t)
   499  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   500  
   501  	testPod := func(name string) *v1.Pod {
   502  		return &v1.Pod{
   503  			ObjectMeta: metav1.ObjectMeta{
   504  				Name: name,
   505  			},
   506  			Spec: v1.PodSpec{
   507  				Containers: []v1.Container{
   508  					{
   509  						Name:  "fake-name",
   510  						Image: "fakeimage",
   511  					},
   512  				},
   513  			},
   514  		}
   515  	}
   516  
   517  	cases := []struct {
   518  		name     string
   519  		original []v1.EphemeralContainer
   520  		update   []v1.EphemeralContainer
   521  		valid    bool
   522  	}{
   523  		{
   524  			name:     "no change, nil",
   525  			original: nil,
   526  			update:   nil,
   527  			valid:    true,
   528  		},
   529  		{
   530  			name: "no change, set",
   531  			original: []v1.EphemeralContainer{
   532  				{
   533  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   534  						Name:                     "debugger",
   535  						Image:                    "debugimage",
   536  						ImagePullPolicy:          "Always",
   537  						TerminationMessagePolicy: "File",
   538  					},
   539  				},
   540  			},
   541  			update: []v1.EphemeralContainer{
   542  				{
   543  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   544  						Name:                     "debugger",
   545  						Image:                    "debugimage",
   546  						ImagePullPolicy:          "Always",
   547  						TerminationMessagePolicy: "File",
   548  					},
   549  				},
   550  			},
   551  			valid: true,
   552  		},
   553  		{
   554  			name:     "add single container",
   555  			original: nil,
   556  			update: []v1.EphemeralContainer{
   557  				{
   558  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   559  						Name:                     "debugger",
   560  						Image:                    "debugimage",
   561  						ImagePullPolicy:          "Always",
   562  						TerminationMessagePolicy: "File",
   563  					},
   564  				},
   565  			},
   566  			valid: true,
   567  		},
   568  		{
   569  			name: "remove all containers, nil",
   570  			original: []v1.EphemeralContainer{
   571  				{
   572  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   573  						Name:                     "debugger",
   574  						Image:                    "debugimage",
   575  						ImagePullPolicy:          "Always",
   576  						TerminationMessagePolicy: "File",
   577  					},
   578  				},
   579  			},
   580  			update: nil,
   581  			valid:  false,
   582  		},
   583  		{
   584  			name: "remove all containers, empty",
   585  			original: []v1.EphemeralContainer{
   586  				{
   587  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   588  						Name:                     "debugger",
   589  						Image:                    "debugimage",
   590  						ImagePullPolicy:          "Always",
   591  						TerminationMessagePolicy: "File",
   592  					},
   593  				},
   594  			},
   595  			update: []v1.EphemeralContainer{},
   596  			valid:  false,
   597  		},
   598  		{
   599  			name: "increase number of containers",
   600  			original: []v1.EphemeralContainer{
   601  				{
   602  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   603  						Name:                     "debugger1",
   604  						Image:                    "debugimage",
   605  						ImagePullPolicy:          "Always",
   606  						TerminationMessagePolicy: "File",
   607  					},
   608  				},
   609  			},
   610  			update: []v1.EphemeralContainer{
   611  				{
   612  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   613  						Name:                     "debugger1",
   614  						Image:                    "debugimage",
   615  						ImagePullPolicy:          "Always",
   616  						TerminationMessagePolicy: "File",
   617  					},
   618  				},
   619  				{
   620  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   621  						Name:                     "debugger2",
   622  						Image:                    "debugimage",
   623  						ImagePullPolicy:          "Always",
   624  						TerminationMessagePolicy: "File",
   625  					},
   626  				},
   627  			},
   628  			valid: true,
   629  		},
   630  		{
   631  			name: "decrease number of containers",
   632  			original: []v1.EphemeralContainer{
   633  				{
   634  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   635  						Name:                     "debugger1",
   636  						Image:                    "debugimage",
   637  						ImagePullPolicy:          "Always",
   638  						TerminationMessagePolicy: "File",
   639  					},
   640  				},
   641  				{
   642  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   643  						Name:                     "debugger2",
   644  						Image:                    "debugimage",
   645  						ImagePullPolicy:          "Always",
   646  						TerminationMessagePolicy: "File",
   647  					},
   648  				},
   649  			},
   650  			update: []v1.EphemeralContainer{
   651  				{
   652  					EphemeralContainerCommon: v1.EphemeralContainerCommon{
   653  						Name:                     "debugger1",
   654  						Image:                    "debugimage",
   655  						ImagePullPolicy:          "Always",
   656  						TerminationMessagePolicy: "File",
   657  					},
   658  				},
   659  			},
   660  			valid: false,
   661  		},
   662  	}
   663  
   664  	for i, tc := range cases {
   665  		pod, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), testPod(fmt.Sprintf("ephemeral-container-test-%v", i)), tc.original)
   666  		if err != nil {
   667  			t.Errorf("%v: %v", tc.name, err)
   668  		}
   669  
   670  		pod.Spec.EphemeralContainers = tc.update
   671  		if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{}); tc.valid && err != nil {
   672  			t.Errorf("%v: failed to update ephemeral containers: %v", tc.name, err)
   673  		} else if !tc.valid && err == nil {
   674  			t.Errorf("%v: unexpected allowed update to ephemeral containers", tc.name)
   675  		}
   676  
   677  		integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name)
   678  	}
   679  }
   680  
   681  func TestMutablePodSchedulingDirectives(t *testing.T) {
   682  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   683  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   684  	defer server.TearDownFn()
   685  
   686  	client := clientset.NewForConfigOrDie(server.ClientConfig)
   687  
   688  	ns := framework.CreateNamespaceOrDie(client, "mutable-pod-scheduling-directives", t)
   689  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   690  
   691  	cases := []struct {
   692  		name                  string
   693  		create                *v1.Pod
   694  		update                *v1.Pod
   695  		enableSchedulingGates bool
   696  		err                   string
   697  	}{
   698  		{
   699  			name: "node selector is immutable when AllowMutableNodeSelector is false",
   700  			create: &v1.Pod{
   701  				ObjectMeta: metav1.ObjectMeta{
   702  					Name: "test-pod",
   703  				},
   704  				Spec: v1.PodSpec{
   705  					Containers: []v1.Container{
   706  						{
   707  							Name:  "fake-name",
   708  							Image: "fakeimage",
   709  						},
   710  					},
   711  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   712  				},
   713  			},
   714  			update: &v1.Pod{
   715  				ObjectMeta: metav1.ObjectMeta{
   716  					Name: "test-pod",
   717  				},
   718  				Spec: v1.PodSpec{
   719  					Containers: []v1.Container{
   720  						{
   721  							Name:  "fake-name",
   722  							Image: "fakeimage",
   723  						},
   724  					},
   725  					NodeSelector: map[string]string{
   726  						"foo": "bar",
   727  					},
   728  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   729  				},
   730  			},
   731  			err: "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
   732  		},
   733  		{
   734  			name: "adding node selector is allowed for gated pods",
   735  			create: &v1.Pod{
   736  				ObjectMeta: metav1.ObjectMeta{
   737  					Name: "test-pod",
   738  				},
   739  				Spec: v1.PodSpec{
   740  					Containers: []v1.Container{
   741  						{
   742  							Name:  "fake-name",
   743  							Image: "fakeimage",
   744  						},
   745  					},
   746  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   747  				},
   748  			},
   749  			update: &v1.Pod{
   750  				ObjectMeta: metav1.ObjectMeta{
   751  					Name: "test-pod",
   752  				},
   753  				Spec: v1.PodSpec{
   754  					Containers: []v1.Container{
   755  						{
   756  							Name:  "fake-name",
   757  							Image: "fakeimage",
   758  						},
   759  					},
   760  					NodeSelector: map[string]string{
   761  						"foo": "bar",
   762  					},
   763  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   764  				},
   765  			},
   766  			enableSchedulingGates: true,
   767  		},
   768  		{
   769  			name: "addition to nodeAffinity is allowed for gated pods",
   770  			create: &v1.Pod{
   771  				ObjectMeta: metav1.ObjectMeta{
   772  					Name: "test-pod",
   773  				},
   774  				Spec: v1.PodSpec{
   775  					Containers: []v1.Container{
   776  						{
   777  							Name:  "fake-name",
   778  							Image: "fakeimage",
   779  						},
   780  					},
   781  					Affinity: &v1.Affinity{
   782  						NodeAffinity: &v1.NodeAffinity{
   783  							RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
   784  								NodeSelectorTerms: []v1.NodeSelectorTerm{
   785  									{
   786  										MatchExpressions: []v1.NodeSelectorRequirement{
   787  											{
   788  												Key:      "expr",
   789  												Operator: v1.NodeSelectorOpIn,
   790  												Values:   []string{"foo"},
   791  											},
   792  										},
   793  									},
   794  								},
   795  							},
   796  						},
   797  					},
   798  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   799  				},
   800  			},
   801  			update: &v1.Pod{
   802  				ObjectMeta: metav1.ObjectMeta{
   803  					Name: "test-pod",
   804  				},
   805  				Spec: v1.PodSpec{
   806  					Containers: []v1.Container{
   807  						{
   808  							Name:  "fake-name",
   809  							Image: "fakeimage",
   810  						},
   811  					},
   812  					Affinity: &v1.Affinity{
   813  						NodeAffinity: &v1.NodeAffinity{
   814  							RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
   815  								// Add 1 MatchExpression and 1 MatchField.
   816  								NodeSelectorTerms: []v1.NodeSelectorTerm{
   817  									{
   818  										MatchExpressions: []v1.NodeSelectorRequirement{
   819  											{
   820  												Key:      "expr",
   821  												Operator: v1.NodeSelectorOpIn,
   822  												Values:   []string{"foo"},
   823  											},
   824  											{
   825  												Key:      "expr2",
   826  												Operator: v1.NodeSelectorOpIn,
   827  												Values:   []string{"foo2"},
   828  											},
   829  										},
   830  										MatchFields: []v1.NodeSelectorRequirement{
   831  											{
   832  												Key:      "metadata.name",
   833  												Operator: v1.NodeSelectorOpIn,
   834  												Values:   []string{"foo"},
   835  											},
   836  										},
   837  									},
   838  								},
   839  							},
   840  						},
   841  					},
   842  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   843  				},
   844  			},
   845  			enableSchedulingGates: true,
   846  		},
   847  		{
   848  			name: "addition to nodeAffinity is allowed for gated pods with nil affinity",
   849  			create: &v1.Pod{
   850  				ObjectMeta: metav1.ObjectMeta{
   851  					Name: "test-pod",
   852  				},
   853  				Spec: v1.PodSpec{
   854  					Containers: []v1.Container{
   855  						{
   856  							Name:  "fake-name",
   857  							Image: "fakeimage",
   858  						},
   859  					},
   860  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   861  				},
   862  			},
   863  			update: &v1.Pod{
   864  				ObjectMeta: metav1.ObjectMeta{
   865  					Name: "test-pod",
   866  				},
   867  				Spec: v1.PodSpec{
   868  					Containers: []v1.Container{
   869  						{
   870  							Name:  "fake-name",
   871  							Image: "fakeimage",
   872  						},
   873  					},
   874  					Affinity: &v1.Affinity{
   875  						NodeAffinity: &v1.NodeAffinity{
   876  							RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
   877  								// Add 1 MatchExpression and 1 MatchField.
   878  								NodeSelectorTerms: []v1.NodeSelectorTerm{
   879  									{
   880  										MatchExpressions: []v1.NodeSelectorRequirement{
   881  											{
   882  												Key:      "expr",
   883  												Operator: v1.NodeSelectorOpIn,
   884  												Values:   []string{"foo"},
   885  											},
   886  										},
   887  										MatchFields: []v1.NodeSelectorRequirement{
   888  											{
   889  												Key:      "metadata.name",
   890  												Operator: v1.NodeSelectorOpIn,
   891  												Values:   []string{"foo"},
   892  											},
   893  										},
   894  									},
   895  								},
   896  							},
   897  						},
   898  					},
   899  					SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}},
   900  				},
   901  			},
   902  			enableSchedulingGates: true,
   903  		},
   904  	}
   905  	for _, tc := range cases {
   906  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tc.enableSchedulingGates)()
   907  
   908  		if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), tc.create, metav1.CreateOptions{}); err != nil {
   909  			t.Errorf("Failed to create pod: %v", err)
   910  		}
   911  
   912  		_, err := client.CoreV1().Pods(ns.Name).Update(context.TODO(), tc.update, metav1.UpdateOptions{})
   913  		if (tc.err == "" && err != nil) || (tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err)) {
   914  			t.Errorf("Unexpected error: got %q, want %q", err.Error(), err)
   915  		}
   916  		integration.DeletePodOrErrorf(t, client, ns.Name, tc.update.Name)
   917  	}
   918  }