...

Source file src/k8s.io/kubernetes/pkg/registry/core/pod/strategy_test.go

Documentation: k8s.io/kubernetes/pkg/registry/core/pod

     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  	"strings"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/google/go-cmp/cmp/cmpopts"
    30  	"github.com/stretchr/testify/assert"
    31  	apiv1 "k8s.io/api/core/v1"
    32  	"k8s.io/apimachinery/pkg/api/errors"
    33  	"k8s.io/apimachinery/pkg/api/resource"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/fields"
    36  	"k8s.io/apimachinery/pkg/labels"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    40  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    41  	"k8s.io/apiserver/pkg/warning"
    42  	"k8s.io/client-go/tools/cache"
    43  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    44  	utilpointer "k8s.io/utils/pointer"
    45  
    46  	apitesting "k8s.io/kubernetes/pkg/api/testing"
    47  	api "k8s.io/kubernetes/pkg/apis/core"
    48  	"k8s.io/kubernetes/pkg/features"
    49  	"k8s.io/kubernetes/pkg/kubelet/client"
    50  
    51  	// ensure types are installed
    52  	_ "k8s.io/kubernetes/pkg/apis/core/install"
    53  )
    54  
    55  func TestMatchPod(t *testing.T) {
    56  	testCases := []struct {
    57  		in            *api.Pod
    58  		fieldSelector fields.Selector
    59  		expectMatch   bool
    60  	}{
    61  		{
    62  			in: &api.Pod{
    63  				Spec: api.PodSpec{NodeName: "nodeA"},
    64  			},
    65  			fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
    66  			expectMatch:   true,
    67  		},
    68  		{
    69  			in: &api.Pod{
    70  				Spec: api.PodSpec{NodeName: "nodeB"},
    71  			},
    72  			fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
    73  			expectMatch:   false,
    74  		},
    75  		{
    76  			in: &api.Pod{
    77  				Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
    78  			},
    79  			fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Always"),
    80  			expectMatch:   true,
    81  		},
    82  		{
    83  			in: &api.Pod{
    84  				Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
    85  			},
    86  			fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Never"),
    87  			expectMatch:   false,
    88  		},
    89  		{
    90  			in: &api.Pod{
    91  				Spec: api.PodSpec{SchedulerName: "scheduler1"},
    92  			},
    93  			fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler1"),
    94  			expectMatch:   true,
    95  		},
    96  		{
    97  			in: &api.Pod{
    98  				Spec: api.PodSpec{SchedulerName: "scheduler1"},
    99  			},
   100  			fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler2"),
   101  			expectMatch:   false,
   102  		},
   103  		{
   104  			in: &api.Pod{
   105  				Spec: api.PodSpec{ServiceAccountName: "serviceAccount1"},
   106  			},
   107  			fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount1"),
   108  			expectMatch:   true,
   109  		},
   110  		{
   111  			in: &api.Pod{
   112  				Spec: api.PodSpec{SchedulerName: "serviceAccount1"},
   113  			},
   114  			fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount2"),
   115  			expectMatch:   false,
   116  		},
   117  		{
   118  			in: &api.Pod{
   119  				Status: api.PodStatus{Phase: api.PodRunning},
   120  			},
   121  			fieldSelector: fields.ParseSelectorOrDie("status.phase=Running"),
   122  			expectMatch:   true,
   123  		},
   124  		{
   125  			in: &api.Pod{
   126  				Status: api.PodStatus{Phase: api.PodRunning},
   127  			},
   128  			fieldSelector: fields.ParseSelectorOrDie("status.phase=Pending"),
   129  			expectMatch:   false,
   130  		},
   131  		{
   132  			in: &api.Pod{
   133  				Status: api.PodStatus{
   134  					PodIPs: []api.PodIP{
   135  						{IP: "1.2.3.4"},
   136  					},
   137  				},
   138  			},
   139  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=1.2.3.4"),
   140  			expectMatch:   true,
   141  		},
   142  		{
   143  			in: &api.Pod{
   144  				Status: api.PodStatus{
   145  					PodIPs: []api.PodIP{
   146  						{IP: "1.2.3.4"},
   147  					},
   148  				},
   149  			},
   150  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=4.3.2.1"),
   151  			expectMatch:   false,
   152  		},
   153  		{
   154  			in: &api.Pod{
   155  				Status: api.PodStatus{NominatedNodeName: "node1"},
   156  			},
   157  			fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node1"),
   158  			expectMatch:   true,
   159  		},
   160  		{
   161  			in: &api.Pod{
   162  				Status: api.PodStatus{NominatedNodeName: "node1"},
   163  			},
   164  			fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node2"),
   165  			expectMatch:   false,
   166  		},
   167  		{
   168  			in: &api.Pod{
   169  				Status: api.PodStatus{
   170  					PodIPs: []api.PodIP{
   171  						{IP: "2001:db8::"},
   172  					},
   173  				},
   174  			},
   175  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db8::"),
   176  			expectMatch:   true,
   177  		},
   178  		{
   179  			in: &api.Pod{
   180  				Status: api.PodStatus{
   181  					PodIPs: []api.PodIP{
   182  						{IP: "2001:db8::"},
   183  					},
   184  				},
   185  			},
   186  			fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db7::"),
   187  			expectMatch:   false,
   188  		},
   189  		{
   190  			in: &api.Pod{
   191  				Spec: api.PodSpec{
   192  					SecurityContext: &api.PodSecurityContext{
   193  						HostNetwork: true,
   194  					},
   195  				},
   196  			},
   197  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"),
   198  			expectMatch:   true,
   199  		},
   200  		{
   201  			in: &api.Pod{
   202  				Spec: api.PodSpec{
   203  					SecurityContext: &api.PodSecurityContext{
   204  						HostNetwork: true,
   205  					},
   206  				},
   207  			},
   208  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   209  			expectMatch:   false,
   210  		},
   211  		{
   212  			in: &api.Pod{
   213  				Spec: api.PodSpec{
   214  					SecurityContext: &api.PodSecurityContext{
   215  						HostNetwork: false,
   216  					},
   217  				},
   218  			},
   219  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   220  			expectMatch:   true,
   221  		},
   222  		{
   223  			in: &api.Pod{
   224  				Spec: api.PodSpec{},
   225  			},
   226  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"),
   227  			expectMatch:   true,
   228  		},
   229  		{
   230  			in: &api.Pod{
   231  				Spec: api.PodSpec{},
   232  			},
   233  			fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"),
   234  			expectMatch:   false,
   235  		},
   236  	}
   237  	for _, testCase := range testCases {
   238  		m := MatchPod(labels.Everything(), testCase.fieldSelector)
   239  		result, err := m.Matches(testCase.in)
   240  		if err != nil {
   241  			t.Errorf("Unexpected error %v", err)
   242  		}
   243  		if result != testCase.expectMatch {
   244  			t.Errorf("Result %v, Expected %v, Selector: %v, Pod: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in)
   245  		}
   246  	}
   247  }
   248  
   249  func getResourceList(cpu, memory string) api.ResourceList {
   250  	res := api.ResourceList{}
   251  	if cpu != "" {
   252  		res[api.ResourceCPU] = resource.MustParse(cpu)
   253  	}
   254  	if memory != "" {
   255  		res[api.ResourceMemory] = resource.MustParse(memory)
   256  	}
   257  	return res
   258  }
   259  
   260  func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
   261  	res := api.ResourceRequirements{}
   262  	res.Requests = requests
   263  	res.Limits = limits
   264  	return res
   265  }
   266  
   267  func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container {
   268  	return api.Container{
   269  		Name:      name,
   270  		Resources: getResourceRequirements(requests, limits),
   271  	}
   272  }
   273  
   274  func newPod(name string, containers []api.Container) *api.Pod {
   275  	return &api.Pod{
   276  		ObjectMeta: metav1.ObjectMeta{
   277  			Name: name,
   278  		},
   279  		Spec: api.PodSpec{
   280  			Containers: containers,
   281  		},
   282  	}
   283  }
   284  
   285  func TestGetPodQOS(t *testing.T) {
   286  	testCases := []struct {
   287  		pod      *api.Pod
   288  		expected api.PodQOSClass
   289  	}{
   290  		{
   291  			pod: newPod("guaranteed", []api.Container{
   292  				newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
   293  			}),
   294  			expected: api.PodQOSGuaranteed,
   295  		},
   296  		{
   297  			pod: newPod("best-effort", []api.Container{
   298  				newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
   299  			}),
   300  			expected: api.PodQOSBestEffort,
   301  		},
   302  		{
   303  			pod: newPod("burstable", []api.Container{
   304  				newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("", "")),
   305  			}),
   306  			expected: api.PodQOSBurstable,
   307  		},
   308  	}
   309  	for id, testCase := range testCases {
   310  		Strategy.PrepareForCreate(genericapirequest.NewContext(), testCase.pod)
   311  		actual := testCase.pod.Status.QOSClass
   312  		if actual != testCase.expected {
   313  			t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
   314  		}
   315  	}
   316  }
   317  
   318  func TestSchedulingGatedCondition(t *testing.T) {
   319  	tests := []struct {
   320  		name string
   321  		pod  *api.Pod
   322  		want api.PodCondition
   323  	}{
   324  		{
   325  			name: "pod without .spec.schedulingGates",
   326  			pod:  &api.Pod{},
   327  			want: api.PodCondition{},
   328  		},
   329  		{
   330  			name: "pod with .spec.schedulingGates",
   331  			pod: &api.Pod{
   332  				Spec: api.PodSpec{
   333  					SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}},
   334  				},
   335  			},
   336  			want: api.PodCondition{
   337  				Type:    api.PodScheduled,
   338  				Status:  api.ConditionFalse,
   339  				Reason:  apiv1.PodReasonSchedulingGated,
   340  				Message: "Scheduling is blocked due to non-empty scheduling gates",
   341  			},
   342  		},
   343  	}
   344  
   345  	for _, tt := range tests {
   346  		t.Run(tt.name, func(t *testing.T) {
   347  			Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod)
   348  			var got api.PodCondition
   349  			for _, condition := range tt.pod.Status.Conditions {
   350  				if condition.Type == api.PodScheduled {
   351  					got = condition
   352  					break
   353  				}
   354  			}
   355  
   356  			if diff := cmp.Diff(tt.want, got); diff != "" {
   357  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
   358  			}
   359  		})
   360  	}
   361  }
   362  
   363  func TestCheckGracefulDelete(t *testing.T) {
   364  	defaultGracePeriod := int64(30)
   365  	tcs := []struct {
   366  		name              string
   367  		pod               *api.Pod
   368  		deleteGracePeriod *int64
   369  		gracePeriod       int64
   370  	}{
   371  		{
   372  			name: "in pending phase with has node name",
   373  			pod: &api.Pod{
   374  				Spec:   api.PodSpec{NodeName: "something"},
   375  				Status: api.PodStatus{Phase: api.PodPending},
   376  			},
   377  			deleteGracePeriod: &defaultGracePeriod,
   378  			gracePeriod:       defaultGracePeriod,
   379  		},
   380  		{
   381  			name: "in failed phase with has node name",
   382  			pod: &api.Pod{
   383  				Spec:   api.PodSpec{NodeName: "something"},
   384  				Status: api.PodStatus{Phase: api.PodFailed},
   385  			},
   386  			deleteGracePeriod: &defaultGracePeriod,
   387  			gracePeriod:       0,
   388  		},
   389  		{
   390  			name: "in failed phase",
   391  			pod: &api.Pod{
   392  				Spec:   api.PodSpec{},
   393  				Status: api.PodStatus{Phase: api.PodPending},
   394  			},
   395  			deleteGracePeriod: &defaultGracePeriod,
   396  			gracePeriod:       0,
   397  		},
   398  		{
   399  			name: "in succeeded phase",
   400  			pod: &api.Pod{
   401  				Spec:   api.PodSpec{},
   402  				Status: api.PodStatus{Phase: api.PodSucceeded},
   403  			},
   404  			deleteGracePeriod: &defaultGracePeriod,
   405  			gracePeriod:       0,
   406  		},
   407  		{
   408  			name: "no phase",
   409  			pod: &api.Pod{
   410  				Spec:   api.PodSpec{},
   411  				Status: api.PodStatus{},
   412  			},
   413  			deleteGracePeriod: &defaultGracePeriod,
   414  			gracePeriod:       0,
   415  		},
   416  		{
   417  			name: "has negative grace period",
   418  			pod: &api.Pod{
   419  				Spec: api.PodSpec{
   420  					NodeName:                      "something",
   421  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
   422  				},
   423  				Status: api.PodStatus{},
   424  			},
   425  			gracePeriod: 1,
   426  		},
   427  	}
   428  	for _, tc := range tcs {
   429  		t.Run(tc.name, func(t *testing.T) {
   430  			out := &metav1.DeleteOptions{}
   431  			if tc.deleteGracePeriod != nil {
   432  				out.GracePeriodSeconds = utilpointer.Int64(*tc.deleteGracePeriod)
   433  			}
   434  			Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.pod, out)
   435  			if out.GracePeriodSeconds == nil {
   436  				t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod)
   437  			}
   438  			if *(out.GracePeriodSeconds) != tc.gracePeriod {
   439  				t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod)
   440  			}
   441  		})
   442  	}
   443  }
   444  
   445  type mockPodGetter struct {
   446  	pod *api.Pod
   447  }
   448  
   449  func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) {
   450  	return g.pod, nil
   451  }
   452  
   453  func TestCheckLogLocation(t *testing.T) {
   454  	ctx := genericapirequest.NewDefaultContext()
   455  	fakePodName := "test"
   456  	tcs := []struct {
   457  		name              string
   458  		in                *api.Pod
   459  		opts              *api.PodLogOptions
   460  		expectedErr       error
   461  		expectedTransport http.RoundTripper
   462  	}{
   463  		{
   464  			name: "simple",
   465  			in: &api.Pod{
   466  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   467  				Spec: api.PodSpec{
   468  					Containers: []api.Container{
   469  						{Name: "mycontainer"},
   470  					},
   471  					NodeName: "foo",
   472  				},
   473  				Status: api.PodStatus{},
   474  			},
   475  			opts:              &api.PodLogOptions{},
   476  			expectedErr:       nil,
   477  			expectedTransport: fakeSecureRoundTripper,
   478  		},
   479  		{
   480  			name: "insecure",
   481  			in: &api.Pod{
   482  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   483  				Spec: api.PodSpec{
   484  					Containers: []api.Container{
   485  						{Name: "mycontainer"},
   486  					},
   487  					NodeName: "foo",
   488  				},
   489  				Status: api.PodStatus{},
   490  			},
   491  			opts: &api.PodLogOptions{
   492  				InsecureSkipTLSVerifyBackend: true,
   493  			},
   494  			expectedErr:       nil,
   495  			expectedTransport: fakeInsecureRoundTripper,
   496  		},
   497  		{
   498  			name: "missing container",
   499  			in: &api.Pod{
   500  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   501  				Spec:       api.PodSpec{},
   502  				Status:     api.PodStatus{},
   503  			},
   504  			opts:              &api.PodLogOptions{},
   505  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test"),
   506  			expectedTransport: nil,
   507  		},
   508  		{
   509  			name: "choice of two containers",
   510  			in: &api.Pod{
   511  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   512  				Spec: api.PodSpec{
   513  					Containers: []api.Container{
   514  						{Name: "container1"},
   515  						{Name: "container2"},
   516  					},
   517  				},
   518  				Status: api.PodStatus{},
   519  			},
   520  			opts:              &api.PodLogOptions{},
   521  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
   522  			expectedTransport: nil,
   523  		},
   524  		{
   525  			name: "initcontainers",
   526  			in: &api.Pod{
   527  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   528  				Spec: api.PodSpec{
   529  					Containers: []api.Container{
   530  						{Name: "container1"},
   531  						{Name: "container2"},
   532  					},
   533  					InitContainers: []api.Container{
   534  						{Name: "initcontainer1"},
   535  					},
   536  				},
   537  				Status: api.PodStatus{},
   538  			},
   539  			opts:              &api.PodLogOptions{},
   540  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2]"),
   541  			expectedTransport: nil,
   542  		},
   543  		{
   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  					EphemeralContainers: []api.EphemeralContainer{
   555  						{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debugger"}},
   556  					},
   557  				},
   558  				Status: api.PodStatus{},
   559  			},
   560  			opts:              &api.PodLogOptions{},
   561  			expectedErr:       errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2 debugger]"),
   562  			expectedTransport: nil,
   563  		},
   564  		{
   565  			name: "bad container",
   566  			in: &api.Pod{
   567  				ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
   568  				Spec: api.PodSpec{
   569  					Containers: []api.Container{
   570  						{Name: "container1"},
   571  						{Name: "container2"},
   572  					},
   573  				},
   574  				Status: api.PodStatus{},
   575  			},
   576  			opts: &api.PodLogOptions{
   577  				Container: "unknown",
   578  			},
   579  			expectedErr:       errors.NewBadRequest("container unknown is not valid for pod test"),
   580  			expectedTransport: nil,
   581  		},
   582  		{
   583  			name: "good with two containers",
   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  					NodeName: "foo",
   592  				},
   593  				Status: api.PodStatus{},
   594  			},
   595  			opts: &api.PodLogOptions{
   596  				Container: "container2",
   597  			},
   598  			expectedErr:       nil,
   599  			expectedTransport: fakeSecureRoundTripper,
   600  		},
   601  	}
   602  	for _, tc := range tcs {
   603  		t.Run(tc.name, func(t *testing.T) {
   604  			getter := &mockPodGetter{tc.in}
   605  			connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{
   606  				Transport:                      fakeSecureRoundTripper,
   607  				InsecureSkipTLSVerifyTransport: fakeInsecureRoundTripper,
   608  			}}
   609  
   610  			_, actualTransport, err := LogLocation(ctx, getter, connectionGetter, fakePodName, tc.opts)
   611  			if !reflect.DeepEqual(err, tc.expectedErr) {
   612  				t.Errorf("expected %q, got %q", tc.expectedErr, err)
   613  			}
   614  			if actualTransport != tc.expectedTransport {
   615  				t.Errorf("expected %q, got %q", tc.expectedTransport, actualTransport)
   616  			}
   617  		})
   618  	}
   619  }
   620  
   621  func TestSelectableFieldLabelConversions(t *testing.T) {
   622  	apitesting.TestSelectableFieldLabelConversionsOfKind(t,
   623  		"v1",
   624  		"Pod",
   625  		ToSelectableFields(&api.Pod{}),
   626  		nil,
   627  	)
   628  }
   629  
   630  type mockConnectionInfoGetter struct {
   631  	info *client.ConnectionInfo
   632  }
   633  
   634  func (g mockConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*client.ConnectionInfo, error) {
   635  	return g.info, nil
   636  }
   637  
   638  func TestPortForwardLocation(t *testing.T) {
   639  	ctx := genericapirequest.NewDefaultContext()
   640  	tcs := []struct {
   641  		in          *api.Pod
   642  		info        *client.ConnectionInfo
   643  		opts        *api.PodPortForwardOptions
   644  		expectedErr error
   645  		expectedURL *url.URL
   646  	}{
   647  		{
   648  			in: &api.Pod{
   649  				Spec: api.PodSpec{},
   650  			},
   651  			opts:        &api.PodPortForwardOptions{},
   652  			expectedErr: errors.NewBadRequest("pod test does not have a host assigned"),
   653  		},
   654  		{
   655  			in: &api.Pod{
   656  				ObjectMeta: metav1.ObjectMeta{
   657  					Namespace: "ns",
   658  					Name:      "pod1",
   659  				},
   660  				Spec: api.PodSpec{
   661  					NodeName: "node1",
   662  				},
   663  			},
   664  			info:        &client.ConnectionInfo{},
   665  			opts:        &api.PodPortForwardOptions{},
   666  			expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"},
   667  		},
   668  		{
   669  			in: &api.Pod{
   670  				ObjectMeta: metav1.ObjectMeta{
   671  					Namespace: "ns",
   672  					Name:      "pod1",
   673  				},
   674  				Spec: api.PodSpec{
   675  					NodeName: "node1",
   676  				},
   677  			},
   678  			info:        &client.ConnectionInfo{},
   679  			opts:        &api.PodPortForwardOptions{Ports: []int32{80}},
   680  			expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"},
   681  		},
   682  	}
   683  	for _, tc := range tcs {
   684  		getter := &mockPodGetter{tc.in}
   685  		connectionGetter := &mockConnectionInfoGetter{tc.info}
   686  		loc, _, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts)
   687  		if !reflect.DeepEqual(err, tc.expectedErr) {
   688  			t.Errorf("expected %v, got %v", tc.expectedErr, err)
   689  		}
   690  		if !reflect.DeepEqual(loc, tc.expectedURL) {
   691  			t.Errorf("expected %v, got %v", tc.expectedURL, loc)
   692  		}
   693  	}
   694  }
   695  
   696  func TestGetPodIP(t *testing.T) {
   697  	testCases := []struct {
   698  		name       string
   699  		pod        *api.Pod
   700  		expectedIP string
   701  	}{
   702  		{
   703  			name:       "nil pod",
   704  			pod:        nil,
   705  			expectedIP: "",
   706  		},
   707  		{
   708  			name: "no status object",
   709  			pod: &api.Pod{
   710  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   711  				Spec:       api.PodSpec{},
   712  			},
   713  			expectedIP: "",
   714  		},
   715  		{
   716  			name: "no pod ips",
   717  			pod: &api.Pod{
   718  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   719  				Spec:       api.PodSpec{},
   720  				Status:     api.PodStatus{},
   721  			},
   722  			expectedIP: "",
   723  		},
   724  		{
   725  			name: "empty list",
   726  			pod: &api.Pod{
   727  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   728  				Spec:       api.PodSpec{},
   729  				Status: api.PodStatus{
   730  					PodIPs: []api.PodIP{},
   731  				},
   732  			},
   733  			expectedIP: "",
   734  		},
   735  		{
   736  			name: "1 ip",
   737  			pod: &api.Pod{
   738  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   739  				Spec:       api.PodSpec{},
   740  				Status: api.PodStatus{
   741  					PodIPs: []api.PodIP{
   742  						{IP: "10.0.0.10"},
   743  					},
   744  				},
   745  			},
   746  			expectedIP: "10.0.0.10",
   747  		},
   748  		{
   749  			name: "multiple ips",
   750  			pod: &api.Pod{
   751  				ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
   752  				Spec:       api.PodSpec{},
   753  				Status: api.PodStatus{
   754  					PodIPs: []api.PodIP{
   755  						{IP: "10.0.0.10"},
   756  						{IP: "10.0.0.20"},
   757  					},
   758  				},
   759  			},
   760  			expectedIP: "10.0.0.10",
   761  		},
   762  	}
   763  	for _, tc := range testCases {
   764  		t.Run(tc.name, func(t *testing.T) {
   765  			podIP := getPodIP(tc.pod)
   766  			if podIP != tc.expectedIP {
   767  				t.Errorf("expected pod ip:%v does not match actual %v", tc.expectedIP, podIP)
   768  			}
   769  		})
   770  	}
   771  }
   772  
   773  type fakeTransport struct {
   774  	val string
   775  }
   776  
   777  func (f fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
   778  	return nil, nil
   779  }
   780  
   781  var (
   782  	fakeSecureRoundTripper   = fakeTransport{val: "secure"}
   783  	fakeInsecureRoundTripper = fakeTransport{val: "insecure"}
   784  )
   785  
   786  func TestPodIndexFunc(t *testing.T) {
   787  	tcs := []struct {
   788  		name          string
   789  		indexFunc     cache.IndexFunc
   790  		pod           interface{}
   791  		expectedValue string
   792  		expectedErr   error
   793  	}{
   794  		{
   795  			name:      "node name index",
   796  			indexFunc: NodeNameIndexFunc,
   797  			pod: &api.Pod{
   798  				Spec: api.PodSpec{
   799  					NodeName: "test-pod",
   800  				},
   801  			},
   802  			expectedValue: "test-pod",
   803  			expectedErr:   nil,
   804  		},
   805  		{
   806  			name:          "not a pod failed",
   807  			indexFunc:     NodeNameIndexFunc,
   808  			pod:           "not a pod object",
   809  			expectedValue: "test-pod",
   810  			expectedErr:   fmt.Errorf("not a pod"),
   811  		},
   812  	}
   813  
   814  	for _, tc := range tcs {
   815  		indexValues, err := tc.indexFunc(tc.pod)
   816  		if !reflect.DeepEqual(err, tc.expectedErr) {
   817  			t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedErr, err)
   818  		}
   819  		if err == nil && len(indexValues) != 1 && indexValues[0] != tc.expectedValue {
   820  			t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedValue, indexValues)
   821  		}
   822  
   823  	}
   824  }
   825  
   826  func newPodWithHugePageValue(resourceName api.ResourceName, value resource.Quantity) *api.Pod {
   827  	return &api.Pod{
   828  		ObjectMeta: metav1.ObjectMeta{
   829  			Namespace:       "default",
   830  			Name:            "foo",
   831  			ResourceVersion: "1",
   832  		},
   833  		Spec: api.PodSpec{
   834  			RestartPolicy: api.RestartPolicyAlways,
   835  			DNSPolicy:     api.DNSDefault,
   836  			Containers: []api.Container{{
   837  				Name:                     "foo",
   838  				Image:                    "image",
   839  				ImagePullPolicy:          "IfNotPresent",
   840  				TerminationMessagePolicy: "File",
   841  				Resources: api.ResourceRequirements{
   842  					Requests: api.ResourceList{
   843  						api.ResourceCPU: resource.MustParse("10"),
   844  						resourceName:    value,
   845  					},
   846  					Limits: api.ResourceList{
   847  						api.ResourceCPU: resource.MustParse("10"),
   848  						resourceName:    value,
   849  					},
   850  				}},
   851  			},
   852  		},
   853  	}
   854  }
   855  
   856  func TestPodStrategyValidate(t *testing.T) {
   857  	const containerName = "container"
   858  
   859  	tests := []struct {
   860  		name    string
   861  		pod     *api.Pod
   862  		wantErr bool
   863  	}{
   864  		{
   865  			name:    "a new pod setting container with indivisible hugepages values",
   866  			pod:     newPodWithHugePageValue(api.ResourceHugePagesPrefix+"1Mi", resource.MustParse("1.1Mi")),
   867  			wantErr: true,
   868  		},
   869  		{
   870  			name: "a new pod setting init-container with indivisible hugepages values",
   871  			pod: &api.Pod{
   872  				ObjectMeta: metav1.ObjectMeta{
   873  					Namespace: "default",
   874  					Name:      "foo",
   875  				},
   876  				Spec: api.PodSpec{
   877  					RestartPolicy: api.RestartPolicyAlways,
   878  					DNSPolicy:     api.DNSDefault,
   879  					InitContainers: []api.Container{{
   880  						Name:                     containerName,
   881  						Image:                    "image",
   882  						ImagePullPolicy:          "IfNotPresent",
   883  						TerminationMessagePolicy: "File",
   884  						Resources: api.ResourceRequirements{
   885  							Requests: api.ResourceList{
   886  								api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"),
   887  							},
   888  							Limits: api.ResourceList{
   889  								api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"),
   890  							},
   891  						}},
   892  					},
   893  				},
   894  			},
   895  			wantErr: true,
   896  		},
   897  		{
   898  			name: "a new pod setting init-container with indivisible hugepages values while container with divisible hugepages values",
   899  			pod: &api.Pod{
   900  				ObjectMeta: metav1.ObjectMeta{
   901  					Namespace: "default",
   902  					Name:      "foo",
   903  				},
   904  				Spec: api.PodSpec{
   905  					RestartPolicy: api.RestartPolicyAlways,
   906  					DNSPolicy:     api.DNSDefault,
   907  					InitContainers: []api.Container{{
   908  						Name:                     containerName,
   909  						Image:                    "image",
   910  						ImagePullPolicy:          "IfNotPresent",
   911  						TerminationMessagePolicy: "File",
   912  						Resources: api.ResourceRequirements{
   913  							Requests: api.ResourceList{
   914  								api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"),
   915  							},
   916  							Limits: api.ResourceList{
   917  								api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"),
   918  							},
   919  						}},
   920  					},
   921  					Containers: []api.Container{{
   922  						Name:                     containerName,
   923  						Image:                    "image",
   924  						ImagePullPolicy:          "IfNotPresent",
   925  						TerminationMessagePolicy: "File",
   926  						Resources: api.ResourceRequirements{
   927  							Requests: api.ResourceList{
   928  								api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"),
   929  							},
   930  							Limits: api.ResourceList{
   931  								api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"),
   932  							},
   933  						}},
   934  					},
   935  				},
   936  			},
   937  			wantErr: true,
   938  		},
   939  		{
   940  			name: "a new pod setting container with divisible hugepages values",
   941  			pod: &api.Pod{
   942  				ObjectMeta: metav1.ObjectMeta{
   943  					Namespace: "default",
   944  					Name:      "foo",
   945  				},
   946  				Spec: api.PodSpec{
   947  					RestartPolicy: api.RestartPolicyAlways,
   948  					DNSPolicy:     api.DNSDefault,
   949  					Containers: []api.Container{{
   950  						Name:                     containerName,
   951  						Image:                    "image",
   952  						ImagePullPolicy:          "IfNotPresent",
   953  						TerminationMessagePolicy: "File",
   954  						Resources: api.ResourceRequirements{
   955  							Requests: api.ResourceList{
   956  								api.ResourceName(api.ResourceCPU):                     resource.MustParse("10"),
   957  								api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"),
   958  							},
   959  							Limits: api.ResourceList{
   960  								api.ResourceName(api.ResourceCPU):                     resource.MustParse("10"),
   961  								api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"),
   962  							},
   963  						}},
   964  					},
   965  				},
   966  			},
   967  		},
   968  	}
   969  
   970  	for _, tc := range tests {
   971  		t.Run(tc.name, func(t *testing.T) {
   972  			errs := Strategy.Validate(genericapirequest.NewContext(), tc.pod)
   973  			if tc.wantErr && len(errs) == 0 {
   974  				t.Errorf("expected errors but got none")
   975  			}
   976  			if !tc.wantErr && len(errs) != 0 {
   977  				t.Errorf("unexpected errors: %v", errs.ToAggregate())
   978  			}
   979  		})
   980  	}
   981  }
   982  
   983  func TestEphemeralContainerStrategyValidateUpdate(t *testing.T) {
   984  
   985  	test := []struct {
   986  		name   string
   987  		newPod *api.Pod
   988  		oldPod *api.Pod
   989  	}{
   990  		{
   991  			name: "add ephemeral container to regular pod and expect success",
   992  			oldPod: &api.Pod{
   993  				ObjectMeta: metav1.ObjectMeta{
   994  					Name:            "test-pod",
   995  					Namespace:       "test-ns",
   996  					ResourceVersion: "1",
   997  				},
   998  				Spec: api.PodSpec{
   999  					RestartPolicy: api.RestartPolicyAlways,
  1000  					DNSPolicy:     api.DNSDefault,
  1001  					Containers: []api.Container{
  1002  						{
  1003  							Name:                     "container",
  1004  							Image:                    "image",
  1005  							ImagePullPolicy:          "IfNotPresent",
  1006  							TerminationMessagePolicy: "File",
  1007  						},
  1008  					},
  1009  				},
  1010  			},
  1011  			newPod: &api.Pod{
  1012  				ObjectMeta: metav1.ObjectMeta{
  1013  					Name:            "test-pod",
  1014  					Namespace:       "test-ns",
  1015  					ResourceVersion: "1",
  1016  				},
  1017  				Spec: api.PodSpec{
  1018  					RestartPolicy: api.RestartPolicyAlways,
  1019  					DNSPolicy:     api.DNSDefault,
  1020  					Containers: []api.Container{
  1021  						{
  1022  							Name:                     "container",
  1023  							Image:                    "image",
  1024  							ImagePullPolicy:          "IfNotPresent",
  1025  							TerminationMessagePolicy: "File",
  1026  						},
  1027  					},
  1028  					EphemeralContainers: []api.EphemeralContainer{
  1029  						{
  1030  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1031  								Name:                     "debugger",
  1032  								Image:                    "image",
  1033  								ImagePullPolicy:          "IfNotPresent",
  1034  								TerminationMessagePolicy: "File",
  1035  							},
  1036  						},
  1037  					},
  1038  				},
  1039  			},
  1040  		},
  1041  	}
  1042  
  1043  	// expect no errors
  1044  	for _, tc := range test {
  1045  		t.Run(tc.name, func(t *testing.T) {
  1046  			if errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 {
  1047  				t.Errorf("unexpected error:%v", errs)
  1048  			}
  1049  		})
  1050  	}
  1051  
  1052  	test = []struct {
  1053  		name   string
  1054  		newPod *api.Pod
  1055  		oldPod *api.Pod
  1056  	}{
  1057  		{
  1058  			name: "add ephemeral container to static pod and expect failure",
  1059  			oldPod: &api.Pod{
  1060  				ObjectMeta: metav1.ObjectMeta{
  1061  					Name:            "test-pod",
  1062  					Namespace:       "test-ns",
  1063  					ResourceVersion: "1",
  1064  					Annotations:     map[string]string{api.MirrorPodAnnotationKey: "someVal"},
  1065  				},
  1066  				Spec: api.PodSpec{
  1067  					RestartPolicy: api.RestartPolicyAlways,
  1068  					DNSPolicy:     api.DNSDefault,
  1069  					Containers: []api.Container{
  1070  						{
  1071  							Name:                     "container",
  1072  							Image:                    "image",
  1073  							ImagePullPolicy:          "IfNotPresent",
  1074  							TerminationMessagePolicy: "File",
  1075  						},
  1076  					},
  1077  					NodeName: "example.com",
  1078  				},
  1079  			},
  1080  			newPod: &api.Pod{
  1081  				ObjectMeta: metav1.ObjectMeta{
  1082  					Name:            "test-pod",
  1083  					Namespace:       "test-ns",
  1084  					ResourceVersion: "1",
  1085  					Annotations:     map[string]string{api.MirrorPodAnnotationKey: "someVal"},
  1086  				},
  1087  				Spec: api.PodSpec{
  1088  					RestartPolicy: api.RestartPolicyAlways,
  1089  					DNSPolicy:     api.DNSDefault,
  1090  					Containers: []api.Container{
  1091  						{
  1092  							Name:                     "container",
  1093  							Image:                    "image",
  1094  							ImagePullPolicy:          "IfNotPresent",
  1095  							TerminationMessagePolicy: "File",
  1096  						},
  1097  					},
  1098  					EphemeralContainers: []api.EphemeralContainer{
  1099  						{
  1100  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1101  								Name:                     "debugger",
  1102  								Image:                    "image",
  1103  								ImagePullPolicy:          "IfNotPresent",
  1104  								TerminationMessagePolicy: "File",
  1105  							},
  1106  						},
  1107  					},
  1108  					NodeName: "example.com",
  1109  				},
  1110  			},
  1111  		},
  1112  		{
  1113  			name: "remove ephemeral container from regular pod and expect failure",
  1114  			newPod: &api.Pod{
  1115  				ObjectMeta: metav1.ObjectMeta{
  1116  					Name:            "test-pod",
  1117  					Namespace:       "test-ns",
  1118  					ResourceVersion: "1",
  1119  				},
  1120  				Spec: api.PodSpec{
  1121  					RestartPolicy: api.RestartPolicyAlways,
  1122  					DNSPolicy:     api.DNSDefault,
  1123  					Containers: []api.Container{
  1124  						{
  1125  							Name:                     "container",
  1126  							Image:                    "image",
  1127  							ImagePullPolicy:          "IfNotPresent",
  1128  							TerminationMessagePolicy: "File",
  1129  						},
  1130  					},
  1131  				},
  1132  			},
  1133  			oldPod: &api.Pod{
  1134  				ObjectMeta: metav1.ObjectMeta{
  1135  					Name:            "test-pod",
  1136  					Namespace:       "test-ns",
  1137  					ResourceVersion: "1",
  1138  				},
  1139  				Spec: api.PodSpec{
  1140  					RestartPolicy: api.RestartPolicyAlways,
  1141  					DNSPolicy:     api.DNSDefault,
  1142  					Containers: []api.Container{
  1143  						{
  1144  							Name:                     "container",
  1145  							Image:                    "image",
  1146  							ImagePullPolicy:          "IfNotPresent",
  1147  							TerminationMessagePolicy: "File",
  1148  						},
  1149  					},
  1150  					EphemeralContainers: []api.EphemeralContainer{
  1151  						{
  1152  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1153  								Name:                     "debugger",
  1154  								Image:                    "image",
  1155  								ImagePullPolicy:          "IfNotPresent",
  1156  								TerminationMessagePolicy: "File",
  1157  							},
  1158  						},
  1159  					},
  1160  				},
  1161  			},
  1162  		},
  1163  		{
  1164  			name: "change ephemeral container from regular pod and expect failure",
  1165  			newPod: &api.Pod{
  1166  				ObjectMeta: metav1.ObjectMeta{
  1167  					Name:            "test-pod",
  1168  					Namespace:       "test-ns",
  1169  					ResourceVersion: "1",
  1170  				},
  1171  				Spec: api.PodSpec{
  1172  					RestartPolicy: api.RestartPolicyAlways,
  1173  					DNSPolicy:     api.DNSDefault,
  1174  					Containers: []api.Container{
  1175  						{
  1176  							Name:                     "container",
  1177  							Image:                    "image",
  1178  							ImagePullPolicy:          "IfNotPresent",
  1179  							TerminationMessagePolicy: "File",
  1180  						},
  1181  					},
  1182  					EphemeralContainers: []api.EphemeralContainer{
  1183  						{
  1184  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1185  								Name:                     "debugger",
  1186  								Image:                    "image2",
  1187  								ImagePullPolicy:          "IfNotPresent",
  1188  								TerminationMessagePolicy: "File",
  1189  							},
  1190  						},
  1191  					},
  1192  				},
  1193  			},
  1194  			oldPod: &api.Pod{
  1195  				ObjectMeta: metav1.ObjectMeta{
  1196  					Name:            "test-pod",
  1197  					Namespace:       "test-ns",
  1198  					ResourceVersion: "1",
  1199  				},
  1200  				Spec: api.PodSpec{
  1201  					RestartPolicy: api.RestartPolicyAlways,
  1202  					DNSPolicy:     api.DNSDefault,
  1203  					Containers: []api.Container{
  1204  						{
  1205  							Name:                     "container",
  1206  							Image:                    "image",
  1207  							ImagePullPolicy:          "IfNotPresent",
  1208  							TerminationMessagePolicy: "File",
  1209  						},
  1210  					},
  1211  					EphemeralContainers: []api.EphemeralContainer{
  1212  						{
  1213  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1214  								Name:                     "debugger",
  1215  								Image:                    "image",
  1216  								ImagePullPolicy:          "IfNotPresent",
  1217  								TerminationMessagePolicy: "File",
  1218  							},
  1219  						},
  1220  					},
  1221  				},
  1222  			},
  1223  		},
  1224  	}
  1225  
  1226  	// expect one error
  1227  	for _, tc := range test {
  1228  		t.Run(tc.name, func(t *testing.T) {
  1229  			errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod)
  1230  			if len(errs) == 0 {
  1231  				t.Errorf("unexpected success:ephemeral containers are not supported for static pods")
  1232  			} else if len(errs) != 1 {
  1233  				t.Errorf("unexpected errors:expected one error about ephemeral containers are not supported for static pods:got:%v:", errs)
  1234  			}
  1235  		})
  1236  	}
  1237  }
  1238  
  1239  func TestPodStrategyValidateUpdate(t *testing.T) {
  1240  	test := []struct {
  1241  		name   string
  1242  		newPod *api.Pod
  1243  		oldPod *api.Pod
  1244  	}{
  1245  		{
  1246  			name:   "an existing pod with indivisible hugepages values to a new pod with indivisible hugepages values",
  1247  			newPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")),
  1248  			oldPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")),
  1249  		},
  1250  	}
  1251  
  1252  	for _, tc := range test {
  1253  		t.Run(tc.name, func(t *testing.T) {
  1254  			if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 {
  1255  				t.Errorf("unexpected error:%v", errs)
  1256  			}
  1257  		})
  1258  	}
  1259  }
  1260  
  1261  func TestDropNonEphemeralContainerUpdates(t *testing.T) {
  1262  	tests := []struct {
  1263  		name                    string
  1264  		oldPod, newPod, wantPod *api.Pod
  1265  	}{
  1266  		{
  1267  			name: "simple ephemeral container append",
  1268  			oldPod: &api.Pod{
  1269  				ObjectMeta: metav1.ObjectMeta{
  1270  					Name:            "test-pod",
  1271  					Namespace:       "test-ns",
  1272  					ResourceVersion: "1",
  1273  				},
  1274  				Spec: api.PodSpec{
  1275  					Containers: []api.Container{
  1276  						{
  1277  							Name:  "container",
  1278  							Image: "image",
  1279  						},
  1280  					},
  1281  				},
  1282  			},
  1283  			newPod: &api.Pod{
  1284  				ObjectMeta: metav1.ObjectMeta{
  1285  					Name:            "test-pod",
  1286  					Namespace:       "test-ns",
  1287  					ResourceVersion: "1",
  1288  				},
  1289  				Spec: api.PodSpec{
  1290  					Containers: []api.Container{
  1291  						{
  1292  							Name:  "container",
  1293  							Image: "image",
  1294  						},
  1295  					},
  1296  					EphemeralContainers: []api.EphemeralContainer{
  1297  						{
  1298  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1299  								Name:  "container",
  1300  								Image: "image",
  1301  							},
  1302  						},
  1303  					},
  1304  				},
  1305  			},
  1306  			wantPod: &api.Pod{
  1307  				ObjectMeta: metav1.ObjectMeta{
  1308  					Name:            "test-pod",
  1309  					Namespace:       "test-ns",
  1310  					ResourceVersion: "1",
  1311  				},
  1312  				Spec: api.PodSpec{
  1313  					Containers: []api.Container{
  1314  						{
  1315  							Name:  "container",
  1316  							Image: "image",
  1317  						},
  1318  					},
  1319  					EphemeralContainers: []api.EphemeralContainer{
  1320  						{
  1321  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1322  								Name:  "container",
  1323  								Image: "image",
  1324  							},
  1325  						},
  1326  					},
  1327  				},
  1328  			},
  1329  		},
  1330  		{
  1331  			name: "whoops wrong pod",
  1332  			oldPod: &api.Pod{
  1333  				ObjectMeta: metav1.ObjectMeta{
  1334  					Name:            "test-pod",
  1335  					Namespace:       "test-ns",
  1336  					ResourceVersion: "1",
  1337  					UID:             "blue",
  1338  				},
  1339  			},
  1340  			newPod: &api.Pod{
  1341  				ObjectMeta: metav1.ObjectMeta{
  1342  					Name:            "new-pod",
  1343  					Namespace:       "new-ns",
  1344  					ResourceVersion: "1",
  1345  					UID:             "green",
  1346  				},
  1347  			},
  1348  			wantPod: &api.Pod{
  1349  				ObjectMeta: metav1.ObjectMeta{
  1350  					Name:            "new-pod",
  1351  					Namespace:       "new-ns",
  1352  					ResourceVersion: "1",
  1353  					UID:             "green",
  1354  				},
  1355  			},
  1356  		},
  1357  		{
  1358  			name: "resource conflict during update",
  1359  			oldPod: &api.Pod{
  1360  				ObjectMeta: metav1.ObjectMeta{
  1361  					Name:            "test-pod",
  1362  					Namespace:       "test-ns",
  1363  					ResourceVersion: "2",
  1364  					UID:             "blue",
  1365  				},
  1366  			},
  1367  			newPod: &api.Pod{
  1368  				ObjectMeta: metav1.ObjectMeta{
  1369  					Name:            "test-pod",
  1370  					Namespace:       "test-ns",
  1371  					ResourceVersion: "1",
  1372  					UID:             "blue",
  1373  				},
  1374  			},
  1375  			wantPod: &api.Pod{
  1376  				ObjectMeta: metav1.ObjectMeta{
  1377  					Name:            "test-pod",
  1378  					Namespace:       "test-ns",
  1379  					ResourceVersion: "1",
  1380  					UID:             "blue",
  1381  				},
  1382  			},
  1383  		},
  1384  		{
  1385  			name: "drop non-ephemeral container changes",
  1386  			oldPod: &api.Pod{
  1387  				ObjectMeta: metav1.ObjectMeta{
  1388  					Name:            "test-pod",
  1389  					Namespace:       "test-ns",
  1390  					ResourceVersion: "1",
  1391  					Annotations:     map[string]string{"foo": "bar"},
  1392  				},
  1393  				Spec: api.PodSpec{
  1394  					Containers: []api.Container{
  1395  						{
  1396  							Name:  "container",
  1397  							Image: "image",
  1398  						},
  1399  					},
  1400  				},
  1401  			},
  1402  			newPod: &api.Pod{
  1403  				ObjectMeta: metav1.ObjectMeta{
  1404  					Name:            "test-pod",
  1405  					Namespace:       "test-ns",
  1406  					ResourceVersion: "1",
  1407  					Annotations:     map[string]string{"foo": "bar", "whiz": "pop"},
  1408  					Finalizers:      []string{"milo"},
  1409  				},
  1410  				Spec: api.PodSpec{
  1411  					Containers: []api.Container{
  1412  						{
  1413  							Name:  "container",
  1414  							Image: "newimage",
  1415  						},
  1416  					},
  1417  					EphemeralContainers: []api.EphemeralContainer{
  1418  						{
  1419  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1420  								Name:  "container1",
  1421  								Image: "image",
  1422  							},
  1423  						},
  1424  						{
  1425  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1426  								Name:  "container2",
  1427  								Image: "image",
  1428  							},
  1429  						},
  1430  					},
  1431  				},
  1432  				Status: api.PodStatus{
  1433  					Message: "hi.",
  1434  				},
  1435  			},
  1436  			wantPod: &api.Pod{
  1437  				ObjectMeta: metav1.ObjectMeta{
  1438  					Name:            "test-pod",
  1439  					Namespace:       "test-ns",
  1440  					ResourceVersion: "1",
  1441  					Annotations:     map[string]string{"foo": "bar"},
  1442  				},
  1443  				Spec: api.PodSpec{
  1444  					Containers: []api.Container{
  1445  						{
  1446  							Name:  "container",
  1447  							Image: "image",
  1448  						},
  1449  					},
  1450  					EphemeralContainers: []api.EphemeralContainer{
  1451  						{
  1452  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1453  								Name:  "container1",
  1454  								Image: "image",
  1455  							},
  1456  						},
  1457  						{
  1458  							EphemeralContainerCommon: api.EphemeralContainerCommon{
  1459  								Name:  "container2",
  1460  								Image: "image",
  1461  							},
  1462  						},
  1463  					},
  1464  				},
  1465  			},
  1466  		},
  1467  	}
  1468  
  1469  	for _, tc := range tests {
  1470  		t.Run(tc.name, func(t *testing.T) {
  1471  			gotPod := dropNonEphemeralContainerUpdates(tc.newPod, tc.oldPod)
  1472  			if diff := cmp.Diff(tc.wantPod, gotPod); diff != "" {
  1473  				t.Errorf("unexpected diff when dropping fields (-want, +got):\n%s", diff)
  1474  			}
  1475  		})
  1476  	}
  1477  }
  1478  
  1479  func TestNodeInclusionPolicyEnablementInCreating(t *testing.T) {
  1480  	var (
  1481  		honor            = api.NodeInclusionPolicyHonor
  1482  		ignore           = api.NodeInclusionPolicyIgnore
  1483  		emptyConstraints = []api.TopologySpreadConstraint{
  1484  			{
  1485  				WhenUnsatisfiable: api.DoNotSchedule,
  1486  				TopologyKey:       "kubernetes.io/hostname",
  1487  				MaxSkew:           1,
  1488  			},
  1489  		}
  1490  		defaultConstraints = []api.TopologySpreadConstraint{
  1491  			{
  1492  				NodeAffinityPolicy: &honor,
  1493  				NodeTaintsPolicy:   &ignore,
  1494  				WhenUnsatisfiable:  api.DoNotSchedule,
  1495  				TopologyKey:        "kubernetes.io/hostname",
  1496  				MaxSkew:            1,
  1497  			},
  1498  		}
  1499  	)
  1500  
  1501  	tests := []struct {
  1502  		name                          string
  1503  		topologySpreadConstraints     []api.TopologySpreadConstraint
  1504  		wantTopologySpreadConstraints []api.TopologySpreadConstraint
  1505  		enableNodeInclusionPolicy     bool
  1506  	}{
  1507  		{
  1508  			name:                          "nodeInclusionPolicy enabled with topology unset",
  1509  			topologySpreadConstraints:     emptyConstraints,
  1510  			wantTopologySpreadConstraints: emptyConstraints,
  1511  			enableNodeInclusionPolicy:     true,
  1512  		},
  1513  		{
  1514  			name:                          "nodeInclusionPolicy enabled with topology configured",
  1515  			topologySpreadConstraints:     defaultConstraints,
  1516  			wantTopologySpreadConstraints: defaultConstraints,
  1517  			enableNodeInclusionPolicy:     true,
  1518  		},
  1519  		{
  1520  			name:                          "nodeInclusionPolicy disabled with topology configured",
  1521  			topologySpreadConstraints:     defaultConstraints,
  1522  			wantTopologySpreadConstraints: emptyConstraints,
  1523  		},
  1524  	}
  1525  
  1526  	for _, tc := range tests {
  1527  		t.Run(tc.name, func(t *testing.T) {
  1528  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tc.enableNodeInclusionPolicy)()
  1529  
  1530  			pod := &api.Pod{
  1531  				ObjectMeta: metav1.ObjectMeta{
  1532  					Namespace: "default",
  1533  					Name:      "foo",
  1534  				},
  1535  				Spec: api.PodSpec{
  1536  					RestartPolicy: api.RestartPolicyAlways,
  1537  					DNSPolicy:     api.DNSDefault,
  1538  					Containers: []api.Container{
  1539  						{
  1540  							Name:                     "container",
  1541  							Image:                    "image",
  1542  							ImagePullPolicy:          "IfNotPresent",
  1543  							TerminationMessagePolicy: "File",
  1544  						},
  1545  					},
  1546  				},
  1547  			}
  1548  			wantPod := pod.DeepCopy()
  1549  			pod.Spec.TopologySpreadConstraints = append(pod.Spec.TopologySpreadConstraints, tc.topologySpreadConstraints...)
  1550  
  1551  			errs := Strategy.Validate(genericapirequest.NewContext(), pod)
  1552  			if len(errs) != 0 {
  1553  				t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1554  			}
  1555  
  1556  			Strategy.PrepareForCreate(genericapirequest.NewContext(), pod)
  1557  			wantPod.Spec.TopologySpreadConstraints = append(wantPod.Spec.TopologySpreadConstraints, tc.wantTopologySpreadConstraints...)
  1558  			if diff := cmp.Diff(wantPod, pod, cmpopts.IgnoreFields(pod.Status, "Phase", "QOSClass")); diff != "" {
  1559  				t.Errorf("%s unexpected result (-want, +got): %s", tc.name, diff)
  1560  			}
  1561  		})
  1562  	}
  1563  }
  1564  
  1565  func TestNodeInclusionPolicyEnablementInUpdating(t *testing.T) {
  1566  	var (
  1567  		honor  = api.NodeInclusionPolicyHonor
  1568  		ignore = api.NodeInclusionPolicyIgnore
  1569  	)
  1570  
  1571  	// Enable the Feature Gate during the first rule creation
  1572  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true)()
  1573  	ctx := genericapirequest.NewDefaultContext()
  1574  
  1575  	pod := &api.Pod{
  1576  		ObjectMeta: metav1.ObjectMeta{
  1577  			Namespace:       "default",
  1578  			Name:            "foo",
  1579  			ResourceVersion: "1",
  1580  		},
  1581  		Spec: api.PodSpec{
  1582  			RestartPolicy: api.RestartPolicyAlways,
  1583  			DNSPolicy:     api.DNSDefault,
  1584  			Containers: []api.Container{
  1585  				{
  1586  					Name:                     "container",
  1587  					Image:                    "image",
  1588  					ImagePullPolicy:          "IfNotPresent",
  1589  					TerminationMessagePolicy: "File",
  1590  				},
  1591  			},
  1592  			TopologySpreadConstraints: []api.TopologySpreadConstraint{
  1593  				{
  1594  					NodeAffinityPolicy: &ignore,
  1595  					NodeTaintsPolicy:   &honor,
  1596  					WhenUnsatisfiable:  api.DoNotSchedule,
  1597  					TopologyKey:        "kubernetes.io/hostname",
  1598  					MaxSkew:            1,
  1599  				},
  1600  			},
  1601  		},
  1602  	}
  1603  
  1604  	errs := Strategy.Validate(ctx, pod)
  1605  	if len(errs) != 0 {
  1606  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1607  	}
  1608  
  1609  	createdPod := pod.DeepCopy()
  1610  	Strategy.PrepareForCreate(ctx, createdPod)
  1611  
  1612  	if len(createdPod.Spec.TopologySpreadConstraints) != 1 ||
  1613  		*createdPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1614  		*createdPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1615  		t.Error("NodeInclusionPolicy created with unexpected result")
  1616  	}
  1617  
  1618  	// Disable the Feature Gate and expect these fields still exist after updating.
  1619  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, false)()
  1620  
  1621  	updatedPod := createdPod.DeepCopy()
  1622  	updatedPod.Labels = map[string]string{"foo": "bar"}
  1623  	updatedPod.ResourceVersion = "2"
  1624  
  1625  	errs = Strategy.ValidateUpdate(ctx, updatedPod, createdPod)
  1626  	if len(errs) != 0 {
  1627  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1628  	}
  1629  
  1630  	Strategy.PrepareForUpdate(ctx, updatedPod, createdPod)
  1631  
  1632  	if len(updatedPod.Spec.TopologySpreadConstraints) != 1 ||
  1633  		*updatedPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1634  		*updatedPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1635  		t.Error("NodeInclusionPolicy updated with unexpected result")
  1636  	}
  1637  
  1638  	// Enable the Feature Gate again to check whether configured fields still exist after updating.
  1639  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true)()
  1640  
  1641  	updatedPod2 := updatedPod.DeepCopy()
  1642  	updatedPod2.Labels = map[string]string{"foo": "fuz"}
  1643  	updatedPod2.ResourceVersion = "3"
  1644  
  1645  	errs = Strategy.ValidateUpdate(ctx, updatedPod2, updatedPod)
  1646  	if len(errs) != 0 {
  1647  		t.Errorf("Unexpected error: %v", errs.ToAggregate())
  1648  	}
  1649  
  1650  	Strategy.PrepareForUpdate(ctx, updatedPod2, updatedPod)
  1651  	if len(updatedPod2.Spec.TopologySpreadConstraints) != 1 ||
  1652  		*updatedPod2.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore ||
  1653  		*updatedPod2.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor {
  1654  		t.Error("NodeInclusionPolicy updated with unexpected result")
  1655  	}
  1656  }
  1657  
  1658  func Test_mutatePodAffinity(t *testing.T) {
  1659  	tests := []struct {
  1660  		name               string
  1661  		pod                *api.Pod
  1662  		wantPod            *api.Pod
  1663  		featureGateEnabled bool
  1664  	}{
  1665  		{
  1666  			name:               "matchLabelKeys are merged into labelSelector with In and mismatchLabelKeys are merged with NotIn",
  1667  			featureGateEnabled: true,
  1668  			pod: &api.Pod{
  1669  				ObjectMeta: metav1.ObjectMeta{
  1670  					Labels: map[string]string{
  1671  						"country": "Japan",
  1672  						"city":    "Kyoto",
  1673  					},
  1674  				},
  1675  				Spec: api.PodSpec{
  1676  					Affinity: &api.Affinity{
  1677  						PodAffinity: &api.PodAffinity{
  1678  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1679  								{
  1680  									LabelSelector: &metav1.LabelSelector{
  1681  										MatchLabels: map[string]string{
  1682  											"region": "Asia",
  1683  										},
  1684  									},
  1685  									MatchLabelKeys:    []string{"country"},
  1686  									MismatchLabelKeys: []string{"city"},
  1687  								},
  1688  							},
  1689  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1690  								{
  1691  									PodAffinityTerm: api.PodAffinityTerm{
  1692  										LabelSelector: &metav1.LabelSelector{
  1693  											MatchLabels: map[string]string{
  1694  												"region": "Asia",
  1695  											},
  1696  										},
  1697  										MatchLabelKeys:    []string{"country"},
  1698  										MismatchLabelKeys: []string{"city"},
  1699  									},
  1700  								},
  1701  							},
  1702  						},
  1703  						PodAntiAffinity: &api.PodAntiAffinity{
  1704  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1705  								{
  1706  									LabelSelector: &metav1.LabelSelector{
  1707  										MatchLabels: map[string]string{
  1708  											"region": "Asia",
  1709  										},
  1710  									},
  1711  									MatchLabelKeys:    []string{"country"},
  1712  									MismatchLabelKeys: []string{"city"},
  1713  								},
  1714  							},
  1715  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1716  								{
  1717  									PodAffinityTerm: api.PodAffinityTerm{
  1718  										LabelSelector: &metav1.LabelSelector{
  1719  											MatchLabels: map[string]string{
  1720  												"region": "Asia",
  1721  											},
  1722  										},
  1723  										MatchLabelKeys:    []string{"country"},
  1724  										MismatchLabelKeys: []string{"city"},
  1725  									},
  1726  								},
  1727  							},
  1728  						},
  1729  					},
  1730  				},
  1731  			},
  1732  			wantPod: &api.Pod{
  1733  				ObjectMeta: metav1.ObjectMeta{
  1734  					Labels: map[string]string{
  1735  						"country": "Japan",
  1736  						"city":    "Kyoto",
  1737  					},
  1738  				},
  1739  				Spec: api.PodSpec{
  1740  					Affinity: &api.Affinity{
  1741  						PodAffinity: &api.PodAffinity{
  1742  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1743  								{
  1744  									LabelSelector: &metav1.LabelSelector{
  1745  										MatchLabels: map[string]string{
  1746  											"region": "Asia",
  1747  										},
  1748  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1749  											{
  1750  												Key:      "country",
  1751  												Operator: metav1.LabelSelectorOpIn,
  1752  												Values:   []string{"Japan"},
  1753  											},
  1754  											{
  1755  												Key:      "city",
  1756  												Operator: metav1.LabelSelectorOpNotIn,
  1757  												Values:   []string{"Kyoto"},
  1758  											},
  1759  										},
  1760  									},
  1761  									MatchLabelKeys:    []string{"country"},
  1762  									MismatchLabelKeys: []string{"city"},
  1763  								},
  1764  							},
  1765  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1766  								{
  1767  									PodAffinityTerm: api.PodAffinityTerm{
  1768  										LabelSelector: &metav1.LabelSelector{
  1769  											MatchLabels: map[string]string{
  1770  												"region": "Asia",
  1771  											},
  1772  											MatchExpressions: []metav1.LabelSelectorRequirement{
  1773  												{
  1774  													Key:      "country",
  1775  													Operator: metav1.LabelSelectorOpIn,
  1776  													Values:   []string{"Japan"},
  1777  												},
  1778  												{
  1779  													Key:      "city",
  1780  													Operator: metav1.LabelSelectorOpNotIn,
  1781  													Values:   []string{"Kyoto"},
  1782  												},
  1783  											},
  1784  										},
  1785  										MatchLabelKeys:    []string{"country"},
  1786  										MismatchLabelKeys: []string{"city"},
  1787  									},
  1788  								},
  1789  							},
  1790  						},
  1791  						PodAntiAffinity: &api.PodAntiAffinity{
  1792  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1793  								{
  1794  									LabelSelector: &metav1.LabelSelector{
  1795  										MatchLabels: map[string]string{
  1796  											"region": "Asia",
  1797  										},
  1798  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1799  											{
  1800  												Key:      "country",
  1801  												Operator: metav1.LabelSelectorOpIn,
  1802  												Values:   []string{"Japan"},
  1803  											},
  1804  											{
  1805  												Key:      "city",
  1806  												Operator: metav1.LabelSelectorOpNotIn,
  1807  												Values:   []string{"Kyoto"},
  1808  											},
  1809  										},
  1810  									},
  1811  									MatchLabelKeys:    []string{"country"},
  1812  									MismatchLabelKeys: []string{"city"},
  1813  								},
  1814  							},
  1815  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
  1816  								{
  1817  									PodAffinityTerm: api.PodAffinityTerm{
  1818  										LabelSelector: &metav1.LabelSelector{
  1819  											MatchLabels: map[string]string{
  1820  												"region": "Asia",
  1821  											},
  1822  											MatchExpressions: []metav1.LabelSelectorRequirement{
  1823  												{
  1824  													Key:      "country",
  1825  													Operator: metav1.LabelSelectorOpIn,
  1826  													Values:   []string{"Japan"},
  1827  												},
  1828  												{
  1829  													Key:      "city",
  1830  													Operator: metav1.LabelSelectorOpNotIn,
  1831  													Values:   []string{"Kyoto"},
  1832  												},
  1833  											},
  1834  										},
  1835  										MatchLabelKeys:    []string{"country"},
  1836  										MismatchLabelKeys: []string{"city"},
  1837  									},
  1838  								},
  1839  							},
  1840  						},
  1841  					},
  1842  				},
  1843  			},
  1844  		},
  1845  		{
  1846  			name:               "keys, which are not found in Pod labels, are ignored",
  1847  			featureGateEnabled: true,
  1848  			pod: &api.Pod{
  1849  				ObjectMeta: metav1.ObjectMeta{
  1850  					Labels: map[string]string{
  1851  						"country": "Japan",
  1852  						"city":    "Kyoto",
  1853  					},
  1854  				},
  1855  				Spec: api.PodSpec{
  1856  					Affinity: &api.Affinity{
  1857  						PodAffinity: &api.PodAffinity{
  1858  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1859  								{
  1860  									LabelSelector: &metav1.LabelSelector{
  1861  										MatchLabels: map[string]string{
  1862  											"region": "Asia",
  1863  										},
  1864  									},
  1865  									MatchLabelKeys: []string{"city", "not-found"},
  1866  								},
  1867  							},
  1868  						},
  1869  					},
  1870  				},
  1871  			},
  1872  			wantPod: &api.Pod{
  1873  				ObjectMeta: metav1.ObjectMeta{
  1874  					Labels: map[string]string{
  1875  						"country": "Japan",
  1876  						"city":    "Kyoto",
  1877  					},
  1878  				},
  1879  				Spec: api.PodSpec{
  1880  					Affinity: &api.Affinity{
  1881  						PodAffinity: &api.PodAffinity{
  1882  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1883  								{
  1884  									LabelSelector: &metav1.LabelSelector{
  1885  										MatchLabels: map[string]string{
  1886  											"region": "Asia",
  1887  										},
  1888  										MatchExpressions: []metav1.LabelSelectorRequirement{
  1889  											// "city" should be added correctly even if matchLabelKey has "not-found" key.
  1890  											{
  1891  												Key:      "city",
  1892  												Operator: metav1.LabelSelectorOpIn,
  1893  												Values:   []string{"Kyoto"},
  1894  											},
  1895  										},
  1896  									},
  1897  									MatchLabelKeys: []string{"city", "not-found"},
  1898  								},
  1899  							},
  1900  						},
  1901  					},
  1902  				},
  1903  			},
  1904  		},
  1905  		{
  1906  			name:               "matchLabelKeys is ignored if the labelSelector is nil",
  1907  			featureGateEnabled: true,
  1908  			pod: &api.Pod{
  1909  				ObjectMeta: metav1.ObjectMeta{
  1910  					Labels: map[string]string{
  1911  						"country": "Japan",
  1912  						"city":    "Kyoto",
  1913  					},
  1914  				},
  1915  				Spec: api.PodSpec{
  1916  					Affinity: &api.Affinity{
  1917  						PodAffinity: &api.PodAffinity{
  1918  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1919  								{
  1920  									MatchLabelKeys:    []string{"country"},
  1921  									MismatchLabelKeys: []string{"city"},
  1922  								},
  1923  							},
  1924  						},
  1925  					},
  1926  				},
  1927  			},
  1928  			wantPod: &api.Pod{
  1929  				ObjectMeta: metav1.ObjectMeta{
  1930  					Labels: map[string]string{
  1931  						"country": "Japan",
  1932  						"city":    "Kyoto",
  1933  					},
  1934  				},
  1935  				Spec: api.PodSpec{
  1936  					Affinity: &api.Affinity{
  1937  						PodAffinity: &api.PodAffinity{
  1938  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1939  								{
  1940  									MatchLabelKeys:    []string{"country"},
  1941  									MismatchLabelKeys: []string{"city"},
  1942  								},
  1943  							},
  1944  						},
  1945  					},
  1946  				},
  1947  			},
  1948  		},
  1949  		{
  1950  			name: "the feature gate is disabled and matchLabelKeys is ignored",
  1951  			pod: &api.Pod{
  1952  				ObjectMeta: metav1.ObjectMeta{
  1953  					Labels: map[string]string{
  1954  						"country": "Japan",
  1955  						"city":    "Kyoto",
  1956  					},
  1957  				},
  1958  				Spec: api.PodSpec{
  1959  					Affinity: &api.Affinity{
  1960  						PodAffinity: &api.PodAffinity{
  1961  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1962  								{
  1963  									LabelSelector: &metav1.LabelSelector{
  1964  										MatchLabels: map[string]string{
  1965  											"region": "Asia",
  1966  										},
  1967  									},
  1968  									MatchLabelKeys:    []string{"country"},
  1969  									MismatchLabelKeys: []string{"city"},
  1970  								},
  1971  							},
  1972  						},
  1973  					},
  1974  				},
  1975  			},
  1976  			wantPod: &api.Pod{
  1977  				ObjectMeta: metav1.ObjectMeta{
  1978  					Labels: map[string]string{
  1979  						"country": "Japan",
  1980  						"city":    "Kyoto",
  1981  					},
  1982  				},
  1983  				Spec: api.PodSpec{
  1984  					Affinity: &api.Affinity{
  1985  						PodAffinity: &api.PodAffinity{
  1986  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
  1987  								{
  1988  									LabelSelector: &metav1.LabelSelector{
  1989  										MatchLabels: map[string]string{
  1990  											"region": "Asia",
  1991  										},
  1992  									},
  1993  									MatchLabelKeys:    []string{"country"},
  1994  									MismatchLabelKeys: []string{"city"},
  1995  								},
  1996  							},
  1997  						},
  1998  					},
  1999  				},
  2000  			},
  2001  		},
  2002  	}
  2003  
  2004  	for _, tc := range tests {
  2005  		t.Run(tc.name, func(t *testing.T) {
  2006  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, tc.featureGateEnabled)()
  2007  
  2008  			pod := tc.pod
  2009  			mutatePodAffinity(pod)
  2010  			if diff := cmp.Diff(tc.wantPod.Spec.Affinity, pod.Spec.Affinity); diff != "" {
  2011  				t.Errorf("unexpected affinity (-want, +got): %s\n", diff)
  2012  			}
  2013  		})
  2014  	}
  2015  }
  2016  
  2017  func TestPodLifecycleSleepActionEnablement(t *testing.T) {
  2018  	getLifecycle := func(pod *api.Pod) *api.Lifecycle {
  2019  		return pod.Spec.Containers[0].Lifecycle
  2020  	}
  2021  
  2022  	defaultTerminationGracePeriodSeconds := int64(30)
  2023  
  2024  	podWithHandler := func() *api.Pod {
  2025  		return &api.Pod{
  2026  			ObjectMeta: metav1.ObjectMeta{
  2027  				Namespace:       "default",
  2028  				Name:            "foo",
  2029  				ResourceVersion: "1",
  2030  			},
  2031  			Spec: api.PodSpec{
  2032  				RestartPolicy: api.RestartPolicyAlways,
  2033  				DNSPolicy:     api.DNSDefault,
  2034  				Containers: []api.Container{
  2035  					{
  2036  						Name:                     "container",
  2037  						Image:                    "image",
  2038  						ImagePullPolicy:          "IfNotPresent",
  2039  						TerminationMessagePolicy: "File",
  2040  						Lifecycle: &api.Lifecycle{
  2041  							PreStop: &api.LifecycleHandler{
  2042  								Sleep: &api.SleepAction{Seconds: 1},
  2043  							},
  2044  						},
  2045  					},
  2046  				},
  2047  				TerminationGracePeriodSeconds: &defaultTerminationGracePeriodSeconds,
  2048  			},
  2049  		}
  2050  	}
  2051  
  2052  	podWithoutHandler := func() *api.Pod {
  2053  		return &api.Pod{
  2054  			ObjectMeta: metav1.ObjectMeta{
  2055  				Namespace:       "default",
  2056  				Name:            "foo",
  2057  				ResourceVersion: "1",
  2058  			},
  2059  			Spec: api.PodSpec{
  2060  				RestartPolicy: api.RestartPolicyAlways,
  2061  				DNSPolicy:     api.DNSDefault,
  2062  				Containers: []api.Container{
  2063  					{
  2064  						Name:                     "container",
  2065  						Image:                    "image",
  2066  						ImagePullPolicy:          "IfNotPresent",
  2067  						TerminationMessagePolicy: "File",
  2068  					},
  2069  				},
  2070  				TerminationGracePeriodSeconds: &defaultTerminationGracePeriodSeconds,
  2071  			},
  2072  		}
  2073  	}
  2074  
  2075  	testCases := []struct {
  2076  		description string
  2077  		gateEnabled bool
  2078  		newPod      *api.Pod
  2079  		wantPod     *api.Pod
  2080  	}{
  2081  		{
  2082  			description: "gate enabled, creating pods with sleep action",
  2083  			gateEnabled: true,
  2084  			newPod:      podWithHandler(),
  2085  			wantPod:     podWithHandler(),
  2086  		},
  2087  		{
  2088  			description: "gate disabled, creating pods with sleep action",
  2089  			gateEnabled: false,
  2090  			newPod:      podWithHandler(),
  2091  			wantPod:     podWithoutHandler(),
  2092  		},
  2093  	}
  2094  
  2095  	for _, tc := range testCases {
  2096  		t.Run(tc.description, func(t *testing.T) {
  2097  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, tc.gateEnabled)()
  2098  
  2099  			newPod := tc.newPod
  2100  
  2101  			Strategy.PrepareForCreate(genericapirequest.NewContext(), newPod)
  2102  			if errs := Strategy.Validate(genericapirequest.NewContext(), newPod); len(errs) != 0 {
  2103  				t.Errorf("Unexpected error: %v", errs.ToAggregate())
  2104  			}
  2105  
  2106  			if diff := cmp.Diff(getLifecycle(newPod), getLifecycle(tc.wantPod)); diff != "" {
  2107  				t.Fatalf("Unexpected modification to life cycle; diff (-got +want)\n%s", diff)
  2108  			}
  2109  		})
  2110  	}
  2111  }
  2112  
  2113  func TestApplyAppArmorVersionSkew(t *testing.T) {
  2114  	testProfile := "test"
  2115  
  2116  	tests := []struct {
  2117  		description   string
  2118  		pod           *api.Pod
  2119  		validation    func(*testing.T, *api.Pod)
  2120  		expectWarning bool
  2121  	}{{
  2122  		description: "Security context nil",
  2123  		pod: &api.Pod{
  2124  			Spec: api.PodSpec{
  2125  				InitContainers: []api.Container{{Name: "init"}},
  2126  				Containers:     []api.Container{{Name: "ctr"}},
  2127  			},
  2128  		},
  2129  		validation: func(t *testing.T, pod *api.Pod) {
  2130  			assert.Empty(t, pod.Annotations)
  2131  			assert.Nil(t, pod.Spec.SecurityContext)
  2132  		},
  2133  	}, {
  2134  		description: "Security context not nil",
  2135  		pod: &api.Pod{
  2136  			Spec: api.PodSpec{
  2137  				SecurityContext: &api.PodSecurityContext{},
  2138  				InitContainers:  []api.Container{{Name: "init"}},
  2139  				Containers:      []api.Container{{Name: "ctr"}},
  2140  			},
  2141  		},
  2142  		validation: func(t *testing.T, pod *api.Pod) {
  2143  			assert.Empty(t, pod.Annotations)
  2144  			assert.Nil(t, pod.Spec.SecurityContext.AppArmorProfile)
  2145  		},
  2146  	}, {
  2147  		description: "Pod field unconfined and no annotation present",
  2148  		pod: &api.Pod{
  2149  			Spec: api.PodSpec{
  2150  				SecurityContext: &api.PodSecurityContext{
  2151  					AppArmorProfile: &api.AppArmorProfile{
  2152  						Type: api.AppArmorProfileTypeUnconfined,
  2153  					},
  2154  				},
  2155  				InitContainers: []api.Container{{Name: "init"}},
  2156  				Containers:     []api.Container{{Name: "ctr"}},
  2157  			},
  2158  		},
  2159  		validation: func(t *testing.T, pod *api.Pod) {
  2160  			assert.Equal(t, map[string]string{
  2161  				api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2162  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr":  api.DeprecatedAppArmorAnnotationValueUnconfined,
  2163  			}, pod.Annotations)
  2164  		},
  2165  	}, {
  2166  		description: "Pod field default and no annotation present",
  2167  		pod: &api.Pod{
  2168  			Spec: api.PodSpec{
  2169  				SecurityContext: &api.PodSecurityContext{
  2170  					AppArmorProfile: &api.AppArmorProfile{
  2171  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2172  					},
  2173  				},
  2174  				InitContainers: []api.Container{{Name: "init"}},
  2175  				Containers:     []api.Container{{Name: "ctr"}},
  2176  			},
  2177  		},
  2178  		validation: func(t *testing.T, pod *api.Pod) {
  2179  			assert.Equal(t, map[string]string{
  2180  				api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2181  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr":  api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2182  			}, pod.Annotations)
  2183  		},
  2184  	}, {
  2185  		description: "Pod field localhost and no annotation present",
  2186  		pod: &api.Pod{
  2187  			Spec: api.PodSpec{
  2188  				SecurityContext: &api.PodSecurityContext{
  2189  					AppArmorProfile: &api.AppArmorProfile{
  2190  						Type:             api.AppArmorProfileTypeLocalhost,
  2191  						LocalhostProfile: &testProfile,
  2192  					},
  2193  				},
  2194  				InitContainers: []api.Container{{Name: "init"}},
  2195  				Containers:     []api.Container{{Name: "ctr"}},
  2196  			},
  2197  		},
  2198  		validation: func(t *testing.T, pod *api.Pod) {
  2199  			assert.Equal(t, map[string]string{
  2200  				api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2201  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr":  api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2202  			}, pod.Annotations)
  2203  		},
  2204  	}, {
  2205  		description: "Pod field localhost but profile is nil",
  2206  		pod: &api.Pod{
  2207  			Spec: api.PodSpec{
  2208  				SecurityContext: &api.PodSecurityContext{
  2209  					AppArmorProfile: &api.AppArmorProfile{
  2210  						Type: api.AppArmorProfileTypeLocalhost,
  2211  					},
  2212  				},
  2213  				InitContainers: []api.Container{{Name: "init"}},
  2214  				Containers:     []api.Container{{Name: "ctr"}},
  2215  			},
  2216  		},
  2217  		validation: func(t *testing.T, pod *api.Pod) {
  2218  			assert.Len(t, pod.Annotations, 0)
  2219  		},
  2220  	}, {
  2221  		description: "Container security context not nil",
  2222  		pod: &api.Pod{
  2223  			Spec: api.PodSpec{
  2224  				Containers: []api.Container{{
  2225  					Name:            "ctr",
  2226  					SecurityContext: &api.SecurityContext{},
  2227  				}},
  2228  			},
  2229  		},
  2230  		validation: func(t *testing.T, pod *api.Pod) {
  2231  			assert.Len(t, pod.Annotations, 0)
  2232  		},
  2233  	}, {
  2234  		description: "Container field RuntimeDefault and no annotation present",
  2235  		pod: &api.Pod{
  2236  			Spec: api.PodSpec{
  2237  				Containers: []api.Container{{
  2238  					Name: "ctr",
  2239  					SecurityContext: &api.SecurityContext{
  2240  						AppArmorProfile: &api.AppArmorProfile{
  2241  							Type: api.AppArmorProfileTypeRuntimeDefault,
  2242  						},
  2243  					},
  2244  				}},
  2245  			},
  2246  		},
  2247  		validation: func(t *testing.T, pod *api.Pod) {
  2248  			assert.Equal(t, map[string]string{
  2249  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2250  			}, pod.Annotations)
  2251  			assert.Nil(t, pod.Spec.SecurityContext)
  2252  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2253  		},
  2254  	}, {
  2255  		description: "Container field localhost and no annotation present",
  2256  		pod: &api.Pod{
  2257  			Spec: api.PodSpec{
  2258  				Containers: []api.Container{{
  2259  					Name: "ctr",
  2260  					SecurityContext: &api.SecurityContext{
  2261  						AppArmorProfile: &api.AppArmorProfile{
  2262  							Type:             api.AppArmorProfileTypeLocalhost,
  2263  							LocalhostProfile: &testProfile,
  2264  						},
  2265  					},
  2266  				}},
  2267  			},
  2268  		},
  2269  		validation: func(t *testing.T, pod *api.Pod) {
  2270  			assert.Equal(t, map[string]string{
  2271  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2272  			}, pod.Annotations)
  2273  			assert.Nil(t, pod.Spec.SecurityContext)
  2274  			assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2275  		},
  2276  	}, {
  2277  		description: "Container overrides pod profile",
  2278  		pod: &api.Pod{
  2279  			Spec: api.PodSpec{
  2280  				SecurityContext: &api.PodSecurityContext{
  2281  					AppArmorProfile: &api.AppArmorProfile{
  2282  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2283  					},
  2284  				},
  2285  				Containers: []api.Container{{
  2286  					Name: "ctr",
  2287  					SecurityContext: &api.SecurityContext{
  2288  						AppArmorProfile: &api.AppArmorProfile{
  2289  							Type: api.AppArmorProfileTypeUnconfined,
  2290  						},
  2291  					},
  2292  				}},
  2293  			},
  2294  		},
  2295  		validation: func(t *testing.T, pod *api.Pod) {
  2296  			assert.Equal(t, map[string]string{
  2297  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2298  			}, pod.Annotations)
  2299  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2300  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2301  		},
  2302  	}, {
  2303  		description: "Multiple containers with fields (container)",
  2304  		pod: &api.Pod{
  2305  			Spec: api.PodSpec{
  2306  				InitContainers: []api.Container{{
  2307  					Name: "init",
  2308  					SecurityContext: &api.SecurityContext{
  2309  						AppArmorProfile: &api.AppArmorProfile{
  2310  							Type:             api.AppArmorProfileTypeLocalhost,
  2311  							LocalhostProfile: &testProfile,
  2312  						},
  2313  					},
  2314  				}},
  2315  				Containers: []api.Container{{
  2316  					Name: "a",
  2317  					SecurityContext: &api.SecurityContext{
  2318  						AppArmorProfile: &api.AppArmorProfile{
  2319  							Type: api.AppArmorProfileTypeUnconfined,
  2320  						},
  2321  					},
  2322  				}, {
  2323  					Name: "b",
  2324  				}, {
  2325  					Name: "c",
  2326  					SecurityContext: &api.SecurityContext{
  2327  						AppArmorProfile: &api.AppArmorProfile{
  2328  							Type: api.AppArmorProfileTypeRuntimeDefault,
  2329  						},
  2330  					},
  2331  				}},
  2332  			},
  2333  		},
  2334  		validation: func(t *testing.T, pod *api.Pod) {
  2335  			assert.Equal(t, map[string]string{
  2336  				api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2337  				api.DeprecatedAppArmorAnnotationKeyPrefix + "a":    api.DeprecatedAppArmorAnnotationValueUnconfined,
  2338  				api.DeprecatedAppArmorAnnotationKeyPrefix + "c":    api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2339  			}, pod.Annotations)
  2340  			assert.Nil(t, pod.Spec.SecurityContext)
  2341  			assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
  2342  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2343  			assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
  2344  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[2].SecurityContext.AppArmorProfile.Type)
  2345  		},
  2346  	}, {
  2347  		description: "Annotation 'unconfined' and no fields present",
  2348  		pod: &api.Pod{
  2349  			ObjectMeta: metav1.ObjectMeta{
  2350  				Annotations: map[string]string{
  2351  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2352  				},
  2353  			},
  2354  			Spec: api.PodSpec{
  2355  				Containers: []api.Container{{Name: "ctr"}},
  2356  			},
  2357  		},
  2358  		validation: func(t *testing.T, pod *api.Pod) {
  2359  			assert.Equal(t, map[string]string{
  2360  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2361  			}, pod.Annotations)
  2362  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2363  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
  2364  			assert.Nil(t, pod.Spec.SecurityContext)
  2365  		},
  2366  		expectWarning: true,
  2367  	}, {
  2368  		description: "Annotation for non-existent container",
  2369  		pod: &api.Pod{
  2370  			ObjectMeta: metav1.ObjectMeta{
  2371  				Annotations: map[string]string{
  2372  					api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2373  				},
  2374  			},
  2375  			Spec: api.PodSpec{
  2376  				Containers: []api.Container{{Name: "ctr"}},
  2377  			},
  2378  		},
  2379  		validation: func(t *testing.T, pod *api.Pod) {
  2380  			assert.Equal(t, map[string]string{
  2381  				api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2382  			}, pod.Annotations)
  2383  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2384  			assert.Nil(t, pod.Spec.SecurityContext)
  2385  		},
  2386  	}, {
  2387  		description: "Annotation 'runtime/default' and no fields present",
  2388  		pod: &api.Pod{
  2389  			ObjectMeta: metav1.ObjectMeta{
  2390  				Annotations: map[string]string{
  2391  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2392  				},
  2393  			},
  2394  			Spec: api.PodSpec{
  2395  				SecurityContext: &api.PodSecurityContext{
  2396  					AppArmorProfile: &api.AppArmorProfile{
  2397  						Type: api.AppArmorProfileTypeUnconfined,
  2398  					},
  2399  				},
  2400  				Containers: []api.Container{{
  2401  					Name:            "ctr",
  2402  					SecurityContext: &api.SecurityContext{},
  2403  				}},
  2404  			},
  2405  		},
  2406  		validation: func(t *testing.T, pod *api.Pod) {
  2407  			assert.Equal(t, map[string]string{
  2408  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2409  			}, pod.Annotations)
  2410  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2411  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
  2412  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2413  		},
  2414  		expectWarning: true,
  2415  	}, {
  2416  		description: "Multiple containers by annotations",
  2417  		pod: &api.Pod{
  2418  			ObjectMeta: metav1.ObjectMeta{
  2419  				Annotations: map[string]string{
  2420  					api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2421  					api.DeprecatedAppArmorAnnotationKeyPrefix + "a":    api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2422  					api.DeprecatedAppArmorAnnotationKeyPrefix + "c":    api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2423  				},
  2424  			},
  2425  			Spec: api.PodSpec{
  2426  				SecurityContext: &api.PodSecurityContext{
  2427  					AppArmorProfile: &api.AppArmorProfile{
  2428  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2429  					},
  2430  				},
  2431  				InitContainers: []api.Container{{Name: "init"}},
  2432  				Containers: []api.Container{
  2433  					{Name: "a"},
  2434  					{Name: "b"},
  2435  					{Name: "c"},
  2436  				},
  2437  			},
  2438  		},
  2439  		validation: func(t *testing.T, pod *api.Pod) {
  2440  			assert.Equal(t, map[string]string{
  2441  				api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2442  				api.DeprecatedAppArmorAnnotationKeyPrefix + "a":    api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2443  				api.DeprecatedAppArmorAnnotationKeyPrefix + "b":    api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2444  				api.DeprecatedAppArmorAnnotationKeyPrefix + "c":    api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2445  			}, pod.Annotations)
  2446  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type)
  2447  			assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2448  			assert.Equal(t, testProfile, *pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
  2449  			assert.Nil(t, pod.Spec.Containers[1].SecurityContext)
  2450  			assert.Nil(t, pod.Spec.Containers[2].SecurityContext)
  2451  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2452  		},
  2453  		expectWarning: true,
  2454  	}, {
  2455  		description: "Conflicting field and annotations",
  2456  		pod: &api.Pod{
  2457  			ObjectMeta: metav1.ObjectMeta{
  2458  				Annotations: map[string]string{
  2459  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2460  				},
  2461  			},
  2462  			Spec: api.PodSpec{
  2463  				Containers: []api.Container{{
  2464  					Name: "ctr",
  2465  					SecurityContext: &api.SecurityContext{
  2466  						AppArmorProfile: &api.AppArmorProfile{
  2467  							Type: api.AppArmorProfileTypeRuntimeDefault,
  2468  						},
  2469  					},
  2470  				}},
  2471  			},
  2472  		},
  2473  		validation: func(t *testing.T, pod *api.Pod) {
  2474  			assert.Equal(t, map[string]string{
  2475  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile,
  2476  			}, pod.Annotations)
  2477  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2478  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile)
  2479  			assert.Nil(t, pod.Spec.SecurityContext)
  2480  		},
  2481  	}, {
  2482  		description: "Pod field and matching annotations",
  2483  		pod: &api.Pod{
  2484  			ObjectMeta: metav1.ObjectMeta{
  2485  				Annotations: map[string]string{
  2486  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2487  				},
  2488  			},
  2489  			Spec: api.PodSpec{
  2490  				SecurityContext: &api.PodSecurityContext{
  2491  					AppArmorProfile: &api.AppArmorProfile{
  2492  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2493  					},
  2494  				},
  2495  				Containers: []api.Container{{
  2496  					Name: "ctr",
  2497  				}},
  2498  			},
  2499  		},
  2500  		validation: func(t *testing.T, pod *api.Pod) {
  2501  			assert.Equal(t, map[string]string{
  2502  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2503  			}, pod.Annotations)
  2504  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2505  			// Annotation shouldn't be synced to container security context
  2506  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2507  		},
  2508  	}, {
  2509  		description: "Annotation overrides pod field",
  2510  		pod: &api.Pod{
  2511  			ObjectMeta: metav1.ObjectMeta{
  2512  				Annotations: map[string]string{
  2513  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2514  				},
  2515  			},
  2516  			Spec: api.PodSpec{
  2517  				SecurityContext: &api.PodSecurityContext{
  2518  					AppArmorProfile: &api.AppArmorProfile{
  2519  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2520  					},
  2521  				},
  2522  				Containers: []api.Container{{
  2523  					Name: "ctr",
  2524  				}},
  2525  			},
  2526  		},
  2527  		validation: func(t *testing.T, pod *api.Pod) {
  2528  			assert.Equal(t, map[string]string{
  2529  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2530  			}, pod.Annotations)
  2531  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2532  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2533  		},
  2534  		expectWarning: true,
  2535  	}, {
  2536  		description: "Mixed annotations and fields",
  2537  		pod: &api.Pod{
  2538  			ObjectMeta: metav1.ObjectMeta{
  2539  				Annotations: map[string]string{
  2540  					api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2541  				},
  2542  			},
  2543  			Spec: api.PodSpec{
  2544  				SecurityContext: &api.PodSecurityContext{
  2545  					AppArmorProfile: &api.AppArmorProfile{
  2546  						Type: api.AppArmorProfileTypeRuntimeDefault,
  2547  					},
  2548  				},
  2549  				Containers: []api.Container{{
  2550  					Name: "unconf-annot",
  2551  				}, {
  2552  					Name: "unconf-field",
  2553  					SecurityContext: &api.SecurityContext{
  2554  						AppArmorProfile: &api.AppArmorProfile{
  2555  							Type: api.AppArmorProfileTypeUnconfined,
  2556  						},
  2557  					},
  2558  				}, {
  2559  					Name: "default-pod",
  2560  				}},
  2561  			},
  2562  		},
  2563  		validation: func(t *testing.T, pod *api.Pod) {
  2564  			assert.Equal(t, map[string]string{
  2565  				api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2566  				api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-field": api.DeprecatedAppArmorAnnotationValueUnconfined,
  2567  				api.DeprecatedAppArmorAnnotationKeyPrefix + "default-pod":  api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2568  			}, pod.Annotations)
  2569  			assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type)
  2570  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type)
  2571  			assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[1].SecurityContext.AppArmorProfile.Type)
  2572  			assert.Nil(t, pod.Spec.Containers[2].SecurityContext)
  2573  		},
  2574  		expectWarning: true,
  2575  	}, {
  2576  		description: "Invalid annotation value",
  2577  		pod: &api.Pod{
  2578  			ObjectMeta: metav1.ObjectMeta{
  2579  				Annotations: map[string]string{
  2580  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "localhost/",
  2581  				},
  2582  			},
  2583  			Spec: api.PodSpec{
  2584  				Containers: []api.Container{{Name: "ctr"}},
  2585  			},
  2586  		},
  2587  		validation: func(t *testing.T, pod *api.Pod) {
  2588  			assert.Equal(t, map[string]string{
  2589  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "localhost/",
  2590  			}, pod.Annotations)
  2591  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2592  			assert.Nil(t, pod.Spec.SecurityContext)
  2593  		},
  2594  		expectWarning: true,
  2595  	}, {
  2596  		description: "Invalid localhost annotation",
  2597  		pod: &api.Pod{
  2598  			ObjectMeta: metav1.ObjectMeta{
  2599  				Annotations: map[string]string{
  2600  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + strings.Repeat("a", 4096),
  2601  				},
  2602  			},
  2603  			Spec: api.PodSpec{
  2604  				Containers: []api.Container{{Name: "ctr"}},
  2605  			},
  2606  		},
  2607  		validation: func(t *testing.T, pod *api.Pod) {
  2608  			assert.Contains(t, pod.Annotations, api.DeprecatedAppArmorAnnotationKeyPrefix+"ctr")
  2609  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2610  			assert.Nil(t, pod.Spec.SecurityContext)
  2611  		},
  2612  		expectWarning: true,
  2613  	}, {
  2614  		description: "Invalid field type",
  2615  		pod: &api.Pod{
  2616  			Spec: api.PodSpec{
  2617  				SecurityContext: &api.PodSecurityContext{
  2618  					AppArmorProfile: &api.AppArmorProfile{
  2619  						Type: "invalid-type",
  2620  					},
  2621  				},
  2622  				Containers: []api.Container{{Name: "ctr"}},
  2623  			},
  2624  		},
  2625  		validation: func(t *testing.T, pod *api.Pod) {
  2626  			assert.Empty(t, pod.Annotations)
  2627  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2628  		},
  2629  	}, {
  2630  		description: "Ignore annotations on windows",
  2631  		pod: &api.Pod{
  2632  			ObjectMeta: metav1.ObjectMeta{
  2633  				Annotations: map[string]string{
  2634  					api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2635  				},
  2636  			},
  2637  			Spec: api.PodSpec{
  2638  				OS:         &api.PodOS{Name: api.Windows},
  2639  				Containers: []api.Container{{Name: "ctr"}},
  2640  			},
  2641  		},
  2642  		validation: func(t *testing.T, pod *api.Pod) {
  2643  			assert.Equal(t, map[string]string{
  2644  				api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault,
  2645  			}, pod.Annotations)
  2646  			assert.Nil(t, pod.Spec.Containers[0].SecurityContext)
  2647  		},
  2648  	}}
  2649  
  2650  	for _, test := range tests {
  2651  		t.Run(test.description, func(t *testing.T) {
  2652  			warnings := &warningRecorder{}
  2653  			ctx := warning.WithWarningRecorder(context.Background(), warnings)
  2654  			applyAppArmorVersionSkew(ctx, test.pod)
  2655  			test.validation(t, test.pod)
  2656  
  2657  			if test.expectWarning {
  2658  				if assert.NotEmpty(t, warnings.warnings, "expect warnings") {
  2659  					assert.Contains(t, warnings.warnings[0], `deprecated since v1.30; use the "appArmorProfile" field instead`)
  2660  				}
  2661  			} else {
  2662  				assert.Empty(t, warnings.warnings, "shouldn't emit a warning")
  2663  			}
  2664  		})
  2665  	}
  2666  }
  2667  
  2668  type warningRecorder struct {
  2669  	warnings []string
  2670  }
  2671  
  2672  var _ warning.Recorder = &warningRecorder{}
  2673  
  2674  func (w *warningRecorder) AddWarning(_, text string) {
  2675  	w.warnings = append(w.warnings, text)
  2676  }
  2677  

View as plain text