k8s.io/kubernetes@v1.29.3/pkg/registry/core/pod/strategy_test.go (about)

     1  /*
     2  Copyright 2014 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 pod
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  	apiv1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/fields"
    34  	"k8s.io/apimachinery/pkg/labels"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    38  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    39  	"k8s.io/client-go/tools/cache"
    40  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    41  	utilpointer "k8s.io/utils/pointer"
    42  
    43  	apitesting "k8s.io/kubernetes/pkg/api/testing"
    44  	api "k8s.io/kubernetes/pkg/apis/core"
    45  	"k8s.io/kubernetes/pkg/features"
    46  	"k8s.io/kubernetes/pkg/kubelet/client"
    47  
    48  	// ensure types are installed
    49  	_ "k8s.io/kubernetes/pkg/apis/core/install"
    50  )
    51  
    52  func TestMatchPod(t *testing.T) {
    53  	testCases := []struct {
    54  		in            *api.Pod
    55  		fieldSelector fields.Selector
    56  		expectMatch   bool
    57  	}{
    58  		{
    59  			in: &api.Pod{
    60  				Spec: api.PodSpec{NodeName: "nodeA"},
    61  			},
    62  			fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
    63  			expectMatch:   true,
    64  		},
    65  		{
    66  			in: &api.Pod{
    67  				Spec: api.PodSpec{NodeName: "nodeB"},
    68  			},
    69  			fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
    70  			expectMatch:   false,
    71  		},
    72  		{
    73  			in: &api.Pod{
    74  				Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
    75  			},
    76  			fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Always"),
    77  			expectMatch:   true,
    78  		},
    79  		{
    80  			in: &api.Pod{
    81  				Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
    82  			},
    83  			fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Never"),
    84  			expectMatch:   false,
    85  		},
    86  		{
    87  			in: &api.Pod{
    88  				Spec: api.PodSpec{SchedulerName: "scheduler1"},
    89  			},
    90  			fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler1"),
    91  			expectMatch:   true,
    92  		},
    93  		{
    94  			in: &api.Pod{
    95  				Spec: api.PodSpec{SchedulerName: "scheduler1"},
    96  			},
    97  			fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler2"),
    98  			expectMatch:   false,
    99  		},
   100  		{
   101  			in: &api.Pod{
   102  				Spec: api.PodSpec{ServiceAccountName: "serviceAccount1"},
   103  			},
   104  			fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount1"),
   105  			expectMatch:   true,
   106  		},
   107  		{
   108  			in: &api.Pod{
   109  				Spec: api.PodSpec{SchedulerName: "serviceAccount1"},
   110  			},
   111  			fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount2"),
   112  			expectMatch:   false,
   113  		},
   114  		{
   115  			in: &api.Pod{
   116  				Status: api.PodStatus{Phase: api.PodRunning},
   117  			},
   118  			fieldSelector: fields.ParseSelectorOrDie("status.phase=Running"),
   119  			expectMatch:   true,
   120  		},
   121  		{
   122  			in: &api.Pod{
   123  				Status: api.PodStatus{Phase: api.PodRunning},
   124  			},
   125  			fieldSelector: fields.ParseSelectorOrDie("status.phase=Pending"),
   126  			expectMatch:   false,
   127  		},
   128  		{
   129  			in: &api.Pod{
   130  				Status: api.PodStatus{
   131  					PodIPs: []api.PodIP{
   132  						{IP: "1.2.3.4"},
   133  					},
   134  				},
   135  			},
   136  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=1.2.3.4"),
   137  			expectMatch:   true,
   138  		},
   139  		{
   140  			in: &api.Pod{
   141  				Status: api.PodStatus{
   142  					PodIPs: []api.PodIP{
   143  						{IP: "1.2.3.4"},
   144  					},
   145  				},
   146  			},
   147  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=4.3.2.1"),
   148  			expectMatch:   false,
   149  		},
   150  		{
   151  			in: &api.Pod{
   152  				Status: api.PodStatus{NominatedNodeName: "node1"},
   153  			},
   154  			fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node1"),
   155  			expectMatch:   true,
   156  		},
   157  		{
   158  			in: &api.Pod{
   159  				Status: api.PodStatus{NominatedNodeName: "node1"},
   160  			},
   161  			fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node2"),
   162  			expectMatch:   false,
   163  		},
   164  		{
   165  			in: &api.Pod{
   166  				Status: api.PodStatus{
   167  					PodIPs: []api.PodIP{
   168  						{IP: "2001:db8::"},
   169  					},
   170  				},
   171  			},
   172  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db8::"),
   173  			expectMatch:   true,
   174  		},
   175  		{
   176  			in: &api.Pod{
   177  				Status: api.PodStatus{
   178  					PodIPs: []api.PodIP{
   179  						{IP: "2001:db8::"},
   180  					},
   181  				},
   182  			},
   183  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db7::"),
   184  			expectMatch:   false,
   185  		},
   186  		{
   187  			in: &api.Pod{
   188  				Spec: api.PodSpec{
   189  					SecurityContext: &api.PodSecurityContext{
   190  						HostNetwork: true,
   191  					},
   192  				},
   193  			},
   194  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"),
   195  			expectMatch:   true,
   196  		},
   197  		{
   198  			in: &api.Pod{
   199  				Spec: api.PodSpec{
   200  					SecurityContext: &api.PodSecurityContext{
   201  						HostNetwork: true,
   202  					},
   203  				},
   204  			},
   205  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   206  			expectMatch:   false,
   207  		},
   208  		{
   209  			in: &api.Pod{
   210  				Spec: api.PodSpec{
   211  					SecurityContext: &api.PodSecurityContext{
   212  						HostNetwork: false,
   213  					},
   214  				},
   215  			},
   216  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   217  			expectMatch:   true,
   218  		},
   219  		{
   220  			in: &api.Pod{
   221  				Spec: api.PodSpec{},
   222  			},
   223  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   224  			expectMatch:   true,
   225  		},
   226  		{
   227  			in: &api.Pod{
   228  				Spec: api.PodSpec{},
   229  			},
   230  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"),
   231  			expectMatch:   false,
   232  		},
   233  	}
   234  	for _, testCase := range testCases {
   235  		m := MatchPod(labels.Everything(), testCase.fieldSelector)
   236  		result, err := m.Matches(testCase.in)
   237  		if err != nil {
   238  			t.Errorf("Unexpected error %v", err)
   239  		}
   240  		if result != testCase.expectMatch {
   241  			t.Errorf("Result %v, Expected %v, Selector: %v, Pod: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in)
   242  		}
   243  	}
   244  }
   245  
   246  func getResourceList(cpu, memory string) api.ResourceList {
   247  	res := api.ResourceList{}
   248  	if cpu != "" {
   249  		res[api.ResourceCPU] = resource.MustParse(cpu)
   250  	}
   251  	if memory != "" {
   252  		res[api.ResourceMemory] = resource.MustParse(memory)
   253  	}
   254  	return res
   255  }
   256  
   257  func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
   258  	res := api.ResourceRequirements{}
   259  	res.Requests = requests
   260  	res.Limits = limits
   261  	return res
   262  }
   263  
   264  func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container {
   265  	return api.Container{
   266  		Name:      name,
   267  		Resources: getResourceRequirements(requests, limits),
   268  	}
   269  }
   270  
   271  func newPod(name string, containers []api.Container) *api.Pod {
   272  	return &api.Pod{
   273  		ObjectMeta: metav1.ObjectMeta{
   274  			Name: name,
   275  		},
   276  		Spec: api.PodSpec{
   277  			Containers: containers,
   278  		},
   279  	}
   280  }
   281  
   282  func TestGetPodQOS(t *testing.T) {
   283  	testCases := []struct {
   284  		pod      *api.Pod
   285  		expected api.PodQOSClass
   286  	}{
   287  		{
   288  			pod: newPod("guaranteed", []api.Container{
   289  				newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
   290  			}),
   291  			expected: api.PodQOSGuaranteed,
   292  		},
   293  		{
   294  			pod: newPod("best-effort", []api.Container{
   295  				newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
   296  			}),
   297  			expected: api.PodQOSBestEffort,
   298  		},
   299  		{
   300  			pod: newPod("burstable", []api.Container{
   301  				newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("", "")),
   302  			}),
   303  			expected: api.PodQOSBurstable,
   304  		},
   305  	}
   306  	for id, testCase := range testCases {
   307  		Strategy.PrepareForCreate(genericapirequest.NewContext(), testCase.pod)
   308  		actual := testCase.pod.Status.QOSClass
   309  		if actual != testCase.expected {
   310  			t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
   311  		}
   312  	}
   313  }
   314  
   315  func TestWaitingForGatesCondition(t *testing.T) {
   316  	tests := []struct {
   317  		name           string
   318  		pod            *api.Pod
   319  		featureEnabled bool
   320  		want           api.PodCondition
   321  	}{
   322  		{
   323  			name:           "pod without .spec.schedulingGates, feature disabled",
   324  			pod:            &api.Pod{},
   325  			featureEnabled: false,
   326  			want:           api.PodCondition{},
   327  		},
   328  		{
   329  			name:           "pod without .spec.schedulingGates, feature enabled",
   330  			pod:            &api.Pod{},
   331  			featureEnabled: true,
   332  			want:           api.PodCondition{},
   333  		},
   334  		{
   335  			name: "pod with .spec.schedulingGates, feature disabled",
   336  			pod: &api.Pod{
   337  				Spec: api.PodSpec{
   338  					SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
   339  				},
   340  			},
   341  			featureEnabled: false,
   342  			want:           api.PodCondition{},
   343  		},
   344  		{
   345  			name: "pod with .spec.schedulingGates, feature enabled",
   346  			pod: &api.Pod{
   347  				Spec: api.PodSpec{
   348  					SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
   349  				},
   350  			},
   351  			featureEnabled: true,
   352  			want: api.PodCondition{
   353  				Type:    api.PodScheduled,
   354  				Status:  api.ConditionFalse,
   355  				Reason:  apiv1.PodReasonSchedulingGated,
   356  				Message: "Scheduling is blocked due to non-empty scheduling gates",
   357  			},
   358  		},
   359  	}
   360  
   361  	for _, tt := range tests {
   362  		t.Run(tt.name, func(t *testing.T) {
   363  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
   364  
   365  			Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod)
   366  			var got api.PodCondition
   367  			for _, condition := range tt.pod.Status.Conditions {
   368  				if condition.Type == api.PodScheduled {
   369  					got = condition
   370  					break
   371  				}
   372  			}
   373  
   374  			if diff := cmp.Diff(tt.want, got); diff != "" {
   375  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
   376  			}
   377  		})
   378  	}
   379  }
   380  
   381  func TestCheckGracefulDelete(t *testing.T) {
   382  	defaultGracePeriod := int64(30)
   383  	tcs := []struct {
   384  		name              string
   385  		pod               *api.Pod
   386  		deleteGracePeriod *int64
   387  		gracePeriod       int64
   388  	}{
   389  		{
   390  			name: "in pending phase with has node name",
   391  			pod: &api.Pod{
   392  				Spec:   api.PodSpec{NodeName: "something"},
   393  				Status: api.PodStatus{Phase: api.PodPending},
   394  			},
   395  			deleteGracePeriod: &defaultGracePeriod,
   396  			gracePeriod:       defaultGracePeriod,
   397  		},
   398  		{
   399  			name: "in failed phase with has node name",
   400  			pod: &api.Pod{
   401  				Spec:   api.PodSpec{NodeName: "something"},
   402  				Status: api.PodStatus{Phase: api.PodFailed},
   403  			},
   404  			deleteGracePeriod: &defaultGracePeriod,
   405  			gracePeriod:       0,
   406  		},
   407  		{
   408  			name: "in failed phase",
   409  			pod: &api.Pod{
   410  				Spec:   api.PodSpec{},
   411  				Status: api.PodStatus{Phase: api.PodPending},
   412  			},
   413  			deleteGracePeriod: &defaultGracePeriod,
   414  			gracePeriod:       0,
   415  		},
   416  		{
   417  			name: "in succeeded phase",
   418  			pod: &api.Pod{
   419  				Spec:   api.PodSpec{},
   420  				Status: api.PodStatus{Phase: api.PodSucceeded},
   421  			},
   422  			deleteGracePeriod: &defaultGracePeriod,
   423  			gracePeriod:       0,
   424  		},
   425  		{
   426  			name: "no phase",
   427  			pod: &api.Pod{
   428  				Spec:   api.PodSpec{},
   429  				Status: api.PodStatus{},
   430  			},
   431  			deleteGracePeriod: &defaultGracePeriod,
   432  			gracePeriod:       0,
   433  		},
   434  		{
   435  			name: "has negative grace period",
   436  			pod: &api.Pod{
   437  				Spec: api.PodSpec{
   438  					NodeName:                      "something",
   439  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
   440  				},
   441  				Status: api.PodStatus{},
   442  			},
   443  			gracePeriod: 1,
   444  		},
   445  	}
   446  	for _, tc := range tcs {
   447  		t.Run(tc.name, func(t *testing.T) {
   448  			out := &metav1.DeleteOptions{}
   449  			if tc.deleteGracePeriod != nil {
   450  				out.GracePeriodSeconds = utilpointer.Int64(*tc.deleteGracePeriod)
   451  			}
   452  			Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.pod, out)
   453  			if out.GracePeriodSeconds == nil {
   454  				t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod)
   455  			}
   456  			if *(out.GracePeriodSeconds) != tc.gracePeriod {
   457  				t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod)
   458  			}
   459  		})
   460  	}
   461  }
   462  
   463  type mockPodGetter struct {
   464  	pod *api.Pod
   465  }
   466  
   467  func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) {
   468  	return g.pod, nil
   469  }
   470  
   471  func TestCheckLogLocation(t *testing.T) {
   472  	ctx := genericapirequest.NewDefaultContext()
   473  	fakePodName := "test"
   474  	tcs := []struct {
   475  		name              string
   476  		in                *api.Pod
   477  		opts              *api.PodLogOptions
   478  		expectedErr       error
   479  		expectedTransport http.RoundTripper
   480  	}{
   481  		{
   482  			name: "simple",
   483  			in: &api.Pod{
   484  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   485  				Spec: api.PodSpec{
   486  					Containers: []api.Container{
   487  						{Name: "mycontainer"},
   488  					},
   489  					NodeName: "foo",
   490  				},
   491  				Status: api.PodStatus{},
   492  			},
   493  			opts:              &api.PodLogOptions{},
   494  			expectedErr:       nil,
   495  			expectedTransport: fakeSecureRoundTripper,
   496  		},
   497  		{
   498  			name: "insecure",
   499  			in: &api.Pod{
   500  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   501  				Spec: api.PodSpec{
   502  					Containers: []api.Container{
   503  						{Name: "mycontainer"},
   504  					},
   505  					NodeName: "foo",
   506  				},
   507  				Status: api.PodStatus{},
   508  			},
   509  			opts: &api.PodLogOptions{
   510  				InsecureSkipTLSVerifyBackend: true,
   511  			},
   512  			expectedErr:       nil,
   513  			expectedTransport: fakeInsecureRoundTripper,
   514  		},
   515  		{
   516  			name: "missing container",
   517  			in: &api.Pod{
   518  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   519  				Spec:       api.PodSpec{},
   520  				Status:     api.PodStatus{},
   521  			},
   522  			opts:              &api.PodLogOptions{},
   523  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test"),
   524  			expectedTransport: nil,
   525  		},
   526  		{
   527  			name: "choice of two containers",
   528  			in: &api.Pod{
   529  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   530  				Spec: api.PodSpec{
   531  					Containers: []api.Container{
   532  						{Name: "container1"},
   533  						{Name: "container2"},
   534  					},
   535  				},
   536  				Status: api.PodStatus{},
   537  			},
   538  			opts:              &api.PodLogOptions{},
   539  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
   540  			expectedTransport: nil,
   541  		},
   542  		{
   543  			name: "initcontainers",
   544  			in: &api.Pod{
   545  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   546  				Spec: api.PodSpec{
   547  					Containers: []api.Container{
   548  						{Name: "container1"},
   549  						{Name: "container2"},
   550  					},
   551  					InitContainers: []api.Container{
   552  						{Name: "initcontainer1"},
   553  					},
   554  				},
   555  				Status: api.PodStatus{},
   556  			},
   557  			opts:              &api.PodLogOptions{},
   558  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2]"),
   559  			expectedTransport: nil,
   560  		},
   561  		{
   562  			in: &api.Pod{
   563  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   564  				Spec: api.PodSpec{
   565  					Containers: []api.Container{
   566  						{Name: "container1"},
   567  						{Name: "container2"},
   568  					},
   569  					InitContainers: []api.Container{
   570  						{Name: "initcontainer1"},
   571  					},
   572  					EphemeralContainers: []api.EphemeralContainer{
   573  						{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debugger"}},
   574  					},
   575  				},
   576  				Status: api.PodStatus{},
   577  			},
   578  			opts:              &api.PodLogOptions{},
   579  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2 debugger]"),
   580  			expectedTransport: nil,
   581  		},
   582  		{
   583  			name: "bad container",
   584  			in: &api.Pod{
   585  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   586  				Spec: api.PodSpec{
   587  					Containers: []api.Container{
   588  						{Name: "container1"},
   589  						{Name: "container2"},
   590  					},
   591  				},
   592  				Status: api.PodStatus{},
   593  			},
   594  			opts: &api.PodLogOptions{
   595  				Container: "unknown",
   596  			},
   597  			expectedErr:       errors.NewBadRequest("container unknown is not valid for pod test"),
   598  			expectedTransport: nil,
   599  		},
   600  		{
   601  			name: "good with two containers",
   602  			in: &api.Pod{
   603  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   604  				Spec: api.PodSpec{
   605  					Containers: []api.Container{
   606  						{Name: "container1"},
   607  						{Name: "container2"},
   608  					},
   609  					NodeName: "foo",
   610  				},
   611  				Status: api.PodStatus{},
   612  			},
   613  			opts: &api.PodLogOptions{
   614  				Container: "container2",
   615  			},
   616  			expectedErr:       nil,
   617  			expectedTransport: fakeSecureRoundTripper,
   618  		},
   619  	}
   620  	for _, tc := range tcs {
   621  		t.Run(tc.name, func(t *testing.T) {
   622  			getter := &mockPodGetter{tc.in}
   623  			connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{
   624  				Transport:                      fakeSecureRoundTripper,
   625  				InsecureSkipTLSVerifyTransport: fakeInsecureRoundTripper,
   626  			}}
   627  
   628  			_, actualTransport, err := LogLocation(ctx, getter, connectionGetter, fakePodName, tc.opts)
   629  			if !reflect.DeepEqual(err, tc.expectedErr) {
   630  				t.Errorf("expected %q, got %q", tc.expectedErr, err)
   631  			}
   632  			if actualTransport != tc.expectedTransport {
   633  				t.Errorf("expected %q, got %q", tc.expectedTransport, actualTransport)
   634  			}
   635  		})
   636  	}
   637  }
   638  
   639  func TestSelectableFieldLabelConversions(t *testing.T) {
   640  	apitesting.TestSelectableFieldLabelConversionsOfKind(t,
   641  		"v1",
   642  		"Pod",
   643  		ToSelectableFields(&api.Pod{}),
   644  		nil,
   645  	)
   646  }
   647  
   648  type mockConnectionInfoGetter struct {
   649  	info *client.ConnectionInfo
   650  }
   651  
   652  func (g mockConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*client.ConnectionInfo, error) {
   653  	return g.info, nil
   654  }
   655  
   656  func TestPortForwardLocation(t *testing.T) {
   657  	ctx := genericapirequest.NewDefaultContext()
   658  	tcs := []struct {
   659  		in          *api.Pod
   660  		info        *client.ConnectionInfo
   661  		opts        *api.PodPortForwardOptions
   662  		expectedErr error
   663  		expectedURL *url.URL
   664  	}{
   665  		{
   666  			in: &api.Pod{
   667  				Spec: api.PodSpec{},
   668  			},
   669  			opts:        &api.PodPortForwardOptions{},
   670  			expectedErr: errors.NewBadRequest("pod test does not have a host assigned"),
   671  		},
   672  		{
   673  			in: &api.Pod{
   674  				ObjectMeta: metav1.ObjectMeta{
   675  					Namespace: "ns",
   676  					Name:      "pod1",
   677  				},
   678  				Spec: api.PodSpec{
   679  					NodeName: "node1",
   680  				},
   681  			},
   682  			info:        &client.ConnectionInfo{},
   683  			opts:        &api.PodPortForwardOptions{},
   684  			expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"},
   685  		},
   686  		{
   687  			in: &api.Pod{
   688  				ObjectMeta: metav1.ObjectMeta{
   689  					Namespace: "ns",
   690  					Name:      "pod1",
   691  				},
   692  				Spec: api.PodSpec{
   693  					NodeName: "node1",
   694  				},
   695  			},
   696  			info:        &client.ConnectionInfo{},
   697  			opts:        &api.PodPortForwardOptions{Ports: []int32{80}},
   698  			expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"},
   699  		},
   700  	}
   701  	for _, tc := range tcs {
   702  		getter := &mockPodGetter{tc.in}
   703  		connectionGetter := &mockConnectionInfoGetter{tc.info}
   704  		loc, _, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts)
   705  		if !reflect.DeepEqual(err, tc.expectedErr) {
   706  			t.Errorf("expected %v, got %v", tc.expectedErr, err)
   707  		}
   708  		if !reflect.DeepEqual(loc, tc.expectedURL) {
   709  			t.Errorf("expected %v, got %v", tc.expectedURL, loc)
   710  		}
   711  	}
   712  }
   713  
   714  func TestGetPodIP(t *testing.T) {
   715  	testCases := []struct {
   716  		name       string
   717  		pod        *api.Pod
   718  		expectedIP string
   719  	}{
   720  		{
   721  			name:       "nil pod",
   722  			pod:        nil,
   723  			expectedIP: "",
   724  		},
   725  		{
   726  			name: "no status object",
   727  			pod: &api.Pod{
   728  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   729  				Spec:       api.PodSpec{},
   730  			},
   731  			expectedIP: "",
   732  		},
   733  		{
   734  			name: "no pod ips",
   735  			pod: &api.Pod{
   736  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   737  				Spec:       api.PodSpec{},
   738  				Status:     api.PodStatus{},
   739  			},
   740  			expectedIP: "",
   741  		},
   742  		{
   743  			name: "empty list",
   744  			pod: &api.Pod{
   745  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   746  				Spec:       api.PodSpec{},
   747  				Status: api.PodStatus{
   748  					PodIPs: []api.PodIP{},
   749  				},
   750  			},
   751  			expectedIP: "",
   752  		},
   753  		{
   754  			name: "1 ip",
   755  			pod: &api.Pod{
   756  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   757  				Spec:       api.PodSpec{},
   758  				Status: api.PodStatus{
   759  					PodIPs: []api.PodIP{
   760  						{IP: "10.0.0.10"},
   761  					},
   762  				},
   763  			},
   764  			expectedIP: "10.0.0.10",
   765  		},
   766  		{
   767  			name: "multiple ips",
   768  			pod: &api.Pod{
   769  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   770  				Spec:       api.PodSpec{},
   771  				Status: api.PodStatus{
   772  					PodIPs: []api.PodIP{
   773  						{IP: "10.0.0.10"},
   774  						{IP: "10.0.0.20"},
   775  					},
   776  				},
   777  			},
   778  			expectedIP: "10.0.0.10",
   779  		},
   780  	}
   781  	for _, tc := range testCases {
   782  		t.Run(tc.name, func(t *testing.T) {
   783  			podIP := getPodIP(tc.pod)
   784  			if podIP != tc.expectedIP {
   785  				t.Errorf("expected pod ip:%v does not match actual %v", tc.expectedIP, podIP)
   786  			}
   787  		})
   788  	}
   789  }
   790  
   791  type fakeTransport struct {
   792  	val string
   793  }
   794  
   795  func (f fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
   796  	return nil, nil
   797  }
   798  
   799  var (
   800  	fakeSecureRoundTripper   = fakeTransport{val: "secure"}
   801  	fakeInsecureRoundTripper = fakeTransport{val: "insecure"}
   802  )
   803  
   804  func TestPodIndexFunc(t *testing.T) {
   805  	tcs := []struct {
   806  		name          string
   807  		indexFunc     cache.IndexFunc
   808  		pod           interface{}
   809  		expectedValue string
   810  		expectedErr   error
   811  	}{
   812  		{
   813  			name:      "node name index",
   814  			indexFunc: NodeNameIndexFunc,
   815  			pod: &api.Pod{
   816  				Spec: api.PodSpec{
   817  					NodeName: "test-pod",
   818  				},
   819  			},
   820  			expectedValue: "test-pod",
   821  			expectedErr:   nil,
   822  		},
   823  		{
   824  			name:          "not a pod failed",
   825  			indexFunc:     NodeNameIndexFunc,
   826  			pod:           "not a pod object",
   827  			expectedValue: "test-pod",
   828  			expectedErr:   fmt.Errorf("not a pod"),
   829  		},
   830  	}
   831  
   832  	for _, tc := range tcs {
   833  		indexValues, err := tc.indexFunc(tc.pod)
   834  		if !reflect.DeepEqual(err, tc.expectedErr) {
   835  			t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedErr, err)
   836  		}
   837  		if err == nil && len(indexValues) != 1 && indexValues[0] != tc.expectedValue {
   838  			t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedValue, indexValues)
   839  		}
   840  
   841  	}
   842  }
   843  
   844  func newPodWithHugePageValue(resourceName api.ResourceName, value resource.Quantity) *api.Pod {
   845  	return &api.Pod{
   846  		ObjectMeta: metav1.ObjectMeta{
   847  			Namespace:       "default",
   848  			Name:            "foo",
   849  			ResourceVersion: "1",
   850  		},
   851  		Spec: api.PodSpec{
   852  			RestartPolicy: api.RestartPolicyAlways,
   853  			DNSPolicy:     api.DNSDefault,
   854  			Containers: []api.Container{{
   855  				Name:                     "foo",
   856  				Image:                    "image",
   857  				ImagePullPolicy:          "IfNotPresent",
   858  				TerminationMessagePolicy: "File",
   859  				Resources: api.ResourceRequirements{
   860  					Requests: api.ResourceList{
   861  						api.ResourceCPU: resource.MustParse("10"),
   862  						resourceName:    value,
   863  					},
   864  					Limits: api.ResourceList{
   865  						api.ResourceCPU: resource.MustParse("10"),
   866  						resourceName:    value,
   867  					},
   868  				}},
   869  			},
   870  		},
   871  	}
   872  }
   873  
   874  func TestPodStrategyValidate(t *testing.T) {
   875  	const containerName = "container"
   876  
   877  	tests := []struct {
   878  		name    string
   879  		pod     *api.Pod
   880  		wantErr bool
   881  	}{
   882  		{
   883  			name:    "a new pod setting container with indivisible hugepages values",
   884  			pod:     newPodWithHugePageValue(api.ResourceHugePagesPrefix+"1Mi", resource.MustParse("1.1Mi")),
   885  			wantErr: true,
   886  		},
   887  		{
   888  			name: "a new pod setting init-container with indivisible hugepages values",
   889  			pod: &api.Pod{
   890  				ObjectMeta: metav1.ObjectMeta{
   891  					Namespace: "default",
   892  					Name:      "foo",
   893  				},
   894  				Spec: api.PodSpec{
   895  					RestartPolicy: api.RestartPolicyAlways,
   896  					DNSPolicy:     api.DNSDefault,
   897  					InitContainers: []api.Container{{
   898  						Name:                     containerName,
   899  						Image:                    "image",
   900  						ImagePullPolicy:          "IfNotPresent",
   901  						TerminationMessagePolicy: "File",
   902  						Resources: api.ResourceRequirements{
   903  							Requests: api.ResourceList{
   904  								api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"),
   905  							},
   906  							Limits: api.ResourceList{
   907  								api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"),
   908  							},
   909  						}},
   910  					},
   911  				},
   912  			},
   913  			wantErr: true,
   914  		},
   915  		{
   916  			name: "a new pod setting init-container with indivisible hugepages values while container with divisible hugepages values",
   917  			pod: &api.Pod{
   918  				ObjectMeta: metav1.ObjectMeta{
   919  					Namespace: "default",
   920  					Name:      "foo",
   921  				},
   922  				Spec: api.PodSpec{
   923  					RestartPolicy: api.RestartPolicyAlways,
   924  					DNSPolicy:     api.DNSDefault,
   925  					InitContainers: []api.Container{{
   926  						Name:                     containerName,
   927  						Image:                    "image",
   928  						ImagePullPolicy:          "IfNotPresent",
   929  						TerminationMessagePolicy: "File",
   930  						Resources: api.ResourceRequirements{
   931  							Requests: api.ResourceList{
   932  								api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"),
   933  							},
   934  							Limits: api.ResourceList{
   935  								api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"),
   936  							},
   937  						}},
   938  					},
   939  					Containers: []api.Container{{
   940  						Name:                     containerName,
   941  						Image:                    "image",
   942  						ImagePullPolicy:          "IfNotPresent",
   943  						TerminationMessagePolicy: "File",
   944  						Resources: api.ResourceRequirements{
   945  							Requests: api.ResourceList{
   946  								api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"),
   947  							},
   948  							Limits: api.ResourceList{
   949  								api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"),
   950  							},
   951  						}},
   952  					},
   953  				},
   954  			},
   955  			wantErr: true,
   956  		},
   957  		{
   958  			name: "a new pod setting container with divisible hugepages values",
   959  			pod: &api.Pod{
   960  				ObjectMeta: metav1.ObjectMeta{
   961  					Namespace: "default",
   962  					Name:      "foo",
   963  				},
   964  				Spec: api.PodSpec{
   965  					RestartPolicy: api.RestartPolicyAlways,
   966  					DNSPolicy:     api.DNSDefault,
   967  					Containers: []api.Container{{
   968  						Name:                     containerName,
   969  						Image:                    "image",
   970  						ImagePullPolicy:          "IfNotPresent",
   971  						TerminationMessagePolicy: "File",
   972  						Resources: api.ResourceRequirements{
   973  							Requests: api.ResourceList{
   974  								api.ResourceName(api.ResourceCPU):                     resource.MustParse("10"),
   975  								api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"),
   976  							},
   977  							Limits: api.ResourceList{
   978  								api.ResourceName(api.ResourceCPU):                     resource.MustParse("10"),
   979  								api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"),
   980  							},
   981  						}},
   982  					},
   983  				},
   984  			},
   985  		},
   986  	}
   987  
   988  	for _, tc := range tests {
   989  		t.Run(tc.name, func(t *testing.T) {
   990  			errs := Strategy.Validate(genericapirequest.NewContext(), tc.pod)
   991  			if tc.wantErr && len(errs) == 0 {
   992  				t.Errorf("expected errors but got none")
   993  			}
   994  			if !tc.wantErr && len(errs) != 0 {
   995  				t.Errorf("unexpected errors: %v", errs.ToAggregate())
   996  			}
   997  		})
   998  	}
   999  }
  1000  
  1001  func TestEphemeralContainerStrategyValidateUpdate(t *testing.T) {
  1002  
  1003  	test := []struct {
  1004  		name   string
  1005  		newPod *api.Pod
  1006  		oldPod *api.Pod
  1007  	}{
  1008  		{
  1009  			name: "add ephemeral container to regular pod and expect success",
  1010  			oldPod: &api.Pod{
  1011  				ObjectMeta: metav1.ObjectMeta{
  1012  					Name:            "test-pod",
  1013  					Namespace:       "test-ns",
  1014  					ResourceVersion: "1",
  1015  				},
  1016  				Spec: api.PodSpec{
  1017  					RestartPolicy: api.RestartPolicyAlways,
  1018  					DNSPolicy:     api.DNSDefault,
  1019  					Containers: []api.Container{
  1020  						{
  1021  							Name:                     "container",
  1022  							Image:                    "image",
  1023  							ImagePullPolicy:          "IfNotPresent",
  1024  							TerminationMessagePolicy: "File",
  1025  						},
  1026  					},
  1027  				},
  1028  			},
  1029  			newPod: &api.Pod{
  1030  				ObjectMeta: metav1.ObjectMeta{
  1031  					Name:            "test-pod",
  1032  					Namespace:       "test-ns",
  1033  					ResourceVersion: "1",
  1034  				},
  1035  				Spec: api.PodSpec{
  1036  					RestartPolicy: api.RestartPolicyAlways,
  1037  					DNSPolicy:     api.DNSDefault,
  1038  					Containers: []api.Container{
  1039  						{
  1040  							Name:                     "container",
  1041  							Image:                    "image",
  1042  							ImagePullPolicy:          "IfNotPresent",
  1043  							TerminationMessagePolicy: "File",
  1044  						},
  1045  					},
  1046  					EphemeralContainers: []api.EphemeralContainer{
  1047  						{
  1048  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1049  								Name:                     "debugger",
  1050  								Image:                    "image",
  1051  								ImagePullPolicy:          "IfNotPresent",
  1052  								TerminationMessagePolicy: "File",
  1053  							},
  1054  						},
  1055  					},
  1056  				},
  1057  			},
  1058  		},
  1059  	}
  1060  
  1061  	// expect no errors
  1062  	for _, tc := range test {
  1063  		t.Run(tc.name, func(t *testing.T) {
  1064  			if errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 {
  1065  				t.Errorf("unexpected error:%v", errs)
  1066  			}
  1067  		})
  1068  	}
  1069  
  1070  	test = []struct {
  1071  		name   string
  1072  		newPod *api.Pod
  1073  		oldPod *api.Pod
  1074  	}{
  1075  		{
  1076  			name: "add ephemeral container to static pod and expect failure",
  1077  			oldPod: &api.Pod{
  1078  				ObjectMeta: metav1.ObjectMeta{
  1079  					Name:            "test-pod",
  1080  					Namespace:       "test-ns",
  1081  					ResourceVersion: "1",
  1082  					Annotations:     map[string]string{api.MirrorPodAnnotationKey: "someVal"},
  1083  				},
  1084  				Spec: api.PodSpec{
  1085  					RestartPolicy: api.RestartPolicyAlways,
  1086  					DNSPolicy:     api.DNSDefault,
  1087  					Containers: []api.Container{
  1088  						{
  1089  							Name:                     "container",
  1090  							Image:                    "image",
  1091  							ImagePullPolicy:          "IfNotPresent",
  1092  							TerminationMessagePolicy: "File",
  1093  						},
  1094  					},
  1095  					NodeName: "example.com",
  1096  				},
  1097  			},
  1098  			newPod: &api.Pod{
  1099  				ObjectMeta: metav1.ObjectMeta{
  1100  					Name:            "test-pod",
  1101  					Namespace:       "test-ns",
  1102  					ResourceVersion: "1",
  1103  					Annotations:     map[string]string{api.MirrorPodAnnotationKey: "someVal"},
  1104  				},
  1105  				Spec: api.PodSpec{
  1106  					RestartPolicy: api.RestartPolicyAlways,
  1107  					DNSPolicy:     api.DNSDefault,
  1108  					Containers: []api.Container{
  1109  						{
  1110  							Name:                     "container",
  1111  							Image:                    "image",
  1112  							ImagePullPolicy:          "IfNotPresent",
  1113  							TerminationMessagePolicy: "File",
  1114  						},
  1115  					},
  1116  					EphemeralContainers: []api.EphemeralContainer{
  1117  						{
  1118  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1119  								Name:                     "debugger",
  1120  								Image:                    "image",
  1121  								ImagePullPolicy:          "IfNotPresent",
  1122  								TerminationMessagePolicy: "File",
  1123  							},
  1124  						},
  1125  					},
  1126  					NodeName: "example.com",
  1127  				},
  1128  			},
  1129  		},
  1130  		{
  1131  			name: "remove ephemeral container from regular pod and expect failure",
  1132  			newPod: &api.Pod{
  1133  				ObjectMeta: metav1.ObjectMeta{
  1134  					Name:            "test-pod",
  1135  					Namespace:       "test-ns",
  1136  					ResourceVersion: "1",
  1137  				},
  1138  				Spec: api.PodSpec{
  1139  					RestartPolicy: api.RestartPolicyAlways,
  1140  					DNSPolicy:     api.DNSDefault,
  1141  					Containers: []api.Container{
  1142  						{
  1143  							Name:                     "container",
  1144  							Image:                    "image",
  1145  							ImagePullPolicy:          "IfNotPresent",
  1146  							TerminationMessagePolicy: "File",
  1147  						},
  1148  					},
  1149  				},
  1150  			},
  1151  			oldPod: &api.Pod{
  1152  				ObjectMeta: metav1.ObjectMeta{
  1153  					Name:            "test-pod",
  1154  					Namespace:       "test-ns",
  1155  					ResourceVersion: "1",
  1156  				},
  1157  				Spec: api.PodSpec{
  1158  					RestartPolicy: api.RestartPolicyAlways,
  1159  					DNSPolicy:     api.DNSDefault,
  1160  					Containers: []api.Container{
  1161  						{
  1162  							Name:                     "container",
  1163  							Image:                    "image",
  1164  							ImagePullPolicy:          "IfNotPresent",
  1165  							TerminationMessagePolicy: "File",
  1166  						},
  1167  					},
  1168  					EphemeralContainers: []api.EphemeralContainer{
  1169  						{
  1170  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1171  								Name:                     "debugger",
  1172  								Image:                    "image",
  1173  								ImagePullPolicy:          "IfNotPresent",
  1174  								TerminationMessagePolicy: "File",
  1175  							},
  1176  						},
  1177  					},
  1178  				},
  1179  			},
  1180  		},
  1181  		{
  1182  			name: "change ephemeral container from regular pod and expect failure",
  1183  			newPod: &api.Pod{
  1184  				ObjectMeta: metav1.ObjectMeta{
  1185  					Name:            "test-pod",
  1186  					Namespace:       "test-ns",
  1187  					ResourceVersion: "1",
  1188  				},
  1189  				Spec: api.PodSpec{
  1190  					RestartPolicy: api.RestartPolicyAlways,
  1191  					DNSPolicy:     api.DNSDefault,
  1192  					Containers: []api.Container{
  1193  						{
  1194  							Name:                     "container",
  1195  							Image:                    "image",
  1196  							ImagePullPolicy:          "IfNotPresent",
  1197  							TerminationMessagePolicy: "File",
  1198  						},
  1199  					},
  1200  					EphemeralContainers: []api.EphemeralContainer{
  1201  						{
  1202  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1203  								Name:                     "debugger",
  1204  								Image:                    "image2",
  1205  								ImagePullPolicy:          "IfNotPresent",
  1206  								TerminationMessagePolicy: "File",
  1207  							},
  1208  						},
  1209  					},
  1210  				},
  1211  			},
  1212  			oldPod: &api.Pod{
  1213  				ObjectMeta: metav1.ObjectMeta{
  1214  					Name:            "test-pod",
  1215  					Namespace:       "test-ns",
  1216  					ResourceVersion: "1",
  1217  				},
  1218  				Spec: api.PodSpec{
  1219  					RestartPolicy: api.RestartPolicyAlways,
  1220  					DNSPolicy:     api.DNSDefault,
  1221  					Containers: []api.Container{
  1222  						{
  1223  							Name:                     "container",
  1224  							Image:                    "image",
  1225  							ImagePullPolicy:          "IfNotPresent",
  1226  							TerminationMessagePolicy: "File",
  1227  						},
  1228  					},
  1229  					EphemeralContainers: []api.EphemeralContainer{
  1230  						{
  1231  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1232  								Name:                     "debugger",
  1233  								Image:                    "image",
  1234  								ImagePullPolicy:          "IfNotPresent",
  1235  								TerminationMessagePolicy: "File",
  1236  							},
  1237  						},
  1238  					},
  1239  				},
  1240  			},
  1241  		},
  1242  	}
  1243  
  1244  	// expect one error
  1245  	for _, tc := range test {
  1246  		t.Run(tc.name, func(t *testing.T) {
  1247  			errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod)
  1248  			if len(errs) == 0 {
  1249  				t.Errorf("unexpected success:ephemeral containers are not supported for static pods")
  1250  			} else if len(errs) != 1 {
  1251  				t.Errorf("unexpected errors:expected one error about ephemeral containers are not supported for static pods:got:%v:", errs)
  1252  			}
  1253  		})
  1254  	}
  1255  }
  1256  
  1257  func TestPodStrategyValidateUpdate(t *testing.T) {
  1258  	test := []struct {
  1259  		name   string
  1260  		newPod *api.Pod
  1261  		oldPod *api.Pod
  1262  	}{
  1263  		{
  1264  			name:   "an existing pod with indivisible hugepages values to a new pod with indivisible hugepages values",
  1265  			newPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")),
  1266  			oldPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")),
  1267  		},
  1268  	}
  1269  
  1270  	for _, tc := range test {
  1271  		t.Run(tc.name, func(t *testing.T) {
  1272  			if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 {
  1273  				t.Errorf("unexpected error:%v", errs)
  1274  			}
  1275  		})
  1276  	}
  1277  }
  1278  
  1279  func TestDropNonEphemeralContainerUpdates(t *testing.T) {
  1280  	tests := []struct {
  1281  		name                    string
  1282  		oldPod, newPod, wantPod *api.Pod
  1283  	}{
  1284  		{
  1285  			name: "simple ephemeral container append",
  1286  			oldPod: &api.Pod{
  1287  				ObjectMeta: metav1.ObjectMeta{
  1288  					Name:            "test-pod",
  1289  					Namespace:       "test-ns",
  1290  					ResourceVersion: "1",
  1291  				},
  1292  				Spec: api.PodSpec{
  1293  					Containers: []api.Container{
  1294  						{
  1295  							Name:  "container",
  1296  							Image: "image",
  1297  						},
  1298  					},
  1299  				},
  1300  			},
  1301  			newPod: &api.Pod{
  1302  				ObjectMeta: metav1.ObjectMeta{
  1303  					Name:            "test-pod",
  1304  					Namespace:       "test-ns",
  1305  					ResourceVersion: "1",
  1306  				},
  1307  				Spec: api.PodSpec{
  1308  					Containers: []api.Container{
  1309  						{
  1310  							Name:  "container",
  1311  							Image: "image",
  1312  						},
  1313  					},
  1314  					EphemeralContainers: []api.EphemeralContainer{
  1315  						{
  1316  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1317  								Name:  "container",
  1318  								Image: "image",
  1319  							},
  1320  						},
  1321  					},
  1322  				},
  1323  			},
  1324  			wantPod: &api.Pod{
  1325  				ObjectMeta: metav1.ObjectMeta{
  1326  					Name:            "test-pod",
  1327  					Namespace:       "test-ns",
  1328  					ResourceVersion: "1",
  1329  				},
  1330  				Spec: api.PodSpec{
  1331  					Containers: []api.Container{
  1332  						{
  1333  							Name:  "container",
  1334  							Image: "image",
  1335  						},
  1336  					},
  1337  					EphemeralContainers: []api.EphemeralContainer{
  1338  						{
  1339  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1340  								Name:  "container",
  1341  								Image: "image",
  1342  							},
  1343  						},
  1344  					},
  1345  				},
  1346  			},
  1347  		},
  1348  		{
  1349  			name: "whoops wrong pod",
  1350  			oldPod: &api.Pod{
  1351  				ObjectMeta: metav1.ObjectMeta{
  1352  					Name:            "test-pod",
  1353  					Namespace:       "test-ns",
  1354  					ResourceVersion: "1",
  1355  					UID:             "blue",
  1356  				},
  1357  			},
  1358  			newPod: &api.Pod{
  1359  				ObjectMeta: metav1.ObjectMeta{
  1360  					Name:            "new-pod",
  1361  					Namespace:       "new-ns",
  1362  					ResourceVersion: "1",
  1363  					UID:             "green",
  1364  				},
  1365  			},
  1366  			wantPod: &api.Pod{
  1367  				ObjectMeta: metav1.ObjectMeta{
  1368  					Name:            "new-pod",
  1369  					Namespace:       "new-ns",
  1370  					ResourceVersion: "1",
  1371  					UID:             "green",
  1372  				},
  1373  			},
  1374  		},
  1375  		{
  1376  			name: "resource conflict during update",
  1377  			oldPod: &api.Pod{
  1378  				ObjectMeta: metav1.ObjectMeta{
  1379  					Name:            "test-pod",
  1380  					Namespace:       "test-ns",
  1381  					ResourceVersion: "2",
  1382  					UID:             "blue",
  1383  				},
  1384  			},
  1385  			newPod: &api.Pod{
  1386  				ObjectMeta: metav1.ObjectMeta{
  1387  					Name:            "test-pod",
  1388  					Namespace:       "test-ns",
  1389  					ResourceVersion: "1",
  1390  					UID:             "blue",
  1391  				},
  1392  			},
  1393  			wantPod: &api.Pod{
  1394  				ObjectMeta: metav1.ObjectMeta{
  1395  					Name:            "test-pod",
  1396  					Namespace:       "test-ns",
  1397  					ResourceVersion: "1",
  1398  					UID:             "blue",
  1399  				},
  1400  			},
  1401  		},
  1402  		{
  1403  			name: "drop non-ephemeral container changes",
  1404  			oldPod: &api.Pod{
  1405  				ObjectMeta: metav1.ObjectMeta{
  1406  					Name:            "test-pod",
  1407  					Namespace:       "test-ns",
  1408  					ResourceVersion: "1",
  1409  					Annotations:     map[string]string{"foo": "bar"},
  1410  				},
  1411  				Spec: api.PodSpec{
  1412  					Containers: []api.Container{
  1413  						{
  1414  							Name:  "container",
  1415  							Image: "image",
  1416  						},
  1417  					},
  1418  				},
  1419  			},
  1420  			newPod: &api.Pod{
  1421  				ObjectMeta: metav1.ObjectMeta{
  1422  					Name:            "test-pod",
  1423  					Namespace:       "test-ns",
  1424  					ResourceVersion: "1",
  1425  					Annotations:     map[string]string{"foo": "bar", "whiz": "pop"},
  1426  					Finalizers:      []string{"milo"},
  1427  				},
  1428  				Spec: api.PodSpec{
  1429  					Containers: []api.Container{
  1430  						{
  1431  							Name:  "container",
  1432  							Image: "newimage",
  1433  						},
  1434  					},
  1435  					EphemeralContainers: []api.EphemeralContainer{
  1436  						{
  1437  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1438  								Name:  "container1",
  1439  								Image: "image",
  1440  							},
  1441  						},
  1442  						{
  1443  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1444  								Name:  "container2",
  1445  								Image: "image",
  1446  							},
  1447  						},
  1448  					},
  1449  				},
  1450  				Status: api.PodStatus{
  1451  					Message: "hi.",
  1452  				},
  1453  			},
  1454  			wantPod: &api.Pod{
  1455  				ObjectMeta: metav1.ObjectMeta{
  1456  					Name:            "test-pod",
  1457  					Namespace:       "test-ns",
  1458  					ResourceVersion: "1",
  1459  					Annotations:     map[string]string{"foo": "bar"},
  1460  				},
  1461  				Spec: api.PodSpec{
  1462  					Containers: []api.Container{
  1463  						{
  1464  							Name:  "container",
  1465  							Image: "image",
  1466  						},
  1467  					},
  1468  					EphemeralContainers: []api.EphemeralContainer{
  1469  						{
  1470  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1471  								Name:  "container1",
  1472  								Image: "image",
  1473  							},
  1474  						},
  1475  						{
  1476  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1477  								Name:  "container2",
  1478  								Image: "image",
  1479  							},
  1480  						},
  1481  					},
  1482  				},
  1483  			},
  1484  		},
  1485  	}
  1486  
  1487  	for _, tc := range tests {
  1488  		t.Run(tc.name, func(t *testing.T) {
  1489  			gotPod := dropNonEphemeralContainerUpdates(tc.newPod, tc.oldPod)
  1490  			if diff := cmp.Diff(tc.wantPod, gotPod); diff != "" {
  1491  				t.Errorf("unexpected diff when dropping fields (-want, +got):\n%s", diff)
  1492  			}
  1493  		})
  1494  	}
  1495  }
  1496  
  1497  func TestNodeInclusionPolicyEnablementInCreating(t *testing.T) {
  1498  	var (
  1499  		honor            = api.NodeInclusionPolicyHonor
  1500  		ignore           = api.NodeInclusionPolicyIgnore
  1501  		emptyConstraints = []api.TopologySpreadConstraint{
  1502  			{
  1503  				WhenUnsatisfiable: api.DoNotSchedule,
  1504  				TopologyKey:       "kubernetes.io/hostname",
  1505  				MaxSkew:           1,
  1506  			},
  1507  		}
  1508  		defaultConstraints = []api.TopologySpreadConstraint{
  1509  			{
  1510  				NodeAffinityPolicy: &honor,
  1511  				NodeTaintsPolicy:   &ignore,
  1512  				WhenUnsatisfiable:  api.DoNotSchedule,
  1513  				TopologyKey:        "kubernetes.io/hostname",
  1514  				MaxSkew:            1,
  1515  			},
  1516  		}
  1517  	)
  1518  
  1519  	tests := []struct {
  1520  		name                          string
  1521  		topologySpreadConstraints     []api.TopologySpreadConstraint
  1522  		wantTopologySpreadConstraints []api.TopologySpreadConstraint
  1523  		enableNodeInclusionPolicy     bool
  1524  	}{
  1525  		{
  1526  			name:                          "nodeInclusionPolicy enabled with topology unset",
  1527  			topologySpreadConstraints:     emptyConstraints,
  1528  			wantTopologySpreadConstraints: emptyConstraints,
  1529  			enableNodeInclusionPolicy:     true,
  1530  		},
  1531  		{
  1532  			name:                          "nodeInclusionPolicy enabled with topology configured",
  1533  			topologySpreadConstraints:     defaultConstraints,
  1534  			wantTopologySpreadConstraints: defaultConstraints,
  1535  			enableNodeInclusionPolicy:     true,
  1536  		},
  1537  		{
  1538  			name:                          "nodeInclusionPolicy disabled with topology configured",
  1539  			topologySpreadConstraints:     defaultConstraints,
  1540  			wantTopologySpreadConstraints: emptyConstraints,
  1541  		},
  1542  	}
  1543  
  1544  	for _, tc := range tests {
  1545  		t.Run(tc.name, func(t *testing.T) {
  1546  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tc.enableNodeInclusionPolicy)()
  1547  
  1548  			pod := &api.Pod{
  1549  				ObjectMeta: metav1.ObjectMeta{
  1550  					Namespace: "default",
  1551  					Name:      "foo",
  1552  				},
  1553  				Spec: api.PodSpec{
  1554  					RestartPolicy: api.RestartPolicyAlways,
  1555  					DNSPolicy:     api.DNSDefault,
  1556  					Containers: []api.Container{
  1557  						{
  1558  							Name:                     "container",
  1559  							Image:                    "image",
  1560  							ImagePullPolicy:          "IfNotPresent",
  1561  							TerminationMessagePolicy: "File",
  1562  						},
  1563  					},
  1564  				},
  1565  			}
  1566  			wantPod := pod.DeepCopy()
  1567  			pod.Spec.TopologySpreadConstraints = append(pod.Spec.TopologySpreadConstraints, tc.topologySpreadConstraints...)
  1568  
  1569  			errs := Strategy.Validate(genericapirequest.NewContext(), pod)
  1570  			if len(errs) != 0 {
  1571  				t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1572  			}
  1573  
  1574  			Strategy.PrepareForCreate(genericapirequest.NewContext(), pod)
  1575  			wantPod.Spec.TopologySpreadConstraints = append(wantPod.Spec.TopologySpreadConstraints, tc.wantTopologySpreadConstraints...)
  1576  			if diff := cmp.Diff(wantPod, pod, cmpopts.IgnoreFields(pod.Status, "Phase", "QOSClass")); diff != "" {
  1577  				t.Errorf("%s unexpected result (-want, +got): %s", tc.name, diff)
  1578  			}
  1579  		})
  1580  	}
  1581  }
  1582  
  1583  func TestNodeInclusionPolicyEnablementInUpdating(t *testing.T) {
  1584  	var (
  1585  		honor  = api.NodeInclusionPolicyHonor
  1586  		ignore = api.NodeInclusionPolicyIgnore
  1587  	)
  1588  
  1589  	// Enable the Feature Gate during the first rule creation
  1590  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true)()
  1591  	ctx := genericapirequest.NewDefaultContext()
  1592  
  1593  	pod := &api.Pod{
  1594  		ObjectMeta: metav1.ObjectMeta{
  1595  			Namespace:       "default",
  1596  			Name:            "foo",
  1597  			ResourceVersion: "1",
  1598  		},
  1599  		Spec: api.PodSpec{
  1600  			RestartPolicy: api.RestartPolicyAlways,
  1601  			DNSPolicy:     api.DNSDefault,
  1602  			Containers: []api.Container{
  1603  				{
  1604  					Name:                     "container",
  1605  					Image:                    "image",
  1606  					ImagePullPolicy:          "IfNotPresent",
  1607  					TerminationMessagePolicy: "File",
  1608  				},
  1609  			},
  1610  			TopologySpreadConstraints: []api.TopologySpreadConstraint{
  1611  				{
  1612  					NodeAffinityPolicy: &ignore,
  1613  					NodeTaintsPolicy:   &honor,
  1614  					WhenUnsatisfiable:  api.DoNotSchedule,
  1615  					TopologyKey:        "kubernetes.io/hostname",
  1616  					MaxSkew:            1,
  1617  				},
  1618  			},
  1619  		},
  1620  	}
  1621  
  1622  	errs := Strategy.Validate(ctx, pod)
  1623  	if len(errs) != 0 {
  1624  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1625  	}
  1626  
  1627  	createdPod := pod.DeepCopy()
  1628  	Strategy.PrepareForCreate(ctx, createdPod)
  1629  
  1630  	if len(createdPod.Spec.TopologySpreadConstraints) != 1 ||
  1631  		*createdPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1632  		*createdPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1633  		t.Error("NodeInclusionPolicy created with unexpected result")
  1634  	}
  1635  
  1636  	// Disable the Feature Gate and expect these fields still exist after updating.
  1637  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, false)()
  1638  
  1639  	updatedPod := createdPod.DeepCopy()
  1640  	updatedPod.Labels = map[string]string{"foo": "bar"}
  1641  	updatedPod.ResourceVersion = "2"
  1642  
  1643  	errs = Strategy.ValidateUpdate(ctx, updatedPod, createdPod)
  1644  	if len(errs) != 0 {
  1645  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1646  	}
  1647  
  1648  	Strategy.PrepareForUpdate(ctx, updatedPod, createdPod)
  1649  
  1650  	if len(updatedPod.Spec.TopologySpreadConstraints) != 1 ||
  1651  		*updatedPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1652  		*updatedPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1653  		t.Error("NodeInclusionPolicy updated with unexpected result")
  1654  	}
  1655  
  1656  	// Enable the Feature Gate again to check whether configured fields still exist after updating.
  1657  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true)()
  1658  
  1659  	updatedPod2 := updatedPod.DeepCopy()
  1660  	updatedPod2.Labels = map[string]string{"foo": "fuz"}
  1661  	updatedPod2.ResourceVersion = "3"
  1662  
  1663  	errs = Strategy.ValidateUpdate(ctx, updatedPod2, updatedPod)
  1664  	if len(errs) != 0 {
  1665  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1666  	}
  1667  
  1668  	Strategy.PrepareForUpdate(ctx, updatedPod2, updatedPod)
  1669  	if len(updatedPod2.Spec.TopologySpreadConstraints) != 1 ||
  1670  		*updatedPod2.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1671  		*updatedPod2.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1672  		t.Error("NodeInclusionPolicy updated with unexpected result")
  1673  	}
  1674  }
  1675  
  1676  func Test_mutatePodAffinity(t *testing.T) {
  1677  	tests := []struct {
  1678  		name               string
  1679  		pod                *api.Pod
  1680  		wantPod            *api.Pod
  1681  		featureGateEnabled bool
  1682  	}{
  1683  		{
  1684  			name:               "matchLabelKeys are merged into labelSelector with In and mismatchLabelKeys are merged with NotIn",
  1685  			featureGateEnabled: true,
  1686  			pod: &api.Pod{
  1687  				ObjectMeta: metav1.ObjectMeta{
  1688  					Labels: map[string]string{
  1689  						"country": "Japan",
  1690  						"city":    "Kyoto",
  1691  					},
  1692  				},
  1693  				Spec: api.PodSpec{
  1694  					Affinity: &api.Affinity{
  1695  						PodAffinity: &api.PodAffinity{
  1696  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1697  								{
  1698  									LabelSelector: &metav1.LabelSelector{
  1699  										MatchLabels: map[string]string{
  1700  											"region": "Asia",
  1701  										},
  1702  									},
  1703  									MatchLabelKeys:    []string{"country"},
  1704  									MismatchLabelKeys: []string{"city"},
  1705  								},
  1706  							},
  1707  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1708  								{
  1709  									PodAffinityTerm: api.PodAffinityTerm{
  1710  										LabelSelector: &metav1.LabelSelector{
  1711  											MatchLabels: map[string]string{
  1712  												"region": "Asia",
  1713  											},
  1714  										},
  1715  										MatchLabelKeys:    []string{"country"},
  1716  										MismatchLabelKeys: []string{"city"},
  1717  									},
  1718  								},
  1719  							},
  1720  						},
  1721  						PodAntiAffinity: &api.PodAntiAffinity{
  1722  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1723  								{
  1724  									LabelSelector: &metav1.LabelSelector{
  1725  										MatchLabels: map[string]string{
  1726  											"region": "Asia",
  1727  										},
  1728  									},
  1729  									MatchLabelKeys:    []string{"country"},
  1730  									MismatchLabelKeys: []string{"city"},
  1731  								},
  1732  							},
  1733  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1734  								{
  1735  									PodAffinityTerm: api.PodAffinityTerm{
  1736  										LabelSelector: &metav1.LabelSelector{
  1737  											MatchLabels: map[string]string{
  1738  												"region": "Asia",
  1739  											},
  1740  										},
  1741  										MatchLabelKeys:    []string{"country"},
  1742  										MismatchLabelKeys: []string{"city"},
  1743  									},
  1744  								},
  1745  							},
  1746  						},
  1747  					},
  1748  				},
  1749  			},
  1750  			wantPod: &api.Pod{
  1751  				ObjectMeta: metav1.ObjectMeta{
  1752  					Labels: map[string]string{
  1753  						"country": "Japan",
  1754  						"city":    "Kyoto",
  1755  					},
  1756  				},
  1757  				Spec: api.PodSpec{
  1758  					Affinity: &api.Affinity{
  1759  						PodAffinity: &api.PodAffinity{
  1760  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1761  								{
  1762  									LabelSelector: &metav1.LabelSelector{
  1763  										MatchLabels: map[string]string{
  1764  											"region": "Asia",
  1765  										},
  1766  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1767  											{
  1768  												Key:      "country",
  1769  												Operator: metav1.LabelSelectorOpIn,
  1770  												Values:   []string{"Japan"},
  1771  											},
  1772  											{
  1773  												Key:      "city",
  1774  												Operator: metav1.LabelSelectorOpNotIn,
  1775  												Values:   []string{"Kyoto"},
  1776  											},
  1777  										},
  1778  									},
  1779  									MatchLabelKeys:    []string{"country"},
  1780  									MismatchLabelKeys: []string{"city"},
  1781  								},
  1782  							},
  1783  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1784  								{
  1785  									PodAffinityTerm: api.PodAffinityTerm{
  1786  										LabelSelector: &metav1.LabelSelector{
  1787  											MatchLabels: map[string]string{
  1788  												"region": "Asia",
  1789  											},
  1790  											MatchExpressions: []metav1.LabelSelectorRequirement{
  1791  												{
  1792  													Key:      "country",
  1793  													Operator: metav1.LabelSelectorOpIn,
  1794  													Values:   []string{"Japan"},
  1795  												},
  1796  												{
  1797  													Key:      "city",
  1798  													Operator: metav1.LabelSelectorOpNotIn,
  1799  													Values:   []string{"Kyoto"},
  1800  												},
  1801  											},
  1802  										},
  1803  										MatchLabelKeys:    []string{"country"},
  1804  										MismatchLabelKeys: []string{"city"},
  1805  									},
  1806  								},
  1807  							},
  1808  						},
  1809  						PodAntiAffinity: &api.PodAntiAffinity{
  1810  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1811  								{
  1812  									LabelSelector: &metav1.LabelSelector{
  1813  										MatchLabels: map[string]string{
  1814  											"region": "Asia",
  1815  										},
  1816  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1817  											{
  1818  												Key:      "country",
  1819  												Operator: metav1.LabelSelectorOpIn,
  1820  												Values:   []string{"Japan"},
  1821  											},
  1822  											{
  1823  												Key:      "city",
  1824  												Operator: metav1.LabelSelectorOpNotIn,
  1825  												Values:   []string{"Kyoto"},
  1826  											},
  1827  										},
  1828  									},
  1829  									MatchLabelKeys:    []string{"country"},
  1830  									MismatchLabelKeys: []string{"city"},
  1831  								},
  1832  							},
  1833  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1834  								{
  1835  									PodAffinityTerm: api.PodAffinityTerm{
  1836  										LabelSelector: &metav1.LabelSelector{
  1837  											MatchLabels: map[string]string{
  1838  												"region": "Asia",
  1839  											},
  1840  											MatchExpressions: []metav1.LabelSelectorRequirement{
  1841  												{
  1842  													Key:      "country",
  1843  													Operator: metav1.LabelSelectorOpIn,
  1844  													Values:   []string{"Japan"},
  1845  												},
  1846  												{
  1847  													Key:      "city",
  1848  													Operator: metav1.LabelSelectorOpNotIn,
  1849  													Values:   []string{"Kyoto"},
  1850  												},
  1851  											},
  1852  										},
  1853  										MatchLabelKeys:    []string{"country"},
  1854  										MismatchLabelKeys: []string{"city"},
  1855  									},
  1856  								},
  1857  							},
  1858  						},
  1859  					},
  1860  				},
  1861  			},
  1862  		},
  1863  		{
  1864  			name:               "keys, which are not found in Pod labels, are ignored",
  1865  			featureGateEnabled: true,
  1866  			pod: &api.Pod{
  1867  				ObjectMeta: metav1.ObjectMeta{
  1868  					Labels: map[string]string{
  1869  						"country": "Japan",
  1870  						"city":    "Kyoto",
  1871  					},
  1872  				},
  1873  				Spec: api.PodSpec{
  1874  					Affinity: &api.Affinity{
  1875  						PodAffinity: &api.PodAffinity{
  1876  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1877  								{
  1878  									LabelSelector: &metav1.LabelSelector{
  1879  										MatchLabels: map[string]string{
  1880  											"region": "Asia",
  1881  										},
  1882  									},
  1883  									MatchLabelKeys: []string{"city", "not-found"},
  1884  								},
  1885  							},
  1886  						},
  1887  					},
  1888  				},
  1889  			},
  1890  			wantPod: &api.Pod{
  1891  				ObjectMeta: metav1.ObjectMeta{
  1892  					Labels: map[string]string{
  1893  						"country": "Japan",
  1894  						"city":    "Kyoto",
  1895  					},
  1896  				},
  1897  				Spec: api.PodSpec{
  1898  					Affinity: &api.Affinity{
  1899  						PodAffinity: &api.PodAffinity{
  1900  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1901  								{
  1902  									LabelSelector: &metav1.LabelSelector{
  1903  										MatchLabels: map[string]string{
  1904  											"region": "Asia",
  1905  										},
  1906  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1907  											// "city" should be added correctly even if matchLabelKey has "not-found" key.
  1908  											{
  1909  												Key:      "city",
  1910  												Operator: metav1.LabelSelectorOpIn,
  1911  												Values:   []string{"Kyoto"},
  1912  											},
  1913  										},
  1914  									},
  1915  									MatchLabelKeys: []string{"city", "not-found"},
  1916  								},
  1917  							},
  1918  						},
  1919  					},
  1920  				},
  1921  			},
  1922  		},
  1923  		{
  1924  			name:               "matchLabelKeys is ignored if the labelSelector is nil",
  1925  			featureGateEnabled: true,
  1926  			pod: &api.Pod{
  1927  				ObjectMeta: metav1.ObjectMeta{
  1928  					Labels: map[string]string{
  1929  						"country": "Japan",
  1930  						"city":    "Kyoto",
  1931  					},
  1932  				},
  1933  				Spec: api.PodSpec{
  1934  					Affinity: &api.Affinity{
  1935  						PodAffinity: &api.PodAffinity{
  1936  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1937  								{
  1938  									MatchLabelKeys:    []string{"country"},
  1939  									MismatchLabelKeys: []string{"city"},
  1940  								},
  1941  							},
  1942  						},
  1943  					},
  1944  				},
  1945  			},
  1946  			wantPod: &api.Pod{
  1947  				ObjectMeta: metav1.ObjectMeta{
  1948  					Labels: map[string]string{
  1949  						"country": "Japan",
  1950  						"city":    "Kyoto",
  1951  					},
  1952  				},
  1953  				Spec: api.PodSpec{
  1954  					Affinity: &api.Affinity{
  1955  						PodAffinity: &api.PodAffinity{
  1956  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1957  								{
  1958  									MatchLabelKeys:    []string{"country"},
  1959  									MismatchLabelKeys: []string{"city"},
  1960  								},
  1961  							},
  1962  						},
  1963  					},
  1964  				},
  1965  			},
  1966  		},
  1967  		{
  1968  			name: "the feature gate is disabled and matchLabelKeys is ignored",
  1969  			pod: &api.Pod{
  1970  				ObjectMeta: metav1.ObjectMeta{
  1971  					Labels: map[string]string{
  1972  						"country": "Japan",
  1973  						"city":    "Kyoto",
  1974  					},
  1975  				},
  1976  				Spec: api.PodSpec{
  1977  					Affinity: &api.Affinity{
  1978  						PodAffinity: &api.PodAffinity{
  1979  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1980  								{
  1981  									LabelSelector: &metav1.LabelSelector{
  1982  										MatchLabels: map[string]string{
  1983  											"region": "Asia",
  1984  										},
  1985  									},
  1986  									MatchLabelKeys:    []string{"country"},
  1987  									MismatchLabelKeys: []string{"city"},
  1988  								},
  1989  							},
  1990  						},
  1991  					},
  1992  				},
  1993  			},
  1994  			wantPod: &api.Pod{
  1995  				ObjectMeta: metav1.ObjectMeta{
  1996  					Labels: map[string]string{
  1997  						"country": "Japan",
  1998  						"city":    "Kyoto",
  1999  					},
  2000  				},
  2001  				Spec: api.PodSpec{
  2002  					Affinity: &api.Affinity{
  2003  						PodAffinity: &api.PodAffinity{
  2004  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  2005  								{
  2006  									LabelSelector: &metav1.LabelSelector{
  2007  										MatchLabels: map[string]string{
  2008  											"region": "Asia",
  2009  										},
  2010  									},
  2011  									MatchLabelKeys:    []string{"country"},
  2012  									MismatchLabelKeys: []string{"city"},
  2013  								},
  2014  							},
  2015  						},
  2016  					},
  2017  				},
  2018  			},
  2019  		},
  2020  	}
  2021  
  2022  	for _, tc := range tests {
  2023  		t.Run(tc.name, func(t *testing.T) {
  2024  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, tc.featureGateEnabled)()
  2025  
  2026  			pod := tc.pod
  2027  			mutatePodAffinity(pod)
  2028  			if diff := cmp.Diff(tc.wantPod.Spec.Affinity, pod.Spec.Affinity); diff != "" {
  2029  				t.Errorf("unexpected affinity (-want, +got): %s\n", diff)
  2030  			}
  2031  		})
  2032  	}
  2033  }