...

Source file src/k8s.io/kubernetes/test/integration/pods/pods_test.go

Documentation: k8s.io/kubernetes/test/integration/pods

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

View as plain text