k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/core/validation/validation_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"math"
    23  	"reflect"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"google.golang.org/protobuf/proto"
    34  	v1 "k8s.io/api/core/v1"
    35  	"k8s.io/apimachinery/pkg/api/resource"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/util/intstr"
    38  	"k8s.io/apimachinery/pkg/util/sets"
    39  	"k8s.io/apimachinery/pkg/util/validation"
    40  	"k8s.io/apimachinery/pkg/util/validation/field"
    41  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    42  	"k8s.io/component-base/featuregate"
    43  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    44  	kubeletapis "k8s.io/kubelet/pkg/apis"
    45  	"k8s.io/kubernetes/pkg/apis/core"
    46  	"k8s.io/kubernetes/pkg/capabilities"
    47  	"k8s.io/kubernetes/pkg/features"
    48  	utilpointer "k8s.io/utils/pointer"
    49  	"k8s.io/utils/ptr"
    50  )
    51  
    52  const (
    53  	dnsLabelErrMsg                    = "a lowercase RFC 1123 label must consist of"
    54  	dnsSubdomainLabelErrMsg           = "a lowercase RFC 1123 subdomain"
    55  	envVarNameErrMsg                  = "a valid environment variable name must consist of"
    56  	relaxedEnvVarNameFmtErrMsg string = "a valid environment variable name must consist only of printable ASCII characters other than '='"
    57  	defaultGracePeriod                = int64(30)
    58  	noUserNamespace                   = false
    59  )
    60  
    61  var (
    62  	containerRestartPolicyAlways    = core.ContainerRestartPolicyAlways
    63  	containerRestartPolicyOnFailure = core.ContainerRestartPolicy("OnFailure")
    64  	containerRestartPolicyNever     = core.ContainerRestartPolicy("Never")
    65  	containerRestartPolicyInvalid   = core.ContainerRestartPolicy("invalid")
    66  	containerRestartPolicyEmpty     = core.ContainerRestartPolicy("")
    67  )
    68  
    69  type topologyPair struct {
    70  	key   string
    71  	value string
    72  }
    73  
    74  func line() string {
    75  	_, _, line, ok := runtime.Caller(1)
    76  	var s string
    77  	if ok {
    78  		s = fmt.Sprintf("%d", line)
    79  	} else {
    80  		s = "<??>"
    81  	}
    82  	return s
    83  }
    84  
    85  func prettyErrorList(errs field.ErrorList) string {
    86  	var s string
    87  	for _, e := range errs {
    88  		s += fmt.Sprintf("\t%s\n", e)
    89  	}
    90  	return s
    91  }
    92  
    93  func newHostPathType(pathType string) *core.HostPathType {
    94  	hostPathType := new(core.HostPathType)
    95  	*hostPathType = core.HostPathType(pathType)
    96  	return hostPathType
    97  }
    98  
    99  func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume {
   100  	objMeta := metav1.ObjectMeta{Name: name}
   101  	if namespace != "" {
   102  		objMeta.Namespace = namespace
   103  	}
   104  
   105  	return &core.PersistentVolume{
   106  		ObjectMeta: objMeta,
   107  		Spec:       spec,
   108  	}
   109  }
   110  
   111  func TestValidatePersistentVolumes(t *testing.T) {
   112  	validMode := core.PersistentVolumeFilesystem
   113  	invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
   114  	scenarios := map[string]struct {
   115  		isExpectedFailure           bool
   116  		enableVolumeAttributesClass bool
   117  		volume                      *core.PersistentVolume
   118  	}{
   119  		"good-volume": {
   120  			isExpectedFailure: false,
   121  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   122  				Capacity: core.ResourceList{
   123  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   124  				},
   125  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   126  				PersistentVolumeSource: core.PersistentVolumeSource{
   127  					HostPath: &core.HostPathVolumeSource{
   128  						Path: "/foo",
   129  						Type: newHostPathType(string(core.HostPathDirectory)),
   130  					},
   131  				},
   132  			}),
   133  		},
   134  		"good-volume-with-capacity-unit": {
   135  			isExpectedFailure: false,
   136  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   137  				Capacity: core.ResourceList{
   138  					core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"),
   139  				},
   140  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   141  				PersistentVolumeSource: core.PersistentVolumeSource{
   142  					HostPath: &core.HostPathVolumeSource{
   143  						Path: "/foo",
   144  						Type: newHostPathType(string(core.HostPathDirectory)),
   145  					},
   146  				},
   147  			}),
   148  		},
   149  		"good-volume-without-capacity-unit": {
   150  			isExpectedFailure: false,
   151  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   152  				Capacity: core.ResourceList{
   153  					core.ResourceName(core.ResourceStorage): resource.MustParse("10"),
   154  				},
   155  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   156  				PersistentVolumeSource: core.PersistentVolumeSource{
   157  					HostPath: &core.HostPathVolumeSource{
   158  						Path: "/foo",
   159  						Type: newHostPathType(string(core.HostPathDirectory)),
   160  					},
   161  				},
   162  			}),
   163  		},
   164  		"good-volume-with-storage-class": {
   165  			isExpectedFailure: false,
   166  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   167  				Capacity: core.ResourceList{
   168  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   169  				},
   170  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   171  				PersistentVolumeSource: core.PersistentVolumeSource{
   172  					HostPath: &core.HostPathVolumeSource{
   173  						Path: "/foo",
   174  						Type: newHostPathType(string(core.HostPathDirectory)),
   175  					},
   176  				},
   177  				StorageClassName: "valid",
   178  			}),
   179  		},
   180  		"good-volume-with-retain-policy": {
   181  			isExpectedFailure: false,
   182  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   183  				Capacity: core.ResourceList{
   184  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   185  				},
   186  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   187  				PersistentVolumeSource: core.PersistentVolumeSource{
   188  					HostPath: &core.HostPathVolumeSource{
   189  						Path: "/foo",
   190  						Type: newHostPathType(string(core.HostPathDirectory)),
   191  					},
   192  				},
   193  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain,
   194  			}),
   195  		},
   196  		"good-volume-with-volume-mode": {
   197  			isExpectedFailure: false,
   198  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   199  				Capacity: core.ResourceList{
   200  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   201  				},
   202  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   203  				PersistentVolumeSource: core.PersistentVolumeSource{
   204  					HostPath: &core.HostPathVolumeSource{
   205  						Path: "/foo",
   206  						Type: newHostPathType(string(core.HostPathDirectory)),
   207  					},
   208  				},
   209  				VolumeMode: &validMode,
   210  			}),
   211  		},
   212  		"invalid-accessmode": {
   213  			isExpectedFailure: true,
   214  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   215  				Capacity: core.ResourceList{
   216  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   217  				},
   218  				AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
   219  				PersistentVolumeSource: core.PersistentVolumeSource{
   220  					HostPath: &core.HostPathVolumeSource{
   221  						Path: "/foo",
   222  						Type: newHostPathType(string(core.HostPathDirectory)),
   223  					},
   224  				},
   225  			}),
   226  		},
   227  		"invalid-reclaimpolicy": {
   228  			isExpectedFailure: true,
   229  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   230  				Capacity: core.ResourceList{
   231  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   232  				},
   233  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   234  				PersistentVolumeSource: core.PersistentVolumeSource{
   235  					HostPath: &core.HostPathVolumeSource{
   236  						Path: "/foo",
   237  						Type: newHostPathType(string(core.HostPathDirectory)),
   238  					},
   239  				},
   240  				PersistentVolumeReclaimPolicy: "fakeReclaimPolicy",
   241  			}),
   242  		},
   243  		"invalid-volume-mode": {
   244  			isExpectedFailure: true,
   245  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   246  				Capacity: core.ResourceList{
   247  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   248  				},
   249  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   250  				PersistentVolumeSource: core.PersistentVolumeSource{
   251  					HostPath: &core.HostPathVolumeSource{
   252  						Path: "/foo",
   253  						Type: newHostPathType(string(core.HostPathDirectory)),
   254  					},
   255  				},
   256  				VolumeMode: &invalidMode,
   257  			}),
   258  		},
   259  		"with-read-write-once-pod": {
   260  			isExpectedFailure: false,
   261  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   262  				Capacity: core.ResourceList{
   263  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   264  				},
   265  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
   266  				PersistentVolumeSource: core.PersistentVolumeSource{
   267  					HostPath: &core.HostPathVolumeSource{
   268  						Path: "/foo",
   269  						Type: newHostPathType(string(core.HostPathDirectory)),
   270  					},
   271  				},
   272  			}),
   273  		},
   274  		"with-read-write-once-pod-and-others": {
   275  			isExpectedFailure: true,
   276  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   277  				Capacity: core.ResourceList{
   278  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   279  				},
   280  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
   281  				PersistentVolumeSource: core.PersistentVolumeSource{
   282  					HostPath: &core.HostPathVolumeSource{
   283  						Path: "/foo",
   284  						Type: newHostPathType(string(core.HostPathDirectory)),
   285  					},
   286  				},
   287  			}),
   288  		},
   289  		"unexpected-namespace": {
   290  			isExpectedFailure: true,
   291  			volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
   292  				Capacity: core.ResourceList{
   293  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   294  				},
   295  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   296  				PersistentVolumeSource: core.PersistentVolumeSource{
   297  					HostPath: &core.HostPathVolumeSource{
   298  						Path: "/foo",
   299  						Type: newHostPathType(string(core.HostPathDirectory)),
   300  					},
   301  				},
   302  			}),
   303  		},
   304  		"missing-volume-source": {
   305  			isExpectedFailure: true,
   306  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   307  				Capacity: core.ResourceList{
   308  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   309  				},
   310  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   311  			}),
   312  		},
   313  		"bad-name": {
   314  			isExpectedFailure: true,
   315  			volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{
   316  				Capacity: core.ResourceList{
   317  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   318  				},
   319  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   320  				PersistentVolumeSource: core.PersistentVolumeSource{
   321  					HostPath: &core.HostPathVolumeSource{
   322  						Path: "/foo",
   323  						Type: newHostPathType(string(core.HostPathDirectory)),
   324  					},
   325  				},
   326  			}),
   327  		},
   328  		"missing-name": {
   329  			isExpectedFailure: true,
   330  			volume: testVolume("", "", core.PersistentVolumeSpec{
   331  				Capacity: core.ResourceList{
   332  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   333  				},
   334  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   335  				PersistentVolumeSource: core.PersistentVolumeSource{
   336  					HostPath: &core.HostPathVolumeSource{
   337  						Path: "/foo",
   338  						Type: newHostPathType(string(core.HostPathDirectory)),
   339  					},
   340  				},
   341  			}),
   342  		},
   343  		"missing-capacity": {
   344  			isExpectedFailure: true,
   345  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   346  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   347  				PersistentVolumeSource: core.PersistentVolumeSource{
   348  					HostPath: &core.HostPathVolumeSource{
   349  						Path: "/foo",
   350  						Type: newHostPathType(string(core.HostPathDirectory)),
   351  					},
   352  				},
   353  			}),
   354  		},
   355  		"bad-volume-zero-capacity": {
   356  			isExpectedFailure: true,
   357  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   358  				Capacity: core.ResourceList{
   359  					core.ResourceName(core.ResourceStorage): resource.MustParse("0"),
   360  				},
   361  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   362  				PersistentVolumeSource: core.PersistentVolumeSource{
   363  					HostPath: &core.HostPathVolumeSource{
   364  						Path: "/foo",
   365  						Type: newHostPathType(string(core.HostPathDirectory)),
   366  					},
   367  				},
   368  			}),
   369  		},
   370  		"missing-accessmodes": {
   371  			isExpectedFailure: true,
   372  			volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{
   373  				Capacity: core.ResourceList{
   374  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   375  				},
   376  				PersistentVolumeSource: core.PersistentVolumeSource{
   377  					HostPath: &core.HostPathVolumeSource{
   378  						Path: "/foo",
   379  						Type: newHostPathType(string(core.HostPathDirectory)),
   380  					},
   381  				},
   382  			}),
   383  		},
   384  		"too-many-sources": {
   385  			isExpectedFailure: true,
   386  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   387  				Capacity: core.ResourceList{
   388  					core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
   389  				},
   390  				PersistentVolumeSource: core.PersistentVolumeSource{
   391  					HostPath: &core.HostPathVolumeSource{
   392  						Path: "/foo",
   393  						Type: newHostPathType(string(core.HostPathDirectory)),
   394  					},
   395  					GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"},
   396  				},
   397  			}),
   398  		},
   399  		"host mount of / with recycle reclaim policy": {
   400  			isExpectedFailure: true,
   401  			volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
   402  				Capacity: core.ResourceList{
   403  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   404  				},
   405  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   406  				PersistentVolumeSource: core.PersistentVolumeSource{
   407  					HostPath: &core.HostPathVolumeSource{
   408  						Path: "/",
   409  						Type: newHostPathType(string(core.HostPathDirectory)),
   410  					},
   411  				},
   412  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   413  			}),
   414  		},
   415  		"host mount of / with recycle reclaim policy 2": {
   416  			isExpectedFailure: true,
   417  			volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
   418  				Capacity: core.ResourceList{
   419  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   420  				},
   421  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   422  				PersistentVolumeSource: core.PersistentVolumeSource{
   423  					HostPath: &core.HostPathVolumeSource{
   424  						Path: "/a/..",
   425  						Type: newHostPathType(string(core.HostPathDirectory)),
   426  					},
   427  				},
   428  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   429  			}),
   430  		},
   431  		"invalid-storage-class-name": {
   432  			isExpectedFailure: true,
   433  			volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{
   434  				Capacity: core.ResourceList{
   435  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   436  				},
   437  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   438  				PersistentVolumeSource: core.PersistentVolumeSource{
   439  					HostPath: &core.HostPathVolumeSource{
   440  						Path: "/foo",
   441  						Type: newHostPathType(string(core.HostPathDirectory)),
   442  					},
   443  				},
   444  				StorageClassName: "-invalid-",
   445  			}),
   446  		},
   447  		"bad-hostpath-volume-backsteps": {
   448  			isExpectedFailure: true,
   449  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   450  				Capacity: core.ResourceList{
   451  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   452  				},
   453  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   454  				PersistentVolumeSource: core.PersistentVolumeSource{
   455  					HostPath: &core.HostPathVolumeSource{
   456  						Path: "/foo/..",
   457  						Type: newHostPathType(string(core.HostPathDirectory)),
   458  					},
   459  				},
   460  				StorageClassName: "backstep-hostpath",
   461  			}),
   462  		},
   463  		"volume-node-affinity": {
   464  			isExpectedFailure: false,
   465  			volume:            testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
   466  		},
   467  		"volume-empty-node-affinity": {
   468  			isExpectedFailure: true,
   469  			volume:            testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}),
   470  		},
   471  		"volume-bad-node-affinity": {
   472  			isExpectedFailure: true,
   473  			volume: testVolumeWithNodeAffinity(
   474  				&core.VolumeNodeAffinity{
   475  					Required: &core.NodeSelector{
   476  						NodeSelectorTerms: []core.NodeSelectorTerm{{
   477  							MatchExpressions: []core.NodeSelectorRequirement{{
   478  								Operator: core.NodeSelectorOpIn,
   479  								Values:   []string{"test-label-value"},
   480  							}},
   481  						}},
   482  					},
   483  				}),
   484  		},
   485  		"invalid-volume-attributes-class-name": {
   486  			isExpectedFailure:           true,
   487  			enableVolumeAttributesClass: true,
   488  			volume: testVolume("invalid-volume-attributes-class-name", "", core.PersistentVolumeSpec{
   489  				Capacity: core.ResourceList{
   490  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   491  				},
   492  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   493  				PersistentVolumeSource: core.PersistentVolumeSource{
   494  					HostPath: &core.HostPathVolumeSource{
   495  						Path: "/foo",
   496  						Type: newHostPathType(string(core.HostPathDirectory)),
   497  					},
   498  				},
   499  				StorageClassName:          "invalid",
   500  				VolumeAttributesClassName: ptr.To("-invalid-"),
   501  			}),
   502  		},
   503  		"invalid-empty-volume-attributes-class-name": {
   504  			isExpectedFailure:           true,
   505  			enableVolumeAttributesClass: true,
   506  			volume: testVolume("invalid-empty-volume-attributes-class-name", "", core.PersistentVolumeSpec{
   507  				Capacity: core.ResourceList{
   508  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   509  				},
   510  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   511  				PersistentVolumeSource: core.PersistentVolumeSource{
   512  					HostPath: &core.HostPathVolumeSource{
   513  						Path: "/foo",
   514  						Type: newHostPathType(string(core.HostPathDirectory)),
   515  					},
   516  				},
   517  				StorageClassName:          "invalid",
   518  				VolumeAttributesClassName: ptr.To(""),
   519  			}),
   520  		},
   521  		"volume-with-good-volume-attributes-class-and-matched-volume-resource-when-feature-gate-is-on": {
   522  			isExpectedFailure:           false,
   523  			enableVolumeAttributesClass: true,
   524  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   525  				Capacity: core.ResourceList{
   526  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   527  				},
   528  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   529  				PersistentVolumeSource: core.PersistentVolumeSource{
   530  					CSI: &core.CSIPersistentVolumeSource{
   531  						Driver:       "test-driver",
   532  						VolumeHandle: "test-123",
   533  					},
   534  				},
   535  				StorageClassName:          "valid",
   536  				VolumeAttributesClassName: ptr.To("valid"),
   537  			}),
   538  		},
   539  		"volume-with-good-volume-attributes-class-and-mismatched-volume-resource-when-feature-gate-is-on": {
   540  			isExpectedFailure:           true,
   541  			enableVolumeAttributesClass: true,
   542  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   543  				Capacity: core.ResourceList{
   544  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   545  				},
   546  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   547  				PersistentVolumeSource: core.PersistentVolumeSource{
   548  					HostPath: &core.HostPathVolumeSource{
   549  						Path: "/foo",
   550  						Type: newHostPathType(string(core.HostPathDirectory)),
   551  					},
   552  				},
   553  				StorageClassName:          "valid",
   554  				VolumeAttributesClassName: ptr.To("valid"),
   555  			}),
   556  		},
   557  	}
   558  
   559  	for name, scenario := range scenarios {
   560  		t.Run(name, func(t *testing.T) {
   561  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)
   562  
   563  			opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
   564  			errs := ValidatePersistentVolume(scenario.volume, opts)
   565  			if len(errs) == 0 && scenario.isExpectedFailure {
   566  				t.Errorf("Unexpected success for scenario: %s", name)
   567  			}
   568  			if len(errs) > 0 && !scenario.isExpectedFailure {
   569  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   570  			}
   571  		})
   572  	}
   573  
   574  }
   575  
   576  func TestValidatePersistentVolumeSpec(t *testing.T) {
   577  	fsmode := core.PersistentVolumeFilesystem
   578  	blockmode := core.PersistentVolumeBlock
   579  	scenarios := map[string]struct {
   580  		isExpectedFailure bool
   581  		isInlineSpec      bool
   582  		pvSpec            *core.PersistentVolumeSpec
   583  	}{
   584  		"pv-pvspec-valid": {
   585  			isExpectedFailure: false,
   586  			isInlineSpec:      false,
   587  			pvSpec: &core.PersistentVolumeSpec{
   588  				Capacity: core.ResourceList{
   589  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   590  				},
   591  				StorageClassName:              "testclass",
   592  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   593  				AccessModes:                   []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   594  				PersistentVolumeSource: core.PersistentVolumeSource{
   595  					HostPath: &core.HostPathVolumeSource{
   596  						Path: "/foo",
   597  						Type: newHostPathType(string(core.HostPathDirectory)),
   598  					},
   599  				},
   600  				VolumeMode:   &fsmode,
   601  				NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
   602  			},
   603  		},
   604  		"inline-pvspec-with-capacity": {
   605  			isExpectedFailure: true,
   606  			isInlineSpec:      true,
   607  			pvSpec: &core.PersistentVolumeSpec{
   608  				Capacity: core.ResourceList{
   609  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   610  				},
   611  				PersistentVolumeSource: core.PersistentVolumeSource{
   612  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   613  				},
   614  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   615  			},
   616  		},
   617  		"inline-pvspec-with-podSec": {
   618  			isExpectedFailure: true,
   619  			isInlineSpec:      true,
   620  			pvSpec: &core.PersistentVolumeSpec{
   621  				PersistentVolumeSource: core.PersistentVolumeSource{
   622  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   623  				},
   624  				AccessModes:      []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   625  				StorageClassName: "testclass",
   626  			},
   627  		},
   628  		"inline-pvspec-with-non-fs-volume-mode": {
   629  			isExpectedFailure: true,
   630  			isInlineSpec:      true,
   631  			pvSpec: &core.PersistentVolumeSpec{
   632  				PersistentVolumeSource: core.PersistentVolumeSource{
   633  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   634  				},
   635  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   636  				VolumeMode:  &blockmode,
   637  			},
   638  		},
   639  		"inline-pvspec-with-non-retain-reclaim-policy": {
   640  			isExpectedFailure: true,
   641  			isInlineSpec:      true,
   642  			pvSpec: &core.PersistentVolumeSpec{
   643  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   644  				PersistentVolumeSource: core.PersistentVolumeSource{
   645  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   646  				},
   647  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   648  			},
   649  		},
   650  		"inline-pvspec-with-node-affinity": {
   651  			isExpectedFailure: true,
   652  			isInlineSpec:      true,
   653  			pvSpec: &core.PersistentVolumeSpec{
   654  				PersistentVolumeSource: core.PersistentVolumeSource{
   655  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   656  				},
   657  				AccessModes:  []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   658  				NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
   659  			},
   660  		},
   661  		"inline-pvspec-with-non-csi-source": {
   662  			isExpectedFailure: true,
   663  			isInlineSpec:      true,
   664  			pvSpec: &core.PersistentVolumeSpec{
   665  				PersistentVolumeSource: core.PersistentVolumeSource{
   666  					HostPath: &core.HostPathVolumeSource{
   667  						Path: "/foo",
   668  						Type: newHostPathType(string(core.HostPathDirectory)),
   669  					},
   670  				},
   671  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   672  			},
   673  		},
   674  		"inline-pvspec-valid-with-access-modes-and-mount-options": {
   675  			isExpectedFailure: false,
   676  			isInlineSpec:      true,
   677  			pvSpec: &core.PersistentVolumeSpec{
   678  				PersistentVolumeSource: core.PersistentVolumeSource{
   679  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   680  				},
   681  				AccessModes:  []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   682  				MountOptions: []string{"soft", "read-write"},
   683  			},
   684  		},
   685  		"inline-pvspec-valid-with-access-modes": {
   686  			isExpectedFailure: false,
   687  			isInlineSpec:      true,
   688  			pvSpec: &core.PersistentVolumeSpec{
   689  				PersistentVolumeSource: core.PersistentVolumeSource{
   690  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   691  				},
   692  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   693  			},
   694  		},
   695  		"inline-pvspec-with-missing-acess-modes": {
   696  			isExpectedFailure: true,
   697  			isInlineSpec:      true,
   698  			pvSpec: &core.PersistentVolumeSpec{
   699  				PersistentVolumeSource: core.PersistentVolumeSource{
   700  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   701  				},
   702  				MountOptions: []string{"soft", "read-write"},
   703  			},
   704  		},
   705  	}
   706  	for name, scenario := range scenarios {
   707  		opts := PersistentVolumeSpecValidationOptions{}
   708  		errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts)
   709  		if len(errs) == 0 && scenario.isExpectedFailure {
   710  			t.Errorf("Unexpected success for scenario: %s", name)
   711  		}
   712  		if len(errs) > 0 && !scenario.isExpectedFailure {
   713  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   714  		}
   715  	}
   716  }
   717  
   718  func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
   719  	validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
   720  		Capacity: core.ResourceList{
   721  			core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
   722  		},
   723  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   724  		PersistentVolumeSource: core.PersistentVolumeSource{
   725  			HostPath: &core.HostPathVolumeSource{
   726  				Path: "/foo",
   727  				Type: newHostPathType(string(core.HostPathDirectory)),
   728  			},
   729  		},
   730  		StorageClassName: "valid",
   731  	})
   732  	validPvSourceNoUpdate := validVolume.DeepCopy()
   733  	invalidPvSourceUpdateType := validVolume.DeepCopy()
   734  	invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
   735  		FlexVolume: &core.FlexPersistentVolumeSource{
   736  			Driver: "kubernetes.io/blue",
   737  			FSType: "ext4",
   738  		},
   739  	}
   740  	invalidPvSourceUpdateDeep := validVolume.DeepCopy()
   741  	invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
   742  		HostPath: &core.HostPathVolumeSource{
   743  			Path: "/updated",
   744  			Type: newHostPathType(string(core.HostPathDirectory)),
   745  		},
   746  	}
   747  
   748  	validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{
   749  		Capacity: core.ResourceList{
   750  			core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
   751  		},
   752  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   753  		PersistentVolumeSource: core.PersistentVolumeSource{
   754  			CSI: &core.CSIPersistentVolumeSource{
   755  				Driver:       "come.google.gcepd",
   756  				VolumeHandle: "foobar",
   757  			},
   758  		},
   759  		StorageClassName: "gp2",
   760  	})
   761  
   762  	expandSecretRef := &core.SecretReference{
   763  		Name:      "expansion-secret",
   764  		Namespace: "default",
   765  	}
   766  
   767  	// shortSecretRef refers to the secretRefs which are validated with IsDNS1035Label
   768  	shortSecretName := "key-name"
   769  	shortSecretRef := &core.SecretReference{
   770  		Name:      shortSecretName,
   771  		Namespace: "default",
   772  	}
   773  
   774  	// longSecretRef refers to the secretRefs which are validated with IsDNS1123Subdomain
   775  	longSecretName := "key-name.example.com"
   776  	longSecretRef := &core.SecretReference{
   777  		Name:      longSecretName,
   778  		Namespace: "default",
   779  	}
   780  
   781  	// invalidSecrets missing name, namespace and both
   782  	inValidSecretRef := &core.SecretReference{
   783  		Name:      "",
   784  		Namespace: "",
   785  	}
   786  	invalidSecretRefmissingName := &core.SecretReference{
   787  		Name:      "",
   788  		Namespace: "default",
   789  	}
   790  	invalidSecretRefmissingNamespace := &core.SecretReference{
   791  		Name:      "invalidnamespace",
   792  		Namespace: "",
   793  	}
   794  
   795  	scenarios := map[string]struct {
   796  		isExpectedFailure bool
   797  		oldVolume         *core.PersistentVolume
   798  		newVolume         *core.PersistentVolume
   799  	}{
   800  		"condition-no-update": {
   801  			isExpectedFailure: false,
   802  			oldVolume:         validVolume,
   803  			newVolume:         validPvSourceNoUpdate,
   804  		},
   805  		"condition-update-source-type": {
   806  			isExpectedFailure: true,
   807  			oldVolume:         validVolume,
   808  			newVolume:         invalidPvSourceUpdateType,
   809  		},
   810  		"condition-update-source-deep": {
   811  			isExpectedFailure: true,
   812  			oldVolume:         validVolume,
   813  			newVolume:         invalidPvSourceUpdateDeep,
   814  		},
   815  		"csi-expansion-enabled-with-pv-secret": {
   816  			isExpectedFailure: false,
   817  			oldVolume:         validCSIVolume,
   818  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
   819  		},
   820  		"csi-expansion-enabled-with-old-pv-secret": {
   821  			isExpectedFailure: true,
   822  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
   823  			newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{
   824  				Name:      "foo-secret",
   825  				Namespace: "default",
   826  			}, "controllerExpand"),
   827  		},
   828  		"csi-expansion-enabled-with-shortSecretRef": {
   829  			isExpectedFailure: false,
   830  			oldVolume:         validCSIVolume,
   831  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   832  		},
   833  		"csi-expansion-enabled-with-longSecretRef": {
   834  			isExpectedFailure: false, // updating controllerExpandSecretRef is allowed only from nil
   835  			oldVolume:         validCSIVolume,
   836  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   837  		},
   838  		"csi-expansion-enabled-from-shortSecretRef-to-shortSecretRef": {
   839  			isExpectedFailure: false,
   840  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   841  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   842  		},
   843  		"csi-expansion-enabled-from-shortSecretRef-to-longSecretRef": {
   844  			isExpectedFailure: true, // updating controllerExpandSecretRef is allowed only from nil
   845  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   846  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   847  		},
   848  		"csi-expansion-enabled-from-longSecretRef-to-longSecretRef": {
   849  			isExpectedFailure: false,
   850  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   851  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   852  		},
   853  		"csi-cntrlpublish-enabled-with-shortSecretRef": {
   854  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   855  			oldVolume:         validCSIVolume,
   856  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   857  		},
   858  		"csi-cntrlpublish-enabled-with-longSecretRef": {
   859  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   860  			oldVolume:         validCSIVolume,
   861  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   862  		},
   863  		"csi-cntrlpublish-enabled-from-shortSecretRef-to-shortSecretRef": {
   864  			isExpectedFailure: false,
   865  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   866  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   867  		},
   868  		"csi-cntrlpublish-enabled-from-shortSecretRef-to-longSecretRef": {
   869  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   870  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   871  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   872  		},
   873  		"csi-cntrlpublish-enabled-from-longSecretRef-to-longSecretRef": {
   874  			isExpectedFailure: false,
   875  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   876  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   877  		},
   878  		"csi-nodepublish-enabled-with-shortSecretRef": {
   879  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   880  			oldVolume:         validCSIVolume,
   881  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   882  		},
   883  		"csi-nodepublish-enabled-with-longSecretRef": {
   884  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   885  			oldVolume:         validCSIVolume,
   886  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   887  		},
   888  		"csi-nodepublish-enabled-from-shortSecretRef-to-shortSecretRef": {
   889  			isExpectedFailure: false,
   890  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   891  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   892  		},
   893  		"csi-nodepublish-enabled-from-shortSecretRef-to-longSecretRef": {
   894  			isExpectedFailure: true,
   895  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   896  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   897  		},
   898  		"csi-nodepublish-enabled-from-longSecretRef-to-longSecretRef": {
   899  			isExpectedFailure: false,
   900  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   901  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   902  		},
   903  		"csi-nodestage-enabled-with-shortSecretRef": {
   904  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   905  			oldVolume:         validCSIVolume,
   906  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   907  		},
   908  		"csi-nodestage-enabled-with-longSecretRef": {
   909  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   910  			oldVolume:         validCSIVolume,
   911  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   912  		},
   913  		"csi-nodestage-enabled-from-shortSecretRef-to-longSecretRef": {
   914  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   915  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   916  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   917  		},
   918  
   919  		// At present, there is no validation exist for nodeStage secretRef in
   920  		// ValidatePersistentVolumeSpec->validateCSIPersistentVolumeSource, due to that, below
   921  		// checks/validations pass!
   922  
   923  		"csi-nodestage-enabled-from-invalidSecretRef-to-invalidSecretRef": {
   924  			isExpectedFailure: false,
   925  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
   926  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
   927  		},
   928  		"csi-nodestage-enabled-from-invalidSecretRefmissingname-to-invalidSecretRefmissingname": {
   929  			isExpectedFailure: false,
   930  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
   931  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
   932  		},
   933  		"csi-nodestage-enabled-from-invalidSecretRefmissingnamespace-to-invalidSecretRefmissingnamespace": {
   934  			isExpectedFailure: false,
   935  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
   936  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
   937  		},
   938  		"csi-nodestage-enabled-from-shortSecretRef-to-shortSecretRef": {
   939  			isExpectedFailure: false,
   940  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   941  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   942  		},
   943  		"csi-nodestage-enabled-from-longSecretRef-to-longSecretRef": {
   944  			isExpectedFailure: false,
   945  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   946  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   947  		},
   948  	}
   949  	for name, scenario := range scenarios {
   950  		opts := ValidationOptionsForPersistentVolume(scenario.newVolume, scenario.oldVolume)
   951  		errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume, opts)
   952  		if len(errs) == 0 && scenario.isExpectedFailure {
   953  			t.Errorf("Unexpected success for scenario: %s", name)
   954  		}
   955  		if len(errs) > 0 && !scenario.isExpectedFailure {
   956  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   957  		}
   958  	}
   959  }
   960  
   961  func TestValidationOptionsForPersistentVolume(t *testing.T) {
   962  	tests := map[string]struct {
   963  		oldPv                       *core.PersistentVolume
   964  		enableVolumeAttributesClass bool
   965  		expectValidationOpts        PersistentVolumeSpecValidationOptions
   966  	}{
   967  		"nil old pv": {
   968  			oldPv:                nil,
   969  			expectValidationOpts: PersistentVolumeSpecValidationOptions{},
   970  		},
   971  		"nil old pv and feature-gate VolumeAttrributesClass is on": {
   972  			oldPv:                       nil,
   973  			enableVolumeAttributesClass: true,
   974  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   975  		},
   976  		"nil old pv and feature-gate VolumeAttrributesClass is off": {
   977  			oldPv:                       nil,
   978  			enableVolumeAttributesClass: false,
   979  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: false},
   980  		},
   981  		"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is on": {
   982  			oldPv: &core.PersistentVolume{
   983  				Spec: core.PersistentVolumeSpec{
   984  					VolumeAttributesClassName: ptr.To("foo"),
   985  				},
   986  			},
   987  			enableVolumeAttributesClass: true,
   988  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   989  		},
   990  		"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is off": {
   991  			oldPv: &core.PersistentVolume{
   992  				Spec: core.PersistentVolumeSpec{
   993  					VolumeAttributesClassName: ptr.To("foo"),
   994  				},
   995  			},
   996  			enableVolumeAttributesClass: false,
   997  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   998  		},
   999  	}
  1000  
  1001  	for name, tc := range tests {
  1002  		t.Run(name, func(t *testing.T) {
  1003  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)
  1004  
  1005  			opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv)
  1006  			if opts != tc.expectValidationOpts {
  1007  				t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
  1008  			}
  1009  		})
  1010  	}
  1011  }
  1012  
  1013  func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference, secretfield string) *core.PersistentVolume {
  1014  	pvCopy := pv.DeepCopy()
  1015  	switch secretfield {
  1016  	case "controllerExpand":
  1017  		pvCopy.Spec.CSI.ControllerExpandSecretRef = secret
  1018  	case "controllerPublish":
  1019  		pvCopy.Spec.CSI.ControllerPublishSecretRef = secret
  1020  	case "nodePublish":
  1021  		pvCopy.Spec.CSI.NodePublishSecretRef = secret
  1022  	case "nodeStage":
  1023  		pvCopy.Spec.CSI.NodeStageSecretRef = secret
  1024  	default:
  1025  		panic("unknown string")
  1026  	}
  1027  
  1028  	return pvCopy
  1029  }
  1030  
  1031  func pvcWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaim {
  1032  	return &core.PersistentVolumeClaim{
  1033  		Spec: core.PersistentVolumeClaimSpec{
  1034  			VolumeAttributesClassName: vacName,
  1035  		},
  1036  	}
  1037  }
  1038  
  1039  func pvcWithDataSource(dataSource *core.TypedLocalObjectReference) *core.PersistentVolumeClaim {
  1040  	return &core.PersistentVolumeClaim{
  1041  		Spec: core.PersistentVolumeClaimSpec{
  1042  			DataSource: dataSource,
  1043  		},
  1044  	}
  1045  }
  1046  func pvcWithDataSourceRef(ref *core.TypedObjectReference) *core.PersistentVolumeClaim {
  1047  	return &core.PersistentVolumeClaim{
  1048  		Spec: core.PersistentVolumeClaimSpec{
  1049  			DataSourceRef: ref,
  1050  		},
  1051  	}
  1052  }
  1053  
  1054  func pvcTemplateWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimTemplate {
  1055  	return &core.PersistentVolumeClaimTemplate{
  1056  		Spec: core.PersistentVolumeClaimSpec{
  1057  			VolumeAttributesClassName: vacName,
  1058  		},
  1059  	}
  1060  }
  1061  
  1062  func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
  1063  	return core.PersistentVolumeSpec{
  1064  		Capacity: core.ResourceList{
  1065  			core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1066  		},
  1067  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1068  		PersistentVolumeSource: core.PersistentVolumeSource{
  1069  			Local: &core.LocalVolumeSource{
  1070  				Path: path,
  1071  			},
  1072  		},
  1073  		NodeAffinity:     affinity,
  1074  		StorageClassName: "test-storage-class",
  1075  	}
  1076  }
  1077  
  1078  func TestValidateLocalVolumes(t *testing.T) {
  1079  	scenarios := map[string]struct {
  1080  		isExpectedFailure bool
  1081  		volume            *core.PersistentVolume
  1082  	}{
  1083  		"alpha invalid local volume nil annotations": {
  1084  			isExpectedFailure: true,
  1085  			volume: testVolume(
  1086  				"invalid-local-volume-nil-annotations",
  1087  				"",
  1088  				testLocalVolume("/foo", nil)),
  1089  		},
  1090  		"valid local volume": {
  1091  			isExpectedFailure: false,
  1092  			volume: testVolume("valid-local-volume", "",
  1093  				testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
  1094  		},
  1095  		"invalid local volume no node affinity": {
  1096  			isExpectedFailure: true,
  1097  			volume: testVolume("invalid-local-volume-no-node-affinity", "",
  1098  				testLocalVolume("/foo", nil)),
  1099  		},
  1100  		"invalid local volume empty path": {
  1101  			isExpectedFailure: true,
  1102  			volume: testVolume("invalid-local-volume-empty-path", "",
  1103  				testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))),
  1104  		},
  1105  		"invalid-local-volume-backsteps": {
  1106  			isExpectedFailure: true,
  1107  			volume: testVolume("foo", "",
  1108  				testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))),
  1109  		},
  1110  		"valid-local-volume-relative-path": {
  1111  			isExpectedFailure: false,
  1112  			volume: testVolume("foo", "",
  1113  				testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))),
  1114  		},
  1115  	}
  1116  
  1117  	for name, scenario := range scenarios {
  1118  		opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
  1119  		errs := ValidatePersistentVolume(scenario.volume, opts)
  1120  		if len(errs) == 0 && scenario.isExpectedFailure {
  1121  			t.Errorf("Unexpected success for scenario: %s", name)
  1122  		}
  1123  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1124  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1125  		}
  1126  	}
  1127  }
  1128  
  1129  func testVolumeWithVolumeAttributesClass(vacName *string) *core.PersistentVolume {
  1130  	return testVolume("test-volume-with-volume-attributes-class", "",
  1131  		core.PersistentVolumeSpec{
  1132  			Capacity: core.ResourceList{
  1133  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1134  			},
  1135  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1136  			PersistentVolumeSource: core.PersistentVolumeSource{
  1137  				CSI: &core.CSIPersistentVolumeSource{
  1138  					Driver:       "test-driver",
  1139  					VolumeHandle: "test-123",
  1140  				},
  1141  			},
  1142  			StorageClassName:          "test-storage-class",
  1143  			VolumeAttributesClassName: vacName,
  1144  		})
  1145  }
  1146  
  1147  func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
  1148  	return testVolume("test-affinity-volume", "",
  1149  		core.PersistentVolumeSpec{
  1150  			Capacity: core.ResourceList{
  1151  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1152  			},
  1153  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1154  			PersistentVolumeSource: core.PersistentVolumeSource{
  1155  				GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
  1156  					PDName: "foo",
  1157  				},
  1158  			},
  1159  			StorageClassName: "test-storage-class",
  1160  			NodeAffinity:     affinity,
  1161  		})
  1162  }
  1163  
  1164  func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity {
  1165  	return &core.VolumeNodeAffinity{
  1166  		Required: &core.NodeSelector{
  1167  			NodeSelectorTerms: []core.NodeSelectorTerm{{
  1168  				MatchExpressions: []core.NodeSelectorRequirement{{
  1169  					Key:      key,
  1170  					Operator: core.NodeSelectorOpIn,
  1171  					Values:   []string{value},
  1172  				}},
  1173  			}},
  1174  		},
  1175  	}
  1176  }
  1177  
  1178  func multipleVolumeNodeAffinity(terms [][]topologyPair) *core.VolumeNodeAffinity {
  1179  	nodeSelectorTerms := []core.NodeSelectorTerm{}
  1180  	for _, term := range terms {
  1181  		matchExpressions := []core.NodeSelectorRequirement{}
  1182  		for _, topology := range term {
  1183  			matchExpressions = append(matchExpressions, core.NodeSelectorRequirement{
  1184  				Key:      topology.key,
  1185  				Operator: core.NodeSelectorOpIn,
  1186  				Values:   []string{topology.value},
  1187  			})
  1188  		}
  1189  		nodeSelectorTerms = append(nodeSelectorTerms, core.NodeSelectorTerm{
  1190  			MatchExpressions: matchExpressions,
  1191  		})
  1192  	}
  1193  
  1194  	return &core.VolumeNodeAffinity{
  1195  		Required: &core.NodeSelector{
  1196  			NodeSelectorTerms: nodeSelectorTerms,
  1197  		},
  1198  	}
  1199  }
  1200  
  1201  func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
  1202  	scenarios := map[string]struct {
  1203  		isExpectedFailure bool
  1204  		oldPV             *core.PersistentVolume
  1205  		newPV             *core.PersistentVolume
  1206  	}{
  1207  		"nil-nothing-changed": {
  1208  			isExpectedFailure: false,
  1209  			oldPV:             testVolumeWithNodeAffinity(nil),
  1210  			newPV:             testVolumeWithNodeAffinity(nil),
  1211  		},
  1212  		"affinity-nothing-changed": {
  1213  			isExpectedFailure: false,
  1214  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1215  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1216  		},
  1217  		"affinity-changed": {
  1218  			isExpectedFailure: true,
  1219  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1220  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")),
  1221  		},
  1222  		"affinity-non-beta-label-changed": {
  1223  			isExpectedFailure: true,
  1224  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1225  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo2", "bar")),
  1226  		},
  1227  		"affinity-zone-beta-unchanged": {
  1228  			isExpectedFailure: false,
  1229  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1230  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1231  		},
  1232  		"affinity-zone-beta-label-to-GA": {
  1233  			isExpectedFailure: false,
  1234  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1235  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
  1236  		},
  1237  		"affinity-zone-beta-label-to-non-GA": {
  1238  			isExpectedFailure: true,
  1239  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1240  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1241  		},
  1242  		"affinity-zone-GA-label-changed": {
  1243  			isExpectedFailure: true,
  1244  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
  1245  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1246  		},
  1247  		"affinity-region-beta-unchanged": {
  1248  			isExpectedFailure: false,
  1249  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1250  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1251  		},
  1252  		"affinity-region-beta-label-to-GA": {
  1253  			isExpectedFailure: false,
  1254  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1255  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
  1256  		},
  1257  		"affinity-region-beta-label-to-non-GA": {
  1258  			isExpectedFailure: true,
  1259  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1260  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1261  		},
  1262  		"affinity-region-GA-label-changed": {
  1263  			isExpectedFailure: true,
  1264  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
  1265  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1266  		},
  1267  		"affinity-os-beta-label-unchanged": {
  1268  			isExpectedFailure: false,
  1269  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1270  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1271  		},
  1272  		"affinity-os-beta-label-to-GA": {
  1273  			isExpectedFailure: false,
  1274  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1275  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
  1276  		},
  1277  		"affinity-os-beta-label-to-non-GA": {
  1278  			isExpectedFailure: true,
  1279  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1280  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1281  		},
  1282  		"affinity-os-GA-label-changed": {
  1283  			isExpectedFailure: true,
  1284  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
  1285  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1286  		},
  1287  		"affinity-arch-beta-label-unchanged": {
  1288  			isExpectedFailure: false,
  1289  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1290  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1291  		},
  1292  		"affinity-arch-beta-label-to-GA": {
  1293  			isExpectedFailure: false,
  1294  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1295  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
  1296  		},
  1297  		"affinity-arch-beta-label-to-non-GA": {
  1298  			isExpectedFailure: true,
  1299  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1300  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1301  		},
  1302  		"affinity-arch-GA-label-changed": {
  1303  			isExpectedFailure: true,
  1304  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
  1305  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1306  		},
  1307  		"affinity-instanceType-beta-label-unchanged": {
  1308  			isExpectedFailure: false,
  1309  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1310  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1311  		},
  1312  		"affinity-instanceType-beta-label-to-GA": {
  1313  			isExpectedFailure: false,
  1314  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1315  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
  1316  		},
  1317  		"affinity-instanceType-beta-label-to-non-GA": {
  1318  			isExpectedFailure: true,
  1319  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1320  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1321  		},
  1322  		"affinity-instanceType-GA-label-changed": {
  1323  			isExpectedFailure: true,
  1324  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
  1325  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1326  		},
  1327  		"affinity-same-terms-expressions-length-beta-to-GA-partially-changed": {
  1328  			isExpectedFailure: false,
  1329  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1330  				topologyPair{"foo", "bar"},
  1331  			}, {
  1332  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1333  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1334  			}, {
  1335  				topologyPair{kubeletapis.LabelOS, "bar"},
  1336  				topologyPair{kubeletapis.LabelArch, "bar"},
  1337  				topologyPair{v1.LabelInstanceType, "bar"},
  1338  			},
  1339  			})),
  1340  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1341  				topologyPair{"foo", "bar"},
  1342  			}, {
  1343  				topologyPair{v1.LabelTopologyZone, "bar"},
  1344  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1345  			}, {
  1346  				topologyPair{kubeletapis.LabelOS, "bar"},
  1347  				topologyPair{v1.LabelArchStable, "bar"},
  1348  				topologyPair{v1.LabelInstanceTypeStable, "bar"},
  1349  			},
  1350  			})),
  1351  		},
  1352  		"affinity-same-terms-expressions-length-beta-to-non-GA-partially-changed": {
  1353  			isExpectedFailure: true,
  1354  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1355  				topologyPair{"foo", "bar"},
  1356  			}, {
  1357  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1358  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1359  			},
  1360  			})),
  1361  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1362  				topologyPair{"foo", "bar"},
  1363  			}, {
  1364  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1365  				topologyPair{"foo", "bar"},
  1366  			},
  1367  			})),
  1368  		},
  1369  		"affinity-same-terms-expressions-length-GA-partially-changed": {
  1370  			isExpectedFailure: true,
  1371  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1372  				topologyPair{"foo", "bar"},
  1373  			}, {
  1374  				topologyPair{v1.LabelTopologyZone, "bar"},
  1375  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1376  				topologyPair{v1.LabelOSStable, "bar"},
  1377  			},
  1378  			})),
  1379  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1380  				topologyPair{"foo", "bar"},
  1381  			}, {
  1382  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1383  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1384  				topologyPair{v1.LabelOSStable, "bar"},
  1385  			},
  1386  			})),
  1387  		},
  1388  		"affinity-same-terms-expressions-length-beta-fully-changed": {
  1389  			isExpectedFailure: false,
  1390  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1391  				topologyPair{"foo", "bar"},
  1392  			}, {
  1393  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1394  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1395  			}, {
  1396  				topologyPair{kubeletapis.LabelOS, "bar"},
  1397  				topologyPair{kubeletapis.LabelArch, "bar"},
  1398  				topologyPair{v1.LabelInstanceType, "bar"},
  1399  			},
  1400  			})),
  1401  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1402  				topologyPair{"foo", "bar"},
  1403  			}, {
  1404  				topologyPair{v1.LabelTopologyZone, "bar"},
  1405  				topologyPair{v1.LabelTopologyRegion, "bar"},
  1406  			}, {
  1407  				topologyPair{v1.LabelOSStable, "bar"},
  1408  				topologyPair{v1.LabelArchStable, "bar"},
  1409  				topologyPair{v1.LabelInstanceTypeStable, "bar"},
  1410  			},
  1411  			})),
  1412  		},
  1413  		"affinity-same-terms-expressions-length-beta-GA-mixed-fully-changed": {
  1414  			isExpectedFailure: true,
  1415  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1416  				topologyPair{"foo", "bar"},
  1417  			}, {
  1418  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1419  				topologyPair{v1.LabelTopologyZone, "bar"},
  1420  			},
  1421  			})),
  1422  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1423  				topologyPair{"foo", "bar"},
  1424  			}, {
  1425  				topologyPair{v1.LabelTopologyZone, "bar"},
  1426  				topologyPair{v1.LabelFailureDomainBetaZone, "bar2"},
  1427  			},
  1428  			})),
  1429  		},
  1430  		"affinity-same-terms-length-different-expressions-length-beta-changed": {
  1431  			isExpectedFailure: true,
  1432  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1433  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1434  			},
  1435  			})),
  1436  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1437  				topologyPair{v1.LabelTopologyZone, "bar"},
  1438  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1439  			},
  1440  			})),
  1441  		},
  1442  		"affinity-different-terms-expressions-length-beta-changed": {
  1443  			isExpectedFailure: true,
  1444  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1445  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1446  			},
  1447  			})),
  1448  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1449  				topologyPair{v1.LabelTopologyZone, "bar"},
  1450  			}, {
  1451  				topologyPair{v1.LabelArchStable, "bar"},
  1452  			},
  1453  			})),
  1454  		},
  1455  		"nil-to-obj": {
  1456  			isExpectedFailure: false,
  1457  			oldPV:             testVolumeWithNodeAffinity(nil),
  1458  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1459  		},
  1460  		"obj-to-nil": {
  1461  			isExpectedFailure: true,
  1462  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1463  			newPV:             testVolumeWithNodeAffinity(nil),
  1464  		},
  1465  	}
  1466  
  1467  	for name, scenario := range scenarios {
  1468  		originalNewPV := scenario.newPV.DeepCopy()
  1469  		originalOldPV := scenario.oldPV.DeepCopy()
  1470  		opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  1471  		errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  1472  		if len(errs) == 0 && scenario.isExpectedFailure {
  1473  			t.Errorf("Unexpected success for scenario: %s", name)
  1474  		}
  1475  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1476  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1477  		}
  1478  		if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
  1479  			t.Errorf("newPV was modified: %s", diff)
  1480  		}
  1481  		if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
  1482  			t.Errorf("oldPV was modified: %s", diff)
  1483  		}
  1484  	}
  1485  }
  1486  
  1487  func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) {
  1488  	scenarios := map[string]struct {
  1489  		isExpectedFailure           bool
  1490  		enableVolumeAttributesClass bool
  1491  		oldPV                       *core.PersistentVolume
  1492  		newPV                       *core.PersistentVolume
  1493  	}{
  1494  		"nil-nothing-changed": {
  1495  			isExpectedFailure:           false,
  1496  			enableVolumeAttributesClass: true,
  1497  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1498  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1499  		},
  1500  		"vac-nothing-changed": {
  1501  			isExpectedFailure:           false,
  1502  			enableVolumeAttributesClass: true,
  1503  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1504  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1505  		},
  1506  		"vac-changed": {
  1507  			isExpectedFailure:           false,
  1508  			enableVolumeAttributesClass: true,
  1509  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1510  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("bar")),
  1511  		},
  1512  		"nil-to-string": {
  1513  			isExpectedFailure:           false,
  1514  			enableVolumeAttributesClass: true,
  1515  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1516  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1517  		},
  1518  		"nil-to-empty-string": {
  1519  			isExpectedFailure:           true,
  1520  			enableVolumeAttributesClass: true,
  1521  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1522  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1523  		},
  1524  		"string-to-nil": {
  1525  			isExpectedFailure:           true,
  1526  			enableVolumeAttributesClass: true,
  1527  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1528  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1529  		},
  1530  		"string-to-empty-string": {
  1531  			isExpectedFailure:           true,
  1532  			enableVolumeAttributesClass: true,
  1533  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1534  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1535  		},
  1536  		"vac-nothing-changed-when-feature-gate-is-off": {
  1537  			isExpectedFailure:           false,
  1538  			enableVolumeAttributesClass: false,
  1539  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1540  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1541  		},
  1542  		"vac-changed-when-feature-gate-is-off": {
  1543  			isExpectedFailure:           true,
  1544  			enableVolumeAttributesClass: false,
  1545  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1546  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("bar")),
  1547  		},
  1548  		"nil-to-string-when-feature-gate-is-off": {
  1549  			isExpectedFailure:           true,
  1550  			enableVolumeAttributesClass: false,
  1551  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1552  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1553  		},
  1554  		"nil-to-empty-string-when-feature-gate-is-off": {
  1555  			isExpectedFailure:           true,
  1556  			enableVolumeAttributesClass: false,
  1557  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1558  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1559  		},
  1560  		"string-to-nil-when-feature-gate-is-off": {
  1561  			isExpectedFailure:           true,
  1562  			enableVolumeAttributesClass: false,
  1563  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1564  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1565  		},
  1566  		"string-to-empty-string-when-feature-gate-is-off": {
  1567  			isExpectedFailure:           true,
  1568  			enableVolumeAttributesClass: false,
  1569  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1570  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1571  		},
  1572  	}
  1573  
  1574  	for name, scenario := range scenarios {
  1575  		featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)
  1576  
  1577  		originalNewPV := scenario.newPV.DeepCopy()
  1578  		originalOldPV := scenario.oldPV.DeepCopy()
  1579  		opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  1580  		errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  1581  		if len(errs) == 0 && scenario.isExpectedFailure {
  1582  			t.Errorf("Unexpected success for scenario: %s", name)
  1583  		}
  1584  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1585  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1586  		}
  1587  		if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
  1588  			t.Errorf("newPV was modified: %s", diff)
  1589  		}
  1590  		if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
  1591  			t.Errorf("oldPV was modified: %s", diff)
  1592  		}
  1593  	}
  1594  }
  1595  
  1596  func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1597  	return &core.PersistentVolumeClaim{
  1598  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
  1599  		Spec:       spec,
  1600  	}
  1601  }
  1602  
  1603  func testVolumeClaimWithStatus(
  1604  	name, namespace string,
  1605  	spec core.PersistentVolumeClaimSpec,
  1606  	status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim {
  1607  	return &core.PersistentVolumeClaim{
  1608  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
  1609  		Spec:       spec,
  1610  		Status:     status,
  1611  	}
  1612  }
  1613  
  1614  func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1615  	annotations := map[string]string{
  1616  		v1.BetaStorageClassAnnotation: annval,
  1617  	}
  1618  
  1619  	return &core.PersistentVolumeClaim{
  1620  		ObjectMeta: metav1.ObjectMeta{
  1621  			Name:        name,
  1622  			Namespace:   namespace,
  1623  			Annotations: annotations,
  1624  		},
  1625  		Spec: spec,
  1626  	}
  1627  }
  1628  
  1629  func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1630  	annotations := map[string]string{
  1631  		ann: annval,
  1632  	}
  1633  
  1634  	return &core.PersistentVolumeClaim{
  1635  		ObjectMeta: metav1.ObjectMeta{
  1636  			Name:        name,
  1637  			Namespace:   namespace,
  1638  			Annotations: annotations,
  1639  		},
  1640  		Spec: spec,
  1641  	}
  1642  }
  1643  
  1644  func testVolumeClaimStorageClassInSpec(name, namespace, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1645  	spec.StorageClassName = &scName
  1646  	return &core.PersistentVolumeClaim{
  1647  		ObjectMeta: metav1.ObjectMeta{
  1648  			Name:      name,
  1649  			Namespace: namespace,
  1650  		},
  1651  		Spec: spec,
  1652  	}
  1653  }
  1654  
  1655  func testVolumeClaimStorageClassNilInSpec(name, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1656  	spec.StorageClassName = nil
  1657  	return &core.PersistentVolumeClaim{
  1658  		ObjectMeta: metav1.ObjectMeta{
  1659  			Name:      name,
  1660  			Namespace: namespace,
  1661  		},
  1662  		Spec: spec,
  1663  	}
  1664  }
  1665  
  1666  func testVolumeSnapshotDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
  1667  	scName := "csi-plugin"
  1668  	dataSourceInSpec := core.PersistentVolumeClaimSpec{
  1669  		AccessModes: []core.PersistentVolumeAccessMode{
  1670  			core.ReadOnlyMany,
  1671  		},
  1672  		Resources: core.VolumeResourceRequirements{
  1673  			Requests: core.ResourceList{
  1674  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1675  			},
  1676  		},
  1677  		StorageClassName: &scName,
  1678  		DataSource: &core.TypedLocalObjectReference{
  1679  			APIGroup: &apiGroup,
  1680  			Kind:     kind,
  1681  			Name:     name,
  1682  		},
  1683  	}
  1684  
  1685  	return &dataSourceInSpec
  1686  }
  1687  
  1688  func TestAlphaVolumeSnapshotDataSource(t *testing.T) {
  1689  	successTestCases := []core.PersistentVolumeClaimSpec{
  1690  		*testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
  1691  	}
  1692  	failedTestCases := []core.PersistentVolumeClaimSpec{
  1693  		*testVolumeSnapshotDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
  1694  		*testVolumeSnapshotDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
  1695  	}
  1696  
  1697  	for _, tc := range successTestCases {
  1698  		opts := PersistentVolumeClaimSpecValidationOptions{}
  1699  		if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) != 0 {
  1700  			t.Errorf("expected success: %v", errs)
  1701  		}
  1702  	}
  1703  	for _, tc := range failedTestCases {
  1704  		opts := PersistentVolumeClaimSpecValidationOptions{}
  1705  		if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) == 0 {
  1706  			t.Errorf("expected failure: %v", errs)
  1707  		}
  1708  	}
  1709  }
  1710  
  1711  func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1712  	spec.StorageClassName = &scName
  1713  	return &core.PersistentVolumeClaim{
  1714  		ObjectMeta: metav1.ObjectMeta{
  1715  			Name:        name,
  1716  			Namespace:   namespace,
  1717  			Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
  1718  		},
  1719  		Spec: spec,
  1720  	}
  1721  }
  1722  
  1723  func testVolumeClaimStorageClassInAnnotationAndNilInSpec(name, namespace, scNameInAnn string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1724  	spec.StorageClassName = nil
  1725  	return &core.PersistentVolumeClaim{
  1726  		ObjectMeta: metav1.ObjectMeta{
  1727  			Name:        name,
  1728  			Namespace:   namespace,
  1729  			Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
  1730  		},
  1731  		Spec: spec,
  1732  	}
  1733  }
  1734  
  1735  func testValidatePVC(t *testing.T, ephemeral bool) {
  1736  	invalidClassName := "-invalid-"
  1737  	validClassName := "valid"
  1738  	invalidAPIGroup := "^invalid"
  1739  	invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
  1740  	validMode := core.PersistentVolumeFilesystem
  1741  	goodName := "foo"
  1742  	goodNS := "ns"
  1743  	if ephemeral {
  1744  		// Must be empty for ephemeral inline volumes.
  1745  		goodName = ""
  1746  		goodNS = ""
  1747  	}
  1748  	goodClaimSpec := core.PersistentVolumeClaimSpec{
  1749  		Selector: &metav1.LabelSelector{
  1750  			MatchExpressions: []metav1.LabelSelectorRequirement{{
  1751  				Key:      "key2",
  1752  				Operator: "Exists",
  1753  			}},
  1754  		},
  1755  		AccessModes: []core.PersistentVolumeAccessMode{
  1756  			core.ReadWriteOnce,
  1757  			core.ReadOnlyMany,
  1758  		},
  1759  		Resources: core.VolumeResourceRequirements{
  1760  			Requests: core.ResourceList{
  1761  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1762  			},
  1763  		},
  1764  		StorageClassName: &validClassName,
  1765  		VolumeMode:       &validMode,
  1766  	}
  1767  	now := metav1.Now()
  1768  	ten := int64(10)
  1769  
  1770  	scenarios := map[string]struct {
  1771  		isExpectedFailure           bool
  1772  		enableVolumeAttributesClass bool
  1773  		claim                       *core.PersistentVolumeClaim
  1774  	}{
  1775  		"good-claim": {
  1776  			isExpectedFailure: false,
  1777  			claim:             testVolumeClaim(goodName, goodNS, goodClaimSpec),
  1778  		},
  1779  		"missing-name": {
  1780  			isExpectedFailure: !ephemeral,
  1781  			claim:             testVolumeClaim("", goodNS, goodClaimSpec),
  1782  		},
  1783  		"missing-namespace": {
  1784  			isExpectedFailure: !ephemeral,
  1785  			claim:             testVolumeClaim(goodName, "", goodClaimSpec),
  1786  		},
  1787  		"with-generate-name": {
  1788  			isExpectedFailure: ephemeral,
  1789  			claim: func() *core.PersistentVolumeClaim {
  1790  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1791  				claim.GenerateName = "pvc-"
  1792  				return claim
  1793  			}(),
  1794  		},
  1795  		"with-uid": {
  1796  			isExpectedFailure: ephemeral,
  1797  			claim: func() *core.PersistentVolumeClaim {
  1798  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1799  				claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
  1800  				return claim
  1801  			}(),
  1802  		},
  1803  		"with-resource-version": {
  1804  			isExpectedFailure: ephemeral,
  1805  			claim: func() *core.PersistentVolumeClaim {
  1806  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1807  				claim.ResourceVersion = "1"
  1808  				return claim
  1809  			}(),
  1810  		},
  1811  		"with-generation": {
  1812  			isExpectedFailure: ephemeral,
  1813  			claim: func() *core.PersistentVolumeClaim {
  1814  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1815  				claim.Generation = 100
  1816  				return claim
  1817  			}(),
  1818  		},
  1819  		"with-creation-timestamp": {
  1820  			isExpectedFailure: ephemeral,
  1821  			claim: func() *core.PersistentVolumeClaim {
  1822  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1823  				claim.CreationTimestamp = now
  1824  				return claim
  1825  			}(),
  1826  		},
  1827  		"with-deletion-grace-period-seconds": {
  1828  			isExpectedFailure: ephemeral,
  1829  			claim: func() *core.PersistentVolumeClaim {
  1830  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1831  				claim.DeletionGracePeriodSeconds = &ten
  1832  				return claim
  1833  			}(),
  1834  		},
  1835  		"with-owner-references": {
  1836  			isExpectedFailure: ephemeral,
  1837  			claim: func() *core.PersistentVolumeClaim {
  1838  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1839  				claim.OwnerReferences = []metav1.OwnerReference{{
  1840  					APIVersion: "v1",
  1841  					Kind:       "pod",
  1842  					Name:       "foo",
  1843  					UID:        "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
  1844  				},
  1845  				}
  1846  				return claim
  1847  			}(),
  1848  		},
  1849  		"with-finalizers": {
  1850  			isExpectedFailure: ephemeral,
  1851  			claim: func() *core.PersistentVolumeClaim {
  1852  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1853  				claim.Finalizers = []string{
  1854  					"example.com/foo",
  1855  				}
  1856  				return claim
  1857  			}(),
  1858  		},
  1859  		"with-managed-fields": {
  1860  			isExpectedFailure: ephemeral,
  1861  			claim: func() *core.PersistentVolumeClaim {
  1862  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1863  				claim.ManagedFields = []metav1.ManagedFieldsEntry{{
  1864  					FieldsType: "FieldsV1",
  1865  					Operation:  "Apply",
  1866  					APIVersion: "apps/v1",
  1867  					Manager:    "foo",
  1868  				},
  1869  				}
  1870  				return claim
  1871  			}(),
  1872  		},
  1873  		"with-good-labels": {
  1874  			claim: func() *core.PersistentVolumeClaim {
  1875  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1876  				claim.Labels = map[string]string{
  1877  					"apps.kubernetes.io/name": "test",
  1878  				}
  1879  				return claim
  1880  			}(),
  1881  		},
  1882  		"with-bad-labels": {
  1883  			isExpectedFailure: true,
  1884  			claim: func() *core.PersistentVolumeClaim {
  1885  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1886  				claim.Labels = map[string]string{
  1887  					"hello-world": "hyphen not allowed",
  1888  				}
  1889  				return claim
  1890  			}(),
  1891  		},
  1892  		"with-good-annotations": {
  1893  			claim: func() *core.PersistentVolumeClaim {
  1894  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1895  				claim.Labels = map[string]string{
  1896  					"foo": "bar",
  1897  				}
  1898  				return claim
  1899  			}(),
  1900  		},
  1901  		"with-bad-annotations": {
  1902  			isExpectedFailure: true,
  1903  			claim: func() *core.PersistentVolumeClaim {
  1904  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1905  				claim.Labels = map[string]string{
  1906  					"hello-world": "hyphen not allowed",
  1907  				}
  1908  				return claim
  1909  			}(),
  1910  		},
  1911  		"with-read-write-once-pod": {
  1912  			isExpectedFailure: false,
  1913  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1914  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
  1915  				Resources: core.VolumeResourceRequirements{
  1916  					Requests: core.ResourceList{
  1917  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1918  					},
  1919  				},
  1920  			}),
  1921  		},
  1922  		"with-read-write-once-pod-and-others": {
  1923  			isExpectedFailure: true,
  1924  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1925  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
  1926  				Resources: core.VolumeResourceRequirements{
  1927  					Requests: core.ResourceList{
  1928  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1929  					},
  1930  				},
  1931  			}),
  1932  		},
  1933  		"invalid-claim-zero-capacity": {
  1934  			isExpectedFailure: true,
  1935  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1936  				Selector: &metav1.LabelSelector{
  1937  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  1938  						Key:      "key2",
  1939  						Operator: "Exists",
  1940  					}},
  1941  				},
  1942  				AccessModes: []core.PersistentVolumeAccessMode{
  1943  					core.ReadWriteOnce,
  1944  					core.ReadOnlyMany,
  1945  				},
  1946  				Resources: core.VolumeResourceRequirements{
  1947  					Requests: core.ResourceList{
  1948  						core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
  1949  					},
  1950  				},
  1951  				StorageClassName: &validClassName,
  1952  			}),
  1953  		},
  1954  		"invalid-label-selector": {
  1955  			isExpectedFailure: true,
  1956  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1957  				Selector: &metav1.LabelSelector{
  1958  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  1959  						Key:      "key2",
  1960  						Operator: "InvalidOp",
  1961  						Values:   []string{"value1", "value2"},
  1962  					}},
  1963  				},
  1964  				AccessModes: []core.PersistentVolumeAccessMode{
  1965  					core.ReadWriteOnce,
  1966  					core.ReadOnlyMany,
  1967  				},
  1968  				Resources: core.VolumeResourceRequirements{
  1969  					Requests: core.ResourceList{
  1970  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1971  					},
  1972  				},
  1973  			}),
  1974  		},
  1975  		"invalid-accessmode": {
  1976  			isExpectedFailure: true,
  1977  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1978  				AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
  1979  				Resources: core.VolumeResourceRequirements{
  1980  					Requests: core.ResourceList{
  1981  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1982  					},
  1983  				},
  1984  			}),
  1985  		},
  1986  		"no-access-modes": {
  1987  			isExpectedFailure: true,
  1988  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1989  				Resources: core.VolumeResourceRequirements{
  1990  					Requests: core.ResourceList{
  1991  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1992  					},
  1993  				},
  1994  			}),
  1995  		},
  1996  		"no-resource-requests": {
  1997  			isExpectedFailure: true,
  1998  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1999  				AccessModes: []core.PersistentVolumeAccessMode{
  2000  					core.ReadWriteOnce,
  2001  				},
  2002  			}),
  2003  		},
  2004  		"invalid-resource-requests": {
  2005  			isExpectedFailure: true,
  2006  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2007  				AccessModes: []core.PersistentVolumeAccessMode{
  2008  					core.ReadWriteOnce,
  2009  				},
  2010  				Resources: core.VolumeResourceRequirements{
  2011  					Requests: core.ResourceList{
  2012  						core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  2013  					},
  2014  				},
  2015  			}),
  2016  		},
  2017  		"negative-storage-request": {
  2018  			isExpectedFailure: true,
  2019  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2020  				Selector: &metav1.LabelSelector{
  2021  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2022  						Key:      "key2",
  2023  						Operator: "Exists",
  2024  					}},
  2025  				},
  2026  				AccessModes: []core.PersistentVolumeAccessMode{
  2027  					core.ReadWriteOnce,
  2028  					core.ReadOnlyMany,
  2029  				},
  2030  				Resources: core.VolumeResourceRequirements{
  2031  					Requests: core.ResourceList{
  2032  						core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
  2033  					},
  2034  				},
  2035  			}),
  2036  		},
  2037  		"zero-storage-request": {
  2038  			isExpectedFailure: true,
  2039  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2040  				Selector: &metav1.LabelSelector{
  2041  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2042  						Key:      "key2",
  2043  						Operator: "Exists",
  2044  					}},
  2045  				},
  2046  				AccessModes: []core.PersistentVolumeAccessMode{
  2047  					core.ReadWriteOnce,
  2048  					core.ReadOnlyMany,
  2049  				},
  2050  				Resources: core.VolumeResourceRequirements{
  2051  					Requests: core.ResourceList{
  2052  						core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
  2053  					},
  2054  				},
  2055  			}),
  2056  		},
  2057  		"invalid-storage-class-name": {
  2058  			isExpectedFailure: true,
  2059  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2060  				Selector: &metav1.LabelSelector{
  2061  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2062  						Key:      "key2",
  2063  						Operator: "Exists",
  2064  					}},
  2065  				},
  2066  				AccessModes: []core.PersistentVolumeAccessMode{
  2067  					core.ReadWriteOnce,
  2068  					core.ReadOnlyMany,
  2069  				},
  2070  				Resources: core.VolumeResourceRequirements{
  2071  					Requests: core.ResourceList{
  2072  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2073  					},
  2074  				},
  2075  				StorageClassName: &invalidClassName,
  2076  			}),
  2077  		},
  2078  		"invalid-volume-mode": {
  2079  			isExpectedFailure: true,
  2080  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2081  				AccessModes: []core.PersistentVolumeAccessMode{
  2082  					core.ReadWriteOnce,
  2083  					core.ReadOnlyMany,
  2084  				},
  2085  				Resources: core.VolumeResourceRequirements{
  2086  					Requests: core.ResourceList{
  2087  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2088  					},
  2089  				},
  2090  				VolumeMode: &invalidMode,
  2091  			}),
  2092  		},
  2093  		"mismatch-data-source-and-ref": {
  2094  			isExpectedFailure: true,
  2095  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2096  				AccessModes: []core.PersistentVolumeAccessMode{
  2097  					core.ReadWriteOnce,
  2098  				},
  2099  				Resources: core.VolumeResourceRequirements{
  2100  					Requests: core.ResourceList{
  2101  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2102  					},
  2103  				},
  2104  				DataSource: &core.TypedLocalObjectReference{
  2105  					Kind: "PersistentVolumeClaim",
  2106  					Name: "pvc1",
  2107  				},
  2108  				DataSourceRef: &core.TypedObjectReference{
  2109  					Kind: "PersistentVolumeClaim",
  2110  					Name: "pvc2",
  2111  				},
  2112  			}),
  2113  		},
  2114  		"invaild-apigroup-in-data-source": {
  2115  			isExpectedFailure: true,
  2116  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2117  				AccessModes: []core.PersistentVolumeAccessMode{
  2118  					core.ReadWriteOnce,
  2119  				},
  2120  				Resources: core.VolumeResourceRequirements{
  2121  					Requests: core.ResourceList{
  2122  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2123  					},
  2124  				},
  2125  				DataSource: &core.TypedLocalObjectReference{
  2126  					APIGroup: &invalidAPIGroup,
  2127  					Kind:     "Foo",
  2128  					Name:     "foo1",
  2129  				},
  2130  			}),
  2131  		},
  2132  		"invaild-apigroup-in-data-source-ref": {
  2133  			isExpectedFailure: true,
  2134  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2135  				AccessModes: []core.PersistentVolumeAccessMode{
  2136  					core.ReadWriteOnce,
  2137  				},
  2138  				Resources: core.VolumeResourceRequirements{
  2139  					Requests: core.ResourceList{
  2140  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2141  					},
  2142  				},
  2143  				DataSourceRef: &core.TypedObjectReference{
  2144  					APIGroup: &invalidAPIGroup,
  2145  					Kind:     "Foo",
  2146  					Name:     "foo1",
  2147  				},
  2148  			}),
  2149  		},
  2150  		"invalid-volume-attributes-class-name": {
  2151  			isExpectedFailure:           true,
  2152  			enableVolumeAttributesClass: true,
  2153  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2154  				Selector: &metav1.LabelSelector{
  2155  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2156  						Key:      "key2",
  2157  						Operator: "Exists",
  2158  					}},
  2159  				},
  2160  				AccessModes: []core.PersistentVolumeAccessMode{
  2161  					core.ReadWriteOnce,
  2162  					core.ReadOnlyMany,
  2163  				},
  2164  				Resources: core.VolumeResourceRequirements{
  2165  					Requests: core.ResourceList{
  2166  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2167  					},
  2168  				},
  2169  				VolumeAttributesClassName: &invalidClassName,
  2170  			}),
  2171  		},
  2172  	}
  2173  
  2174  	for name, scenario := range scenarios {
  2175  		t.Run(name, func(t *testing.T) {
  2176  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)
  2177  
  2178  			var errs field.ErrorList
  2179  			if ephemeral {
  2180  				volumes := []core.Volume{{
  2181  					Name: "foo",
  2182  					VolumeSource: core.VolumeSource{
  2183  						Ephemeral: &core.EphemeralVolumeSource{
  2184  							VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  2185  								ObjectMeta: scenario.claim.ObjectMeta,
  2186  								Spec:       scenario.claim.Spec,
  2187  							},
  2188  						},
  2189  					},
  2190  				},
  2191  				}
  2192  				opts := PodValidationOptions{}
  2193  				_, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts)
  2194  			} else {
  2195  				opts := ValidationOptionsForPersistentVolumeClaim(scenario.claim, nil)
  2196  				errs = ValidatePersistentVolumeClaim(scenario.claim, opts)
  2197  			}
  2198  			if len(errs) == 0 && scenario.isExpectedFailure {
  2199  				t.Error("Unexpected success for scenario")
  2200  			}
  2201  			if len(errs) > 0 && !scenario.isExpectedFailure {
  2202  				t.Errorf("Unexpected failure: %+v", errs)
  2203  			}
  2204  		})
  2205  	}
  2206  }
  2207  
  2208  func TestValidatePersistentVolumeClaim(t *testing.T) {
  2209  	testValidatePVC(t, false)
  2210  }
  2211  
  2212  func TestValidateEphemeralVolume(t *testing.T) {
  2213  	testValidatePVC(t, true)
  2214  }
  2215  
  2216  func TestAlphaPVVolumeModeUpdate(t *testing.T) {
  2217  	block := core.PersistentVolumeBlock
  2218  	file := core.PersistentVolumeFilesystem
  2219  
  2220  	scenarios := map[string]struct {
  2221  		isExpectedFailure bool
  2222  		oldPV             *core.PersistentVolume
  2223  		newPV             *core.PersistentVolume
  2224  	}{
  2225  		"valid-update-volume-mode-block-to-block": {
  2226  			isExpectedFailure: false,
  2227  			oldPV:             createTestVolModePV(&block),
  2228  			newPV:             createTestVolModePV(&block),
  2229  		},
  2230  		"valid-update-volume-mode-file-to-file": {
  2231  			isExpectedFailure: false,
  2232  			oldPV:             createTestVolModePV(&file),
  2233  			newPV:             createTestVolModePV(&file),
  2234  		},
  2235  		"invalid-update-volume-mode-to-block": {
  2236  			isExpectedFailure: true,
  2237  			oldPV:             createTestVolModePV(&file),
  2238  			newPV:             createTestVolModePV(&block),
  2239  		},
  2240  		"invalid-update-volume-mode-to-file": {
  2241  			isExpectedFailure: true,
  2242  			oldPV:             createTestVolModePV(&block),
  2243  			newPV:             createTestVolModePV(&file),
  2244  		},
  2245  		"invalid-update-volume-mode-nil-to-file": {
  2246  			isExpectedFailure: true,
  2247  			oldPV:             createTestVolModePV(nil),
  2248  			newPV:             createTestVolModePV(&file),
  2249  		},
  2250  		"invalid-update-volume-mode-nil-to-block": {
  2251  			isExpectedFailure: true,
  2252  			oldPV:             createTestVolModePV(nil),
  2253  			newPV:             createTestVolModePV(&block),
  2254  		},
  2255  		"invalid-update-volume-mode-file-to-nil": {
  2256  			isExpectedFailure: true,
  2257  			oldPV:             createTestVolModePV(&file),
  2258  			newPV:             createTestVolModePV(nil),
  2259  		},
  2260  		"invalid-update-volume-mode-block-to-nil": {
  2261  			isExpectedFailure: true,
  2262  			oldPV:             createTestVolModePV(&block),
  2263  			newPV:             createTestVolModePV(nil),
  2264  		},
  2265  		"invalid-update-volume-mode-nil-to-nil": {
  2266  			isExpectedFailure: false,
  2267  			oldPV:             createTestVolModePV(nil),
  2268  			newPV:             createTestVolModePV(nil),
  2269  		},
  2270  		"invalid-update-volume-mode-empty-to-mode": {
  2271  			isExpectedFailure: true,
  2272  			oldPV:             createTestPV(),
  2273  			newPV:             createTestVolModePV(&block),
  2274  		},
  2275  	}
  2276  
  2277  	for name, scenario := range scenarios {
  2278  		t.Run(name, func(t *testing.T) {
  2279  			opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  2280  			// ensure we have a resource version specified for updates
  2281  			errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  2282  			if len(errs) == 0 && scenario.isExpectedFailure {
  2283  				t.Errorf("Unexpected success for scenario: %s", name)
  2284  			}
  2285  			if len(errs) > 0 && !scenario.isExpectedFailure {
  2286  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  2287  			}
  2288  		})
  2289  	}
  2290  }
  2291  
  2292  func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
  2293  	block := core.PersistentVolumeBlock
  2294  	file := core.PersistentVolumeFilesystem
  2295  	invaildAPIGroup := "^invalid"
  2296  
  2297  	validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2298  		AccessModes: []core.PersistentVolumeAccessMode{
  2299  			core.ReadWriteOnce,
  2300  			core.ReadOnlyMany,
  2301  		},
  2302  		Resources: core.VolumeResourceRequirements{
  2303  			Requests: core.ResourceList{
  2304  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2305  			},
  2306  		},
  2307  	}, core.PersistentVolumeClaimStatus{
  2308  		Phase: core.ClaimBound,
  2309  	})
  2310  
  2311  	validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2312  		AccessModes: []core.PersistentVolumeAccessMode{
  2313  			core.ReadOnlyMany,
  2314  		},
  2315  		Resources: core.VolumeResourceRequirements{
  2316  			Requests: core.ResourceList{
  2317  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2318  			},
  2319  		},
  2320  	})
  2321  	validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{
  2322  		AccessModes: []core.PersistentVolumeAccessMode{
  2323  			core.ReadOnlyMany,
  2324  		},
  2325  		Resources: core.VolumeResourceRequirements{
  2326  			Requests: core.ResourceList{
  2327  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2328  			},
  2329  		},
  2330  	})
  2331  	validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2332  		AccessModes: []core.PersistentVolumeAccessMode{
  2333  			core.ReadWriteOnce,
  2334  			core.ReadOnlyMany,
  2335  		},
  2336  		Resources: core.VolumeResourceRequirements{
  2337  			Requests: core.ResourceList{
  2338  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2339  			},
  2340  		},
  2341  		VolumeName: "volume",
  2342  	})
  2343  	invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2344  		AccessModes: []core.PersistentVolumeAccessMode{
  2345  			core.ReadWriteOnce,
  2346  			core.ReadOnlyMany,
  2347  		},
  2348  		Resources: core.VolumeResourceRequirements{
  2349  			Requests: core.ResourceList{
  2350  				core.ResourceName(core.ResourceStorage): resource.MustParse("20G"),
  2351  			},
  2352  		},
  2353  		VolumeName: "volume",
  2354  	})
  2355  	invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2356  		AccessModes: []core.PersistentVolumeAccessMode{
  2357  			core.ReadWriteOnce,
  2358  		},
  2359  		Resources: core.VolumeResourceRequirements{
  2360  			Requests: core.ResourceList{
  2361  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2362  			},
  2363  		},
  2364  		VolumeName: "volume",
  2365  	})
  2366  	validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2367  		AccessModes: []core.PersistentVolumeAccessMode{
  2368  			core.ReadWriteOnce,
  2369  		},
  2370  		VolumeMode: &file,
  2371  		Resources: core.VolumeResourceRequirements{
  2372  			Requests: core.ResourceList{
  2373  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2374  			},
  2375  		},
  2376  		VolumeName: "volume",
  2377  	})
  2378  	validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2379  		AccessModes: []core.PersistentVolumeAccessMode{
  2380  			core.ReadWriteOnce,
  2381  		},
  2382  		VolumeMode: &block,
  2383  		Resources: core.VolumeResourceRequirements{
  2384  			Requests: core.ResourceList{
  2385  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2386  			},
  2387  		},
  2388  		VolumeName: "volume",
  2389  	})
  2390  	invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2391  		AccessModes: []core.PersistentVolumeAccessMode{
  2392  			core.ReadWriteOnce,
  2393  		},
  2394  		VolumeMode: nil,
  2395  		Resources: core.VolumeResourceRequirements{
  2396  			Requests: core.ResourceList{
  2397  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2398  			},
  2399  		},
  2400  		VolumeName: "volume",
  2401  	})
  2402  	invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2403  		AccessModes: []core.PersistentVolumeAccessMode{
  2404  			core.ReadOnlyMany,
  2405  		},
  2406  		Resources: core.VolumeResourceRequirements{
  2407  			Requests: core.ResourceList{
  2408  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2409  			},
  2410  		},
  2411  		VolumeName: "volume",
  2412  	})
  2413  	validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2414  		AccessModes: []core.PersistentVolumeAccessMode{
  2415  			core.ReadOnlyMany,
  2416  		},
  2417  		Resources: core.VolumeResourceRequirements{
  2418  			Requests: core.ResourceList{
  2419  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2420  			},
  2421  		},
  2422  		VolumeName: "volume",
  2423  	})
  2424  	validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2425  		AccessModes: []core.PersistentVolumeAccessMode{
  2426  			core.ReadWriteOnce,
  2427  			core.ReadOnlyMany,
  2428  		},
  2429  		Resources: core.VolumeResourceRequirements{
  2430  			Requests: core.ResourceList{
  2431  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2432  			},
  2433  		},
  2434  		VolumeName: "volume",
  2435  	})
  2436  	validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2437  		AccessModes: []core.PersistentVolumeAccessMode{
  2438  			core.ReadWriteOnce,
  2439  			core.ReadOnlyMany,
  2440  		},
  2441  		Resources: core.VolumeResourceRequirements{
  2442  			Requests: core.ResourceList{
  2443  				core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
  2444  			},
  2445  		},
  2446  	}, core.PersistentVolumeClaimStatus{
  2447  		Phase: core.ClaimBound,
  2448  	})
  2449  
  2450  	invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2451  		AccessModes: []core.PersistentVolumeAccessMode{
  2452  			core.ReadWriteOnce,
  2453  			core.ReadOnlyMany,
  2454  		},
  2455  		Resources: core.VolumeResourceRequirements{
  2456  			Requests: core.ResourceList{
  2457  				core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
  2458  			},
  2459  		},
  2460  	}, core.PersistentVolumeClaimStatus{
  2461  		Phase: core.ClaimBound,
  2462  	})
  2463  
  2464  	unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2465  		AccessModes: []core.PersistentVolumeAccessMode{
  2466  			core.ReadWriteOnce,
  2467  			core.ReadOnlyMany,
  2468  		},
  2469  		Resources: core.VolumeResourceRequirements{
  2470  			Requests: core.ResourceList{
  2471  				core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
  2472  			},
  2473  		},
  2474  	}, core.PersistentVolumeClaimStatus{
  2475  		Phase: core.ClaimPending,
  2476  	})
  2477  
  2478  	validClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2479  		AccessModes: []core.PersistentVolumeAccessMode{
  2480  			core.ReadOnlyMany,
  2481  		},
  2482  		Resources: core.VolumeResourceRequirements{
  2483  			Requests: core.ResourceList{
  2484  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2485  			},
  2486  		},
  2487  	})
  2488  
  2489  	validClaimStorageClassInSpecChanged := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2490  		AccessModes: []core.PersistentVolumeAccessMode{
  2491  			core.ReadOnlyMany,
  2492  		},
  2493  		Resources: core.VolumeResourceRequirements{
  2494  			Requests: core.ResourceList{
  2495  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2496  			},
  2497  		},
  2498  	})
  2499  
  2500  	validClaimStorageClassNil := testVolumeClaimStorageClassNilInSpec("foo", "ns", core.PersistentVolumeClaimSpec{
  2501  		AccessModes: []core.PersistentVolumeAccessMode{
  2502  			core.ReadOnlyMany,
  2503  		},
  2504  		Resources: core.VolumeResourceRequirements{
  2505  			Requests: core.ResourceList{
  2506  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2507  			},
  2508  		},
  2509  	})
  2510  
  2511  	invalidClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2512  		AccessModes: []core.PersistentVolumeAccessMode{
  2513  			core.ReadOnlyMany,
  2514  		},
  2515  		Resources: core.VolumeResourceRequirements{
  2516  			Requests: core.ResourceList{
  2517  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2518  			},
  2519  		},
  2520  	})
  2521  
  2522  	validClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
  2523  		"foo", "ns", "fast", "fast", core.PersistentVolumeClaimSpec{
  2524  			AccessModes: []core.PersistentVolumeAccessMode{
  2525  				core.ReadOnlyMany,
  2526  			},
  2527  			Resources: core.VolumeResourceRequirements{
  2528  				Requests: core.ResourceList{
  2529  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2530  				},
  2531  			},
  2532  		})
  2533  
  2534  	validClaimStorageClassInAnnotationAndNilInSpec := testVolumeClaimStorageClassInAnnotationAndNilInSpec(
  2535  		"foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2536  			AccessModes: []core.PersistentVolumeAccessMode{
  2537  				core.ReadOnlyMany,
  2538  			},
  2539  			Resources: core.VolumeResourceRequirements{
  2540  				Requests: core.ResourceList{
  2541  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2542  				},
  2543  			},
  2544  		})
  2545  
  2546  	invalidClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
  2547  		"foo", "ns", "fast2", "fast", core.PersistentVolumeClaimSpec{
  2548  			AccessModes: []core.PersistentVolumeAccessMode{
  2549  				core.ReadOnlyMany,
  2550  			},
  2551  			Resources: core.VolumeResourceRequirements{
  2552  				Requests: core.ResourceList{
  2553  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2554  				},
  2555  			},
  2556  		})
  2557  
  2558  	validClaimRWOPAccessMode := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2559  		AccessModes: []core.PersistentVolumeAccessMode{
  2560  			core.ReadWriteOncePod,
  2561  		},
  2562  		Resources: core.VolumeResourceRequirements{
  2563  			Requests: core.ResourceList{
  2564  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2565  			},
  2566  		},
  2567  		VolumeName: "volume",
  2568  	})
  2569  
  2570  	validClaimRWOPAccessModeAddAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2571  		AccessModes: []core.PersistentVolumeAccessMode{
  2572  			core.ReadWriteOncePod,
  2573  		},
  2574  		Resources: core.VolumeResourceRequirements{
  2575  			Requests: core.ResourceList{
  2576  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2577  			},
  2578  		},
  2579  		VolumeName: "volume",
  2580  	})
  2581  	validClaimShrinkInitial := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2582  		AccessModes: []core.PersistentVolumeAccessMode{
  2583  			core.ReadWriteOnce,
  2584  			core.ReadOnlyMany,
  2585  		},
  2586  		Resources: core.VolumeResourceRequirements{
  2587  			Requests: core.ResourceList{
  2588  				core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
  2589  			},
  2590  		},
  2591  	}, core.PersistentVolumeClaimStatus{
  2592  		Phase: core.ClaimBound,
  2593  		Capacity: core.ResourceList{
  2594  			core.ResourceStorage: resource.MustParse("10G"),
  2595  		},
  2596  	})
  2597  
  2598  	unboundShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2599  		AccessModes: []core.PersistentVolumeAccessMode{
  2600  			core.ReadWriteOnce,
  2601  			core.ReadOnlyMany,
  2602  		},
  2603  		Resources: core.VolumeResourceRequirements{
  2604  			Requests: core.ResourceList{
  2605  				core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
  2606  			},
  2607  		},
  2608  	}, core.PersistentVolumeClaimStatus{
  2609  		Phase: core.ClaimPending,
  2610  		Capacity: core.ResourceList{
  2611  			core.ResourceStorage: resource.MustParse("10G"),
  2612  		},
  2613  	})
  2614  
  2615  	validClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2616  		AccessModes: []core.PersistentVolumeAccessMode{
  2617  			core.ReadWriteOnce,
  2618  			core.ReadOnlyMany,
  2619  		},
  2620  		Resources: core.VolumeResourceRequirements{
  2621  			Requests: core.ResourceList{
  2622  				core.ResourceStorage: resource.MustParse("13G"),
  2623  			},
  2624  		},
  2625  	}, core.PersistentVolumeClaimStatus{
  2626  		Phase: core.ClaimBound,
  2627  		Capacity: core.ResourceList{
  2628  			core.ResourceStorage: resource.MustParse("10G"),
  2629  		},
  2630  	})
  2631  
  2632  	invalidShrinkToStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2633  		AccessModes: []core.PersistentVolumeAccessMode{
  2634  			core.ReadWriteOnce,
  2635  			core.ReadOnlyMany,
  2636  		},
  2637  		Resources: core.VolumeResourceRequirements{
  2638  			Requests: core.ResourceList{
  2639  				core.ResourceStorage: resource.MustParse("10G"),
  2640  			},
  2641  		},
  2642  	}, core.PersistentVolumeClaimStatus{
  2643  		Phase: core.ClaimBound,
  2644  		Capacity: core.ResourceList{
  2645  			core.ResourceStorage: resource.MustParse("10G"),
  2646  		},
  2647  	})
  2648  
  2649  	invalidClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2650  		AccessModes: []core.PersistentVolumeAccessMode{
  2651  			core.ReadWriteOnce,
  2652  			core.ReadOnlyMany,
  2653  		},
  2654  		Resources: core.VolumeResourceRequirements{
  2655  			Requests: core.ResourceList{
  2656  				core.ResourceStorage: resource.MustParse("3G"),
  2657  			},
  2658  		},
  2659  	}, core.PersistentVolumeClaimStatus{
  2660  		Phase: core.ClaimBound,
  2661  		Capacity: core.ResourceList{
  2662  			core.ResourceStorage: resource.MustParse("10G"),
  2663  		},
  2664  	})
  2665  
  2666  	invalidClaimDataSourceAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2667  		AccessModes: []core.PersistentVolumeAccessMode{
  2668  			core.ReadWriteOnce,
  2669  		},
  2670  		VolumeMode: &file,
  2671  		Resources: core.VolumeResourceRequirements{
  2672  			Requests: core.ResourceList{
  2673  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2674  			},
  2675  		},
  2676  		VolumeName: "volume",
  2677  		DataSource: &core.TypedLocalObjectReference{
  2678  			APIGroup: &invaildAPIGroup,
  2679  			Kind:     "Foo",
  2680  			Name:     "foo",
  2681  		},
  2682  	})
  2683  
  2684  	invalidClaimDataSourceRefAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2685  		AccessModes: []core.PersistentVolumeAccessMode{
  2686  			core.ReadWriteOnce,
  2687  		},
  2688  		VolumeMode: &file,
  2689  		Resources: core.VolumeResourceRequirements{
  2690  			Requests: core.ResourceList{
  2691  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2692  			},
  2693  		},
  2694  		VolumeName: "volume",
  2695  		DataSourceRef: &core.TypedObjectReference{
  2696  			APIGroup: &invaildAPIGroup,
  2697  			Kind:     "Foo",
  2698  			Name:     "foo",
  2699  		},
  2700  	})
  2701  
  2702  	validClaimNilVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2703  		AccessModes: []core.PersistentVolumeAccessMode{
  2704  			core.ReadWriteOnce,
  2705  			core.ReadOnlyMany,
  2706  		},
  2707  		Resources: core.VolumeResourceRequirements{
  2708  			Requests: core.ResourceList{
  2709  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2710  			},
  2711  		},
  2712  	}, core.PersistentVolumeClaimStatus{
  2713  		Phase: core.ClaimBound,
  2714  	})
  2715  	validClaimEmptyVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2716  		VolumeAttributesClassName: utilpointer.String(""),
  2717  		AccessModes: []core.PersistentVolumeAccessMode{
  2718  			core.ReadWriteOnce,
  2719  			core.ReadOnlyMany,
  2720  		},
  2721  		Resources: core.VolumeResourceRequirements{
  2722  			Requests: core.ResourceList{
  2723  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2724  			},
  2725  		},
  2726  	}, core.PersistentVolumeClaimStatus{
  2727  		Phase: core.ClaimBound,
  2728  	})
  2729  	validClaimVolumeAttributesClass1 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2730  		VolumeAttributesClassName: utilpointer.String("vac1"),
  2731  		AccessModes: []core.PersistentVolumeAccessMode{
  2732  			core.ReadWriteOnce,
  2733  			core.ReadOnlyMany,
  2734  		},
  2735  		Resources: core.VolumeResourceRequirements{
  2736  			Requests: core.ResourceList{
  2737  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2738  			},
  2739  		},
  2740  	}, core.PersistentVolumeClaimStatus{
  2741  		Phase: core.ClaimBound,
  2742  	})
  2743  	validClaimVolumeAttributesClass2 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2744  		VolumeAttributesClassName: utilpointer.String("vac2"),
  2745  		AccessModes: []core.PersistentVolumeAccessMode{
  2746  			core.ReadWriteOnce,
  2747  			core.ReadOnlyMany,
  2748  		},
  2749  		Resources: core.VolumeResourceRequirements{
  2750  			Requests: core.ResourceList{
  2751  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2752  			},
  2753  		},
  2754  	}, core.PersistentVolumeClaimStatus{
  2755  		Phase: core.ClaimBound,
  2756  	})
  2757  
  2758  	scenarios := map[string]struct {
  2759  		isExpectedFailure           bool
  2760  		oldClaim                    *core.PersistentVolumeClaim
  2761  		newClaim                    *core.PersistentVolumeClaim
  2762  		enableRecoverFromExpansion  bool
  2763  		enableVolumeAttributesClass bool
  2764  	}{
  2765  		"valid-update-volumeName-only": {
  2766  			isExpectedFailure: false,
  2767  			oldClaim:          validClaim,
  2768  			newClaim:          validUpdateClaim,
  2769  		},
  2770  		"valid-no-op-update": {
  2771  			isExpectedFailure: false,
  2772  			oldClaim:          validUpdateClaim,
  2773  			newClaim:          validUpdateClaim,
  2774  		},
  2775  		"invalid-update-change-resources-on-bound-claim": {
  2776  			isExpectedFailure: true,
  2777  			oldClaim:          validUpdateClaim,
  2778  			newClaim:          invalidUpdateClaimResources,
  2779  		},
  2780  		"invalid-update-change-access-modes-on-bound-claim": {
  2781  			isExpectedFailure: true,
  2782  			oldClaim:          validUpdateClaim,
  2783  			newClaim:          invalidUpdateClaimAccessModes,
  2784  		},
  2785  		"valid-update-volume-mode-block-to-block": {
  2786  			isExpectedFailure: false,
  2787  			oldClaim:          validClaimVolumeModeBlock,
  2788  			newClaim:          validClaimVolumeModeBlock,
  2789  		},
  2790  		"valid-update-volume-mode-file-to-file": {
  2791  			isExpectedFailure: false,
  2792  			oldClaim:          validClaimVolumeModeFile,
  2793  			newClaim:          validClaimVolumeModeFile,
  2794  		},
  2795  		"invalid-update-volume-mode-to-block": {
  2796  			isExpectedFailure: true,
  2797  			oldClaim:          validClaimVolumeModeFile,
  2798  			newClaim:          validClaimVolumeModeBlock,
  2799  		},
  2800  		"invalid-update-volume-mode-to-file": {
  2801  			isExpectedFailure: true,
  2802  			oldClaim:          validClaimVolumeModeBlock,
  2803  			newClaim:          validClaimVolumeModeFile,
  2804  		},
  2805  		"invalid-update-volume-mode-nil-to-file": {
  2806  			isExpectedFailure: true,
  2807  			oldClaim:          invalidClaimVolumeModeNil,
  2808  			newClaim:          validClaimVolumeModeFile,
  2809  		},
  2810  		"invalid-update-volume-mode-nil-to-block": {
  2811  			isExpectedFailure: true,
  2812  			oldClaim:          invalidClaimVolumeModeNil,
  2813  			newClaim:          validClaimVolumeModeBlock,
  2814  		},
  2815  		"invalid-update-volume-mode-block-to-nil": {
  2816  			isExpectedFailure: true,
  2817  			oldClaim:          validClaimVolumeModeBlock,
  2818  			newClaim:          invalidClaimVolumeModeNil,
  2819  		},
  2820  		"invalid-update-volume-mode-file-to-nil": {
  2821  			isExpectedFailure: true,
  2822  			oldClaim:          validClaimVolumeModeFile,
  2823  			newClaim:          invalidClaimVolumeModeNil,
  2824  		},
  2825  		"invalid-update-volume-mode-empty-to-mode": {
  2826  			isExpectedFailure: true,
  2827  			oldClaim:          validClaim,
  2828  			newClaim:          validClaimVolumeModeBlock,
  2829  		},
  2830  		"invalid-update-volume-mode-mode-to-empty": {
  2831  			isExpectedFailure: true,
  2832  			oldClaim:          validClaimVolumeModeBlock,
  2833  			newClaim:          validClaim,
  2834  		},
  2835  		"invalid-update-change-storage-class-annotation-after-creation": {
  2836  			isExpectedFailure: true,
  2837  			oldClaim:          validClaimStorageClass,
  2838  			newClaim:          invalidUpdateClaimStorageClass,
  2839  		},
  2840  		"valid-update-mutable-annotation": {
  2841  			isExpectedFailure: false,
  2842  			oldClaim:          validClaimAnnotation,
  2843  			newClaim:          validUpdateClaimMutableAnnotation,
  2844  		},
  2845  		"valid-update-add-annotation": {
  2846  			isExpectedFailure: false,
  2847  			oldClaim:          validClaim,
  2848  			newClaim:          validAddClaimAnnotation,
  2849  		},
  2850  		"valid-size-update-resize-disabled": {
  2851  			oldClaim: validClaim,
  2852  			newClaim: validSizeUpdate,
  2853  		},
  2854  		"valid-size-update-resize-enabled": {
  2855  			isExpectedFailure: false,
  2856  			oldClaim:          validClaim,
  2857  			newClaim:          validSizeUpdate,
  2858  		},
  2859  		"invalid-size-update-resize-enabled": {
  2860  			isExpectedFailure: true,
  2861  			oldClaim:          validClaim,
  2862  			newClaim:          invalidSizeUpdate,
  2863  		},
  2864  		"unbound-size-update-resize-enabled": {
  2865  			isExpectedFailure: true,
  2866  			oldClaim:          validClaim,
  2867  			newClaim:          unboundSizeUpdate,
  2868  		},
  2869  		"valid-upgrade-storage-class-annotation-to-spec": {
  2870  			isExpectedFailure: false,
  2871  			oldClaim:          validClaimStorageClass,
  2872  			newClaim:          validClaimStorageClassInSpec,
  2873  		},
  2874  		"valid-upgrade-nil-storage-class-spec-to-spec": {
  2875  			isExpectedFailure: false,
  2876  			oldClaim:          validClaimStorageClassNil,
  2877  			newClaim:          validClaimStorageClassInSpec,
  2878  		},
  2879  		"invalid-upgrade-not-nil-storage-class-spec-to-spec": {
  2880  			isExpectedFailure: true,
  2881  			oldClaim:          validClaimStorageClassInSpec,
  2882  			newClaim:          validClaimStorageClassInSpecChanged,
  2883  		},
  2884  		"invalid-upgrade-to-nil-storage-class-spec-to-spec": {
  2885  			isExpectedFailure: true,
  2886  			oldClaim:          validClaimStorageClassInSpec,
  2887  			newClaim:          validClaimStorageClassNil,
  2888  		},
  2889  		"valid-upgrade-storage-class-annotation-and-nil-spec-to-spec-retro": {
  2890  			isExpectedFailure: false,
  2891  			oldClaim:          validClaimStorageClassInAnnotationAndNilInSpec,
  2892  			newClaim:          validClaimStorageClassInAnnotationAndSpec,
  2893  		},
  2894  		"invalid-upgrade-storage-class-annotation-and-spec-to-spec-retro": {
  2895  			isExpectedFailure: true,
  2896  			oldClaim:          validClaimStorageClassInAnnotationAndSpec,
  2897  			newClaim:          validClaimStorageClassInSpecChanged,
  2898  		},
  2899  		"invalid-upgrade-storage-class-annotation-and-no-spec": {
  2900  			isExpectedFailure: true,
  2901  			oldClaim:          validClaimStorageClassInAnnotationAndNilInSpec,
  2902  			newClaim:          validClaimStorageClassInSpecChanged,
  2903  		},
  2904  		"invalid-upgrade-storage-class-annotation-to-spec": {
  2905  			isExpectedFailure: true,
  2906  			oldClaim:          validClaimStorageClass,
  2907  			newClaim:          invalidClaimStorageClassInSpec,
  2908  		},
  2909  		"valid-upgrade-storage-class-annotation-to-annotation-and-spec": {
  2910  			isExpectedFailure: false,
  2911  			oldClaim:          validClaimStorageClass,
  2912  			newClaim:          validClaimStorageClassInAnnotationAndSpec,
  2913  		},
  2914  		"invalid-upgrade-storage-class-annotation-to-annotation-and-spec": {
  2915  			isExpectedFailure: true,
  2916  			oldClaim:          validClaimStorageClass,
  2917  			newClaim:          invalidClaimStorageClassInAnnotationAndSpec,
  2918  		},
  2919  		"invalid-upgrade-storage-class-in-spec": {
  2920  			isExpectedFailure: true,
  2921  			oldClaim:          validClaimStorageClassInSpec,
  2922  			newClaim:          invalidClaimStorageClassInSpec,
  2923  		},
  2924  		"invalid-downgrade-storage-class-spec-to-annotation": {
  2925  			isExpectedFailure: true,
  2926  			oldClaim:          validClaimStorageClassInSpec,
  2927  			newClaim:          validClaimStorageClass,
  2928  		},
  2929  		"valid-update-rwop-used-and-rwop-feature-disabled": {
  2930  			isExpectedFailure: false,
  2931  			oldClaim:          validClaimRWOPAccessMode,
  2932  			newClaim:          validClaimRWOPAccessModeAddAnnotation,
  2933  		},
  2934  		"valid-expand-shrink-resize-enabled": {
  2935  			oldClaim:                   validClaimShrinkInitial,
  2936  			newClaim:                   validClaimShrink,
  2937  			enableRecoverFromExpansion: true,
  2938  		},
  2939  		"invalid-expand-shrink-resize-enabled": {
  2940  			oldClaim:                   validClaimShrinkInitial,
  2941  			newClaim:                   invalidClaimShrink,
  2942  			enableRecoverFromExpansion: true,
  2943  			isExpectedFailure:          true,
  2944  		},
  2945  		"invalid-expand-shrink-to-status-resize-enabled": {
  2946  			oldClaim:                   validClaimShrinkInitial,
  2947  			newClaim:                   invalidShrinkToStatus,
  2948  			enableRecoverFromExpansion: true,
  2949  			isExpectedFailure:          true,
  2950  		},
  2951  		"invalid-expand-shrink-recover-disabled": {
  2952  			oldClaim:                   validClaimShrinkInitial,
  2953  			newClaim:                   validClaimShrink,
  2954  			enableRecoverFromExpansion: false,
  2955  			isExpectedFailure:          true,
  2956  		},
  2957  		"unbound-size-shrink-resize-enabled": {
  2958  			oldClaim:                   validClaimShrinkInitial,
  2959  			newClaim:                   unboundShrink,
  2960  			enableRecoverFromExpansion: true,
  2961  			isExpectedFailure:          true,
  2962  		},
  2963  		"allow-update-pvc-when-data-source-used": {
  2964  			oldClaim:          invalidClaimDataSourceAPIGroup,
  2965  			newClaim:          invalidClaimDataSourceAPIGroup,
  2966  			isExpectedFailure: false,
  2967  		},
  2968  		"allow-update-pvc-when-data-source-ref-used": {
  2969  			oldClaim:          invalidClaimDataSourceRefAPIGroup,
  2970  			newClaim:          invalidClaimDataSourceRefAPIGroup,
  2971  			isExpectedFailure: false,
  2972  		},
  2973  		"valid-update-volume-attributes-class-from-nil": {
  2974  			oldClaim:                    validClaimNilVolumeAttributesClass,
  2975  			newClaim:                    validClaimVolumeAttributesClass1,
  2976  			enableVolumeAttributesClass: true,
  2977  			isExpectedFailure:           false,
  2978  		},
  2979  		"valid-update-volume-attributes-class-from-empty": {
  2980  			oldClaim:                    validClaimEmptyVolumeAttributesClass,
  2981  			newClaim:                    validClaimVolumeAttributesClass1,
  2982  			enableVolumeAttributesClass: true,
  2983  			isExpectedFailure:           false,
  2984  		},
  2985  		"valid-update-volume-attributes-class": {
  2986  			oldClaim:                    validClaimVolumeAttributesClass1,
  2987  			newClaim:                    validClaimVolumeAttributesClass2,
  2988  			enableVolumeAttributesClass: true,
  2989  			isExpectedFailure:           false,
  2990  		},
  2991  		"invalid-update-volume-attributes-class": {
  2992  			oldClaim:                    validClaimVolumeAttributesClass1,
  2993  			newClaim:                    validClaimNilVolumeAttributesClass,
  2994  			enableVolumeAttributesClass: true,
  2995  			isExpectedFailure:           true,
  2996  		},
  2997  		"invalid-update-volume-attributes-class-to-nil": {
  2998  			oldClaim:                    validClaimVolumeAttributesClass1,
  2999  			newClaim:                    validClaimNilVolumeAttributesClass,
  3000  			enableVolumeAttributesClass: true,
  3001  			isExpectedFailure:           true,
  3002  		},
  3003  		"invalid-update-volume-attributes-class-to-empty": {
  3004  			oldClaim:                    validClaimVolumeAttributesClass1,
  3005  			newClaim:                    validClaimEmptyVolumeAttributesClass,
  3006  			enableVolumeAttributesClass: true,
  3007  			isExpectedFailure:           true,
  3008  		},
  3009  		"invalid-update-volume-attributes-class-to-nil-without-featuregate-enabled": {
  3010  			oldClaim:                    validClaimVolumeAttributesClass1,
  3011  			newClaim:                    validClaimNilVolumeAttributesClass,
  3012  			enableVolumeAttributesClass: false,
  3013  			isExpectedFailure:           true,
  3014  		},
  3015  		"invalid-update-volume-attributes-class-without-featuregate-enabled": {
  3016  			oldClaim:                    validClaimVolumeAttributesClass1,
  3017  			newClaim:                    validClaimVolumeAttributesClass2,
  3018  			enableVolumeAttributesClass: false,
  3019  			isExpectedFailure:           true,
  3020  		},
  3021  	}
  3022  
  3023  	for name, scenario := range scenarios {
  3024  		t.Run(name, func(t *testing.T) {
  3025  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)
  3026  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)
  3027  
  3028  			scenario.oldClaim.ResourceVersion = "1"
  3029  			scenario.newClaim.ResourceVersion = "1"
  3030  			opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
  3031  			errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim, opts)
  3032  			if len(errs) == 0 && scenario.isExpectedFailure {
  3033  				t.Errorf("Unexpected success for scenario: %s", name)
  3034  			}
  3035  			if len(errs) > 0 && !scenario.isExpectedFailure {
  3036  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  3037  			}
  3038  		})
  3039  	}
  3040  }
  3041  
  3042  func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
  3043  	invaildAPIGroup := "^invalid"
  3044  
  3045  	tests := map[string]struct {
  3046  		oldPvc                      *core.PersistentVolumeClaim
  3047  		enableVolumeAttributesClass bool
  3048  		expectValidationOpts        PersistentVolumeClaimSpecValidationOptions
  3049  	}{
  3050  		"nil pv": {
  3051  			oldPvc: nil,
  3052  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3053  				EnableRecoverFromExpansionFailure: false,
  3054  				EnableVolumeAttributesClass:       false,
  3055  			},
  3056  		},
  3057  		"invaild apiGroup in dataSource allowed because the old pvc is used": {
  3058  			oldPvc: pvcWithDataSource(&core.TypedLocalObjectReference{APIGroup: &invaildAPIGroup}),
  3059  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3060  				AllowInvalidAPIGroupInDataSourceOrRef: true,
  3061  			},
  3062  		},
  3063  		"invaild apiGroup in dataSourceRef allowed because the old pvc is used": {
  3064  			oldPvc: pvcWithDataSourceRef(&core.TypedObjectReference{APIGroup: &invaildAPIGroup}),
  3065  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3066  				AllowInvalidAPIGroupInDataSourceOrRef: true,
  3067  			},
  3068  		},
  3069  		"volume attributes class allowed because feature enable": {
  3070  			oldPvc:                      pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
  3071  			enableVolumeAttributesClass: true,
  3072  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3073  				EnableRecoverFromExpansionFailure: false,
  3074  				EnableVolumeAttributesClass:       true,
  3075  			},
  3076  		},
  3077  		"volume attributes class validated because used and feature disabled": {
  3078  			oldPvc:                      pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
  3079  			enableVolumeAttributesClass: false,
  3080  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3081  				EnableRecoverFromExpansionFailure: false,
  3082  				EnableVolumeAttributesClass:       true,
  3083  			},
  3084  		},
  3085  	}
  3086  
  3087  	for name, tc := range tests {
  3088  		t.Run(name, func(t *testing.T) {
  3089  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)
  3090  
  3091  			opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc)
  3092  			if opts != tc.expectValidationOpts {
  3093  				t.Errorf("Expected opts: %+v, received: %+v", tc.expectValidationOpts, opts)
  3094  			}
  3095  		})
  3096  	}
  3097  }
  3098  
  3099  func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) {
  3100  	tests := map[string]struct {
  3101  		oldPvcTemplate              *core.PersistentVolumeClaimTemplate
  3102  		enableVolumeAttributesClass bool
  3103  		expectValidationOpts        PersistentVolumeClaimSpecValidationOptions
  3104  	}{
  3105  		"nil pv": {
  3106  			oldPvcTemplate:       nil,
  3107  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{},
  3108  		},
  3109  		"volume attributes class allowed because feature enable": {
  3110  			oldPvcTemplate:              pvcTemplateWithVolumeAttributesClassName(utilpointer.String("foo")),
  3111  			enableVolumeAttributesClass: true,
  3112  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3113  				EnableVolumeAttributesClass: true,
  3114  			},
  3115  		},
  3116  	}
  3117  
  3118  	for name, tc := range tests {
  3119  		t.Run(name, func(t *testing.T) {
  3120  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)
  3121  
  3122  			opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate)
  3123  			if opts != tc.expectValidationOpts {
  3124  				t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
  3125  			}
  3126  		})
  3127  	}
  3128  }
  3129  
  3130  func TestValidateKeyToPath(t *testing.T) {
  3131  	testCases := []struct {
  3132  		kp      core.KeyToPath
  3133  		ok      bool
  3134  		errtype field.ErrorType
  3135  	}{{
  3136  		kp: core.KeyToPath{Key: "k", Path: "p"},
  3137  		ok: true,
  3138  	}, {
  3139  		kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"},
  3140  		ok: true,
  3141  	}, {
  3142  		kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"},
  3143  		ok: true,
  3144  	}, {
  3145  		kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)},
  3146  		ok: true,
  3147  	}, {
  3148  		kp:      core.KeyToPath{Key: "", Path: "p"},
  3149  		ok:      false,
  3150  		errtype: field.ErrorTypeRequired,
  3151  	}, {
  3152  		kp:      core.KeyToPath{Key: "k", Path: ""},
  3153  		ok:      false,
  3154  		errtype: field.ErrorTypeRequired,
  3155  	}, {
  3156  		kp:      core.KeyToPath{Key: "k", Path: "..p"},
  3157  		ok:      false,
  3158  		errtype: field.ErrorTypeInvalid,
  3159  	}, {
  3160  		kp:      core.KeyToPath{Key: "k", Path: "../p"},
  3161  		ok:      false,
  3162  		errtype: field.ErrorTypeInvalid,
  3163  	}, {
  3164  		kp:      core.KeyToPath{Key: "k", Path: "p/../p"},
  3165  		ok:      false,
  3166  		errtype: field.ErrorTypeInvalid,
  3167  	}, {
  3168  		kp:      core.KeyToPath{Key: "k", Path: "p/.."},
  3169  		ok:      false,
  3170  		errtype: field.ErrorTypeInvalid,
  3171  	}, {
  3172  		kp:      core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)},
  3173  		ok:      false,
  3174  		errtype: field.ErrorTypeInvalid,
  3175  	}, {
  3176  		kp:      core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)},
  3177  		ok:      false,
  3178  		errtype: field.ErrorTypeInvalid,
  3179  	},
  3180  	}
  3181  
  3182  	for i, tc := range testCases {
  3183  		errs := validateKeyToPath(&tc.kp, field.NewPath("field"))
  3184  		if tc.ok && len(errs) > 0 {
  3185  			t.Errorf("[%d] unexpected errors: %v", i, errs)
  3186  		} else if !tc.ok && len(errs) == 0 {
  3187  			t.Errorf("[%d] expected error type %v", i, tc.errtype)
  3188  		} else if len(errs) > 1 {
  3189  			t.Errorf("[%d] expected only one error, got %d", i, len(errs))
  3190  		} else if !tc.ok {
  3191  			if errs[0].Type != tc.errtype {
  3192  				t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type)
  3193  			}
  3194  		}
  3195  	}
  3196  }
  3197  
  3198  func TestValidateNFSVolumeSource(t *testing.T) {
  3199  	testCases := []struct {
  3200  		name      string
  3201  		nfs       *core.NFSVolumeSource
  3202  		errtype   field.ErrorType
  3203  		errfield  string
  3204  		errdetail string
  3205  	}{{
  3206  		name:     "missing server",
  3207  		nfs:      &core.NFSVolumeSource{Server: "", Path: "/tmp"},
  3208  		errtype:  field.ErrorTypeRequired,
  3209  		errfield: "server",
  3210  	}, {
  3211  		name:     "missing path",
  3212  		nfs:      &core.NFSVolumeSource{Server: "my-server", Path: ""},
  3213  		errtype:  field.ErrorTypeRequired,
  3214  		errfield: "path",
  3215  	}, {
  3216  		name:      "abs path",
  3217  		nfs:       &core.NFSVolumeSource{Server: "my-server", Path: "tmp"},
  3218  		errtype:   field.ErrorTypeInvalid,
  3219  		errfield:  "path",
  3220  		errdetail: "must be an absolute path",
  3221  	},
  3222  	}
  3223  
  3224  	for i, tc := range testCases {
  3225  		errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field"))
  3226  
  3227  		if len(errs) > 0 && tc.errtype == "" {
  3228  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3229  		} else if len(errs) == 0 && tc.errtype != "" {
  3230  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3231  		} else if len(errs) >= 1 {
  3232  			if errs[0].Type != tc.errtype {
  3233  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3234  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3235  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3236  			} else if !strings.Contains(errs[0].Detail, tc.errdetail) {
  3237  				t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
  3238  			}
  3239  		}
  3240  	}
  3241  }
  3242  
  3243  func TestValidateGlusterfs(t *testing.T) {
  3244  	testCases := []struct {
  3245  		name     string
  3246  		gfs      *core.GlusterfsVolumeSource
  3247  		errtype  field.ErrorType
  3248  		errfield string
  3249  	}{{
  3250  		name:     "missing endpointname",
  3251  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"},
  3252  		errtype:  field.ErrorTypeRequired,
  3253  		errfield: "endpoints",
  3254  	}, {
  3255  		name:     "missing path",
  3256  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""},
  3257  		errtype:  field.ErrorTypeRequired,
  3258  		errfield: "path",
  3259  	}, {
  3260  		name:     "missing endpointname and path",
  3261  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""},
  3262  		errtype:  field.ErrorTypeRequired,
  3263  		errfield: "endpoints",
  3264  	},
  3265  	}
  3266  
  3267  	for i, tc := range testCases {
  3268  		errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field"))
  3269  
  3270  		if len(errs) > 0 && tc.errtype == "" {
  3271  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3272  		} else if len(errs) == 0 && tc.errtype != "" {
  3273  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3274  		} else if len(errs) >= 1 {
  3275  			if errs[0].Type != tc.errtype {
  3276  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3277  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3278  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3279  			}
  3280  		}
  3281  	}
  3282  }
  3283  
  3284  func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) {
  3285  	var epNs *string
  3286  	namespace := ""
  3287  	epNs = &namespace
  3288  
  3289  	testCases := []struct {
  3290  		name     string
  3291  		gfs      *core.GlusterfsPersistentVolumeSource
  3292  		errtype  field.ErrorType
  3293  		errfield string
  3294  	}{{
  3295  		name:     "missing endpointname",
  3296  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"},
  3297  		errtype:  field.ErrorTypeRequired,
  3298  		errfield: "endpoints",
  3299  	}, {
  3300  		name:     "missing path",
  3301  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""},
  3302  		errtype:  field.ErrorTypeRequired,
  3303  		errfield: "path",
  3304  	}, {
  3305  		name:     "non null endpointnamespace with empty string",
  3306  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs},
  3307  		errtype:  field.ErrorTypeInvalid,
  3308  		errfield: "endpointsNamespace",
  3309  	}, {
  3310  		name:     "missing endpointname and path",
  3311  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""},
  3312  		errtype:  field.ErrorTypeRequired,
  3313  		errfield: "endpoints",
  3314  	},
  3315  	}
  3316  
  3317  	for i, tc := range testCases {
  3318  		errs := validateGlusterfsPersistentVolumeSource(tc.gfs, field.NewPath("field"))
  3319  
  3320  		if len(errs) > 0 && tc.errtype == "" {
  3321  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3322  		} else if len(errs) == 0 && tc.errtype != "" {
  3323  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3324  		} else if len(errs) >= 1 {
  3325  			if errs[0].Type != tc.errtype {
  3326  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3327  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3328  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3329  			}
  3330  		}
  3331  	}
  3332  }
  3333  
  3334  func TestValidateCSIVolumeSource(t *testing.T) {
  3335  	testCases := []struct {
  3336  		name     string
  3337  		csi      *core.CSIVolumeSource
  3338  		errtype  field.ErrorType
  3339  		errfield string
  3340  	}{{
  3341  		name: "all required fields ok",
  3342  		csi:  &core.CSIVolumeSource{Driver: "test-driver"},
  3343  	}, {
  3344  		name:     "missing driver name",
  3345  		csi:      &core.CSIVolumeSource{Driver: ""},
  3346  		errtype:  field.ErrorTypeRequired,
  3347  		errfield: "driver",
  3348  	}, {
  3349  		name: "driver name: ok no punctuations",
  3350  		csi:  &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"},
  3351  	}, {
  3352  		name: "driver name: ok dot only",
  3353  		csi:  &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"},
  3354  	}, {
  3355  		name: "driver name: ok dash only",
  3356  		csi:  &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"},
  3357  	}, {
  3358  		name:     "driver name: invalid underscore",
  3359  		csi:      &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"},
  3360  		errtype:  field.ErrorTypeInvalid,
  3361  		errfield: "driver",
  3362  	}, {
  3363  		name:     "driver name: invalid dot underscores",
  3364  		csi:      &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"},
  3365  		errtype:  field.ErrorTypeInvalid,
  3366  		errfield: "driver",
  3367  	}, {
  3368  		name: "driver name: ok beginning with number",
  3369  		csi:  &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"},
  3370  	}, {
  3371  		name: "driver name: ok ending with number",
  3372  		csi:  &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"},
  3373  	}, {
  3374  		name:     "driver name: invalid dot dash underscores",
  3375  		csi:      &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"},
  3376  		errtype:  field.ErrorTypeInvalid,
  3377  		errfield: "driver",
  3378  	},
  3379  
  3380  		{
  3381  			name: "driver name: ok length 1",
  3382  			csi:  &core.CSIVolumeSource{Driver: "a"},
  3383  		}, {
  3384  			name:     "driver name: invalid length > 63",
  3385  			csi:      &core.CSIVolumeSource{Driver: strings.Repeat("g", 65)},
  3386  			errtype:  field.ErrorTypeTooLong,
  3387  			errfield: "driver",
  3388  		}, {
  3389  			name:     "driver name: invalid start char",
  3390  			csi:      &core.CSIVolumeSource{Driver: "_comgooglestoragecsigcepd"},
  3391  			errtype:  field.ErrorTypeInvalid,
  3392  			errfield: "driver",
  3393  		}, {
  3394  			name:     "driver name: invalid end char",
  3395  			csi:      &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd/"},
  3396  			errtype:  field.ErrorTypeInvalid,
  3397  			errfield: "driver",
  3398  		}, {
  3399  			name:     "driver name: invalid separators",
  3400  			csi:      &core.CSIVolumeSource{Driver: "com/google/storage/csi~gcepd"},
  3401  			errtype:  field.ErrorTypeInvalid,
  3402  			errfield: "driver",
  3403  		}, {
  3404  			name: "valid nodePublishSecretRef",
  3405  			csi:  &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: "foobar"}},
  3406  		}, {
  3407  			name:     "nodePublishSecretRef: invalid name missing",
  3408  			csi:      &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: ""}},
  3409  			errtype:  field.ErrorTypeRequired,
  3410  			errfield: "nodePublishSecretRef.name",
  3411  		},
  3412  	}
  3413  
  3414  	for i, tc := range testCases {
  3415  		errs := validateCSIVolumeSource(tc.csi, field.NewPath("field"))
  3416  
  3417  		if len(errs) > 0 && tc.errtype == "" {
  3418  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3419  		} else if len(errs) == 0 && tc.errtype != "" {
  3420  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3421  		} else if len(errs) >= 1 {
  3422  			if errs[0].Type != tc.errtype {
  3423  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3424  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3425  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3426  			}
  3427  		}
  3428  	}
  3429  }
  3430  
  3431  func TestValidateCSIPersistentVolumeSource(t *testing.T) {
  3432  	testCases := []struct {
  3433  		name     string
  3434  		csi      *core.CSIPersistentVolumeSource
  3435  		errtype  field.ErrorType
  3436  		errfield string
  3437  	}{{
  3438  		name: "all required fields ok",
  3439  		csi:  &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
  3440  	}, {
  3441  		name: "with default values ok",
  3442  		csi:  &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
  3443  	}, {
  3444  		name:     "missing driver name",
  3445  		csi:      &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
  3446  		errtype:  field.ErrorTypeRequired,
  3447  		errfield: "driver",
  3448  	}, {
  3449  		name:     "missing volume handle",
  3450  		csi:      &core.CSIPersistentVolumeSource{Driver: "my-driver"},
  3451  		errtype:  field.ErrorTypeRequired,
  3452  		errfield: "volumeHandle",
  3453  	}, {
  3454  		name: "driver name: ok no punctuations",
  3455  		csi:  &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"},
  3456  	}, {
  3457  		name: "driver name: ok dot only",
  3458  		csi:  &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"},
  3459  	}, {
  3460  		name: "driver name: ok dash only",
  3461  		csi:  &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
  3462  	}, {
  3463  		name:     "driver name: invalid underscore",
  3464  		csi:      &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
  3465  		errtype:  field.ErrorTypeInvalid,
  3466  		errfield: "driver",
  3467  	}, {
  3468  		name:     "driver name: invalid dot underscores",
  3469  		csi:      &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
  3470  		errtype:  field.ErrorTypeInvalid,
  3471  		errfield: "driver",
  3472  	}, {
  3473  		name: "driver name: ok beginning with number",
  3474  		csi:  &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"},
  3475  	}, {
  3476  		name: "driver name: ok ending with number",
  3477  		csi:  &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"},
  3478  	}, {
  3479  		name:     "driver name: invalid dot dash underscores",
  3480  		csi:      &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
  3481  		errtype:  field.ErrorTypeInvalid,
  3482  		errfield: "driver",
  3483  	}, {
  3484  		name:     "driver name: invalid length 0",
  3485  		csi:      &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"},
  3486  		errtype:  field.ErrorTypeRequired,
  3487  		errfield: "driver",
  3488  	}, {
  3489  		name: "driver name: ok length 1",
  3490  		csi:  &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
  3491  	}, {
  3492  		name:     "driver name: invalid length > 63",
  3493  		csi:      &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"},
  3494  		errtype:  field.ErrorTypeTooLong,
  3495  		errfield: "driver",
  3496  	}, {
  3497  		name:     "driver name: invalid start char",
  3498  		csi:      &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"},
  3499  		errtype:  field.ErrorTypeInvalid,
  3500  		errfield: "driver",
  3501  	}, {
  3502  		name:     "driver name: invalid end char",
  3503  		csi:      &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"},
  3504  		errtype:  field.ErrorTypeInvalid,
  3505  		errfield: "driver",
  3506  	}, {
  3507  		name:     "driver name: invalid separators",
  3508  		csi:      &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"},
  3509  		errtype:  field.ErrorTypeInvalid,
  3510  		errfield: "driver",
  3511  	}, {
  3512  		name:     "controllerExpandSecretRef: invalid name missing",
  3513  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}},
  3514  		errtype:  field.ErrorTypeRequired,
  3515  		errfield: "controllerExpandSecretRef.name",
  3516  	}, {
  3517  		name:     "controllerExpandSecretRef: invalid namespace missing",
  3518  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}},
  3519  		errtype:  field.ErrorTypeRequired,
  3520  		errfield: "controllerExpandSecretRef.namespace",
  3521  	}, {
  3522  		name: "valid controllerExpandSecretRef",
  3523  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3524  	}, {
  3525  		name:     "controllerPublishSecretRef: invalid name missing",
  3526  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}},
  3527  		errtype:  field.ErrorTypeRequired,
  3528  		errfield: "controllerPublishSecretRef.name",
  3529  	}, {
  3530  		name:     "controllerPublishSecretRef: invalid namespace missing",
  3531  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}},
  3532  		errtype:  field.ErrorTypeRequired,
  3533  		errfield: "controllerPublishSecretRef.namespace",
  3534  	}, {
  3535  		name: "valid controllerPublishSecretRef",
  3536  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3537  	}, {
  3538  		name: "valid nodePublishSecretRef",
  3539  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3540  	}, {
  3541  		name:     "nodePublishSecretRef: invalid name missing",
  3542  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}},
  3543  		errtype:  field.ErrorTypeRequired,
  3544  		errfield: "nodePublishSecretRef.name",
  3545  	}, {
  3546  		name:     "nodePublishSecretRef: invalid namespace missing",
  3547  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}},
  3548  		errtype:  field.ErrorTypeRequired,
  3549  		errfield: "nodePublishSecretRef.namespace",
  3550  	}, {
  3551  		name:     "nodeExpandSecretRef: invalid name missing",
  3552  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}},
  3553  		errtype:  field.ErrorTypeRequired,
  3554  		errfield: "nodeExpandSecretRef.name",
  3555  	}, {
  3556  		name:     "nodeExpandSecretRef: invalid namespace missing",
  3557  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}},
  3558  		errtype:  field.ErrorTypeRequired,
  3559  		errfield: "nodeExpandSecretRef.namespace",
  3560  	}, {
  3561  		name: "valid nodeExpandSecretRef",
  3562  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3563  	}, {
  3564  		name: "Invalid nodePublishSecretRef",
  3565  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3566  	},
  3567  
  3568  		// tests with allowDNSSubDomainSecretName flag on/off
  3569  		{
  3570  			name: "valid nodeExpandSecretRef",
  3571  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3572  		}, {
  3573  			name: "valid long nodeExpandSecretRef",
  3574  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3575  		}, {
  3576  			name:     "Invalid nodeExpandSecretRef",
  3577  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3578  			errtype:  field.ErrorTypeInvalid,
  3579  			errfield: "nodeExpandSecretRef.name",
  3580  		}, {
  3581  			name: "valid nodePublishSecretRef",
  3582  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3583  		}, {
  3584  			name: "valid long nodePublishSecretRef",
  3585  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3586  		}, {
  3587  			name:     "Invalid nodePublishSecretRef",
  3588  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3589  			errtype:  field.ErrorTypeInvalid,
  3590  			errfield: "nodePublishSecretRef.name",
  3591  		}, {
  3592  			name: "valid ControllerExpandSecretRef",
  3593  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3594  		}, {
  3595  			name: "valid long ControllerExpandSecretRef",
  3596  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3597  		}, {
  3598  			name:     "Invalid ControllerExpandSecretRef",
  3599  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3600  			errtype:  field.ErrorTypeInvalid,
  3601  			errfield: "controllerExpandSecretRef.name",
  3602  		},
  3603  	}
  3604  
  3605  	for i, tc := range testCases {
  3606  		errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field"))
  3607  
  3608  		if len(errs) > 0 && tc.errtype == "" {
  3609  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3610  		} else if len(errs) == 0 && tc.errtype != "" {
  3611  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3612  		} else if len(errs) >= 1 {
  3613  			if errs[0].Type != tc.errtype {
  3614  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3615  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3616  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3617  			}
  3618  		}
  3619  	}
  3620  }
  3621  
  3622  // This test is a little too top-to-bottom.  Ideally we would test each volume
  3623  // type on its own, but we want to also make sure that the logic works through
  3624  // the one-of wrapper, so we just do it all in one place.
  3625  func TestValidateVolumes(t *testing.T) {
  3626  	validInitiatorName := "iqn.2015-02.example.com:init"
  3627  	invalidInitiatorName := "2015-02.example.com:init"
  3628  
  3629  	type verr struct {
  3630  		etype  field.ErrorType
  3631  		field  string
  3632  		detail string
  3633  	}
  3634  
  3635  	testCases := []struct {
  3636  		name string
  3637  		vol  core.Volume
  3638  		errs []verr
  3639  		opts PodValidationOptions
  3640  	}{
  3641  		// EmptyDir and basic volume names
  3642  		{
  3643  			name: "valid alpha name",
  3644  			vol: core.Volume{
  3645  				Name: "empty",
  3646  				VolumeSource: core.VolumeSource{
  3647  					EmptyDir: &core.EmptyDirVolumeSource{},
  3648  				},
  3649  			},
  3650  		}, {
  3651  			name: "valid num name",
  3652  			vol: core.Volume{
  3653  				Name: "123",
  3654  				VolumeSource: core.VolumeSource{
  3655  					EmptyDir: &core.EmptyDirVolumeSource{},
  3656  				},
  3657  			},
  3658  		}, {
  3659  			name: "valid alphanum name",
  3660  			vol: core.Volume{
  3661  				Name: "empty-123",
  3662  				VolumeSource: core.VolumeSource{
  3663  					EmptyDir: &core.EmptyDirVolumeSource{},
  3664  				},
  3665  			},
  3666  		}, {
  3667  			name: "valid numalpha name",
  3668  			vol: core.Volume{
  3669  				Name: "123-empty",
  3670  				VolumeSource: core.VolumeSource{
  3671  					EmptyDir: &core.EmptyDirVolumeSource{},
  3672  				},
  3673  			},
  3674  		}, {
  3675  			name: "zero-length name",
  3676  			vol: core.Volume{
  3677  				Name:         "",
  3678  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3679  			},
  3680  			errs: []verr{{
  3681  				etype: field.ErrorTypeRequired,
  3682  				field: "name",
  3683  			}},
  3684  		}, {
  3685  			name: "name > 63 characters",
  3686  			vol: core.Volume{
  3687  				Name:         strings.Repeat("a", 64),
  3688  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3689  			},
  3690  			errs: []verr{{
  3691  				etype:  field.ErrorTypeInvalid,
  3692  				field:  "name",
  3693  				detail: "must be no more than",
  3694  			}},
  3695  		}, {
  3696  			name: "name has dots",
  3697  			vol: core.Volume{
  3698  				Name:         "a.b.c",
  3699  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3700  			},
  3701  			errs: []verr{{
  3702  				etype:  field.ErrorTypeInvalid,
  3703  				field:  "name",
  3704  				detail: "must not contain dots",
  3705  			}},
  3706  		}, {
  3707  			name: "name not a DNS label",
  3708  			vol: core.Volume{
  3709  				Name:         "Not a DNS label!",
  3710  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3711  			},
  3712  			errs: []verr{{
  3713  				etype:  field.ErrorTypeInvalid,
  3714  				field:  "name",
  3715  				detail: dnsLabelErrMsg,
  3716  			}},
  3717  		},
  3718  		// More than one source field specified.
  3719  		{
  3720  			name: "more than one source",
  3721  			vol: core.Volume{
  3722  				Name: "dups",
  3723  				VolumeSource: core.VolumeSource{
  3724  					EmptyDir: &core.EmptyDirVolumeSource{},
  3725  					HostPath: &core.HostPathVolumeSource{
  3726  						Path: "/mnt/path",
  3727  						Type: newHostPathType(string(core.HostPathDirectory)),
  3728  					},
  3729  				},
  3730  			},
  3731  			errs: []verr{{
  3732  				etype:  field.ErrorTypeForbidden,
  3733  				field:  "hostPath",
  3734  				detail: "may not specify more than 1 volume",
  3735  			}},
  3736  		},
  3737  		// HostPath Default
  3738  		{
  3739  			name: "default HostPath",
  3740  			vol: core.Volume{
  3741  				Name: "hostpath",
  3742  				VolumeSource: core.VolumeSource{
  3743  					HostPath: &core.HostPathVolumeSource{
  3744  						Path: "/mnt/path",
  3745  						Type: newHostPathType(string(core.HostPathDirectory)),
  3746  					},
  3747  				},
  3748  			},
  3749  		},
  3750  		// HostPath Supported
  3751  		{
  3752  			name: "valid HostPath",
  3753  			vol: core.Volume{
  3754  				Name: "hostpath",
  3755  				VolumeSource: core.VolumeSource{
  3756  					HostPath: &core.HostPathVolumeSource{
  3757  						Path: "/mnt/path",
  3758  						Type: newHostPathType(string(core.HostPathSocket)),
  3759  					},
  3760  				},
  3761  			},
  3762  		},
  3763  		// HostPath Invalid
  3764  		{
  3765  			name: "invalid HostPath",
  3766  			vol: core.Volume{
  3767  				Name: "hostpath",
  3768  				VolumeSource: core.VolumeSource{
  3769  					HostPath: &core.HostPathVolumeSource{
  3770  						Path: "/mnt/path",
  3771  						Type: newHostPathType("invalid"),
  3772  					},
  3773  				},
  3774  			},
  3775  			errs: []verr{{
  3776  				etype: field.ErrorTypeNotSupported,
  3777  				field: "type",
  3778  			}},
  3779  		}, {
  3780  			name: "invalid HostPath backsteps",
  3781  			vol: core.Volume{
  3782  				Name: "hostpath",
  3783  				VolumeSource: core.VolumeSource{
  3784  					HostPath: &core.HostPathVolumeSource{
  3785  						Path: "/mnt/path/..",
  3786  						Type: newHostPathType(string(core.HostPathDirectory)),
  3787  					},
  3788  				},
  3789  			},
  3790  			errs: []verr{{
  3791  				etype:  field.ErrorTypeInvalid,
  3792  				field:  "path",
  3793  				detail: "must not contain '..'",
  3794  			}},
  3795  		},
  3796  		// GcePersistentDisk
  3797  		{
  3798  			name: "valid GcePersistentDisk",
  3799  			vol: core.Volume{
  3800  				Name: "gce-pd",
  3801  				VolumeSource: core.VolumeSource{
  3802  					GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
  3803  						PDName:    "my-PD",
  3804  						FSType:    "ext4",
  3805  						Partition: 1,
  3806  						ReadOnly:  false,
  3807  					},
  3808  				},
  3809  			},
  3810  		},
  3811  		// AWSElasticBlockStore
  3812  		{
  3813  			name: "valid AWSElasticBlockStore",
  3814  			vol: core.Volume{
  3815  				Name: "aws-ebs",
  3816  				VolumeSource: core.VolumeSource{
  3817  					AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{
  3818  						VolumeID:  "my-PD",
  3819  						FSType:    "ext4",
  3820  						Partition: 1,
  3821  						ReadOnly:  false,
  3822  					},
  3823  				},
  3824  			},
  3825  		},
  3826  		// GitRepo
  3827  		{
  3828  			name: "valid GitRepo",
  3829  			vol: core.Volume{
  3830  				Name: "git-repo",
  3831  				VolumeSource: core.VolumeSource{
  3832  					GitRepo: &core.GitRepoVolumeSource{
  3833  						Repository: "my-repo",
  3834  						Revision:   "hashstring",
  3835  						Directory:  "target",
  3836  					},
  3837  				},
  3838  			},
  3839  		}, {
  3840  			name: "valid GitRepo in .",
  3841  			vol: core.Volume{
  3842  				Name: "git-repo-dot",
  3843  				VolumeSource: core.VolumeSource{
  3844  					GitRepo: &core.GitRepoVolumeSource{
  3845  						Repository: "my-repo",
  3846  						Directory:  ".",
  3847  					},
  3848  				},
  3849  			},
  3850  		}, {
  3851  			name: "valid GitRepo with .. in name",
  3852  			vol: core.Volume{
  3853  				Name: "git-repo-dot-dot-foo",
  3854  				VolumeSource: core.VolumeSource{
  3855  					GitRepo: &core.GitRepoVolumeSource{
  3856  						Repository: "my-repo",
  3857  						Directory:  "..foo",
  3858  					},
  3859  				},
  3860  			},
  3861  		}, {
  3862  			name: "GitRepo starts with ../",
  3863  			vol: core.Volume{
  3864  				Name: "gitrepo",
  3865  				VolumeSource: core.VolumeSource{
  3866  					GitRepo: &core.GitRepoVolumeSource{
  3867  						Repository: "foo",
  3868  						Directory:  "../dots/bar",
  3869  					},
  3870  				},
  3871  			},
  3872  			errs: []verr{{
  3873  				etype:  field.ErrorTypeInvalid,
  3874  				field:  "gitRepo.directory",
  3875  				detail: `must not contain '..'`,
  3876  			}},
  3877  		}, {
  3878  			name: "GitRepo contains ..",
  3879  			vol: core.Volume{
  3880  				Name: "gitrepo",
  3881  				VolumeSource: core.VolumeSource{
  3882  					GitRepo: &core.GitRepoVolumeSource{
  3883  						Repository: "foo",
  3884  						Directory:  "dots/../bar",
  3885  					},
  3886  				},
  3887  			},
  3888  			errs: []verr{{
  3889  				etype:  field.ErrorTypeInvalid,
  3890  				field:  "gitRepo.directory",
  3891  				detail: `must not contain '..'`,
  3892  			}},
  3893  		}, {
  3894  			name: "GitRepo absolute target",
  3895  			vol: core.Volume{
  3896  				Name: "gitrepo",
  3897  				VolumeSource: core.VolumeSource{
  3898  					GitRepo: &core.GitRepoVolumeSource{
  3899  						Repository: "foo",
  3900  						Directory:  "/abstarget",
  3901  					},
  3902  				},
  3903  			},
  3904  			errs: []verr{{
  3905  				etype: field.ErrorTypeInvalid,
  3906  				field: "gitRepo.directory",
  3907  			}},
  3908  		},
  3909  		// ISCSI
  3910  		{
  3911  			name: "valid ISCSI",
  3912  			vol: core.Volume{
  3913  				Name: "iscsi",
  3914  				VolumeSource: core.VolumeSource{
  3915  					ISCSI: &core.ISCSIVolumeSource{
  3916  						TargetPortal: "127.0.0.1",
  3917  						IQN:          "iqn.2015-02.example.com:test",
  3918  						Lun:          1,
  3919  						FSType:       "ext4",
  3920  						ReadOnly:     false,
  3921  					},
  3922  				},
  3923  			},
  3924  		}, {
  3925  			name: "valid IQN: eui format",
  3926  			vol: core.Volume{
  3927  				Name: "iscsi",
  3928  				VolumeSource: core.VolumeSource{
  3929  					ISCSI: &core.ISCSIVolumeSource{
  3930  						TargetPortal: "127.0.0.1",
  3931  						IQN:          "eui.0123456789ABCDEF",
  3932  						Lun:          1,
  3933  						FSType:       "ext4",
  3934  						ReadOnly:     false,
  3935  					},
  3936  				},
  3937  			},
  3938  		}, {
  3939  			name: "valid IQN: naa format",
  3940  			vol: core.Volume{
  3941  				Name: "iscsi",
  3942  				VolumeSource: core.VolumeSource{
  3943  					ISCSI: &core.ISCSIVolumeSource{
  3944  						TargetPortal: "127.0.0.1",
  3945  						IQN:          "naa.62004567BA64678D0123456789ABCDEF",
  3946  						Lun:          1,
  3947  						FSType:       "ext4",
  3948  						ReadOnly:     false,
  3949  					},
  3950  				},
  3951  			},
  3952  		}, {
  3953  			name: "empty portal",
  3954  			vol: core.Volume{
  3955  				Name: "iscsi",
  3956  				VolumeSource: core.VolumeSource{
  3957  					ISCSI: &core.ISCSIVolumeSource{
  3958  						TargetPortal: "",
  3959  						IQN:          "iqn.2015-02.example.com:test",
  3960  						Lun:          1,
  3961  						FSType:       "ext4",
  3962  						ReadOnly:     false,
  3963  					},
  3964  				},
  3965  			},
  3966  			errs: []verr{{
  3967  				etype: field.ErrorTypeRequired,
  3968  				field: "iscsi.targetPortal",
  3969  			}},
  3970  		}, {
  3971  			name: "empty iqn",
  3972  			vol: core.Volume{
  3973  				Name: "iscsi",
  3974  				VolumeSource: core.VolumeSource{
  3975  					ISCSI: &core.ISCSIVolumeSource{
  3976  						TargetPortal: "127.0.0.1",
  3977  						IQN:          "",
  3978  						Lun:          1,
  3979  						FSType:       "ext4",
  3980  						ReadOnly:     false,
  3981  					},
  3982  				},
  3983  			},
  3984  			errs: []verr{{
  3985  				etype: field.ErrorTypeRequired,
  3986  				field: "iscsi.iqn",
  3987  			}},
  3988  		}, {
  3989  			name: "invalid IQN: iqn format",
  3990  			vol: core.Volume{
  3991  				Name: "iscsi",
  3992  				VolumeSource: core.VolumeSource{
  3993  					ISCSI: &core.ISCSIVolumeSource{
  3994  						TargetPortal: "127.0.0.1",
  3995  						IQN:          "iqn.2015-02.example.com:test;ls;",
  3996  						Lun:          1,
  3997  						FSType:       "ext4",
  3998  						ReadOnly:     false,
  3999  					},
  4000  				},
  4001  			},
  4002  			errs: []verr{{
  4003  				etype: field.ErrorTypeInvalid,
  4004  				field: "iscsi.iqn",
  4005  			}},
  4006  		}, {
  4007  			name: "invalid IQN: eui format",
  4008  			vol: core.Volume{
  4009  				Name: "iscsi",
  4010  				VolumeSource: core.VolumeSource{
  4011  					ISCSI: &core.ISCSIVolumeSource{
  4012  						TargetPortal: "127.0.0.1",
  4013  						IQN:          "eui.0123456789ABCDEFGHIJ",
  4014  						Lun:          1,
  4015  						FSType:       "ext4",
  4016  						ReadOnly:     false,
  4017  					},
  4018  				},
  4019  			},
  4020  			errs: []verr{{
  4021  				etype: field.ErrorTypeInvalid,
  4022  				field: "iscsi.iqn",
  4023  			}},
  4024  		}, {
  4025  			name: "invalid IQN: naa format",
  4026  			vol: core.Volume{
  4027  				Name: "iscsi",
  4028  				VolumeSource: core.VolumeSource{
  4029  					ISCSI: &core.ISCSIVolumeSource{
  4030  						TargetPortal: "127.0.0.1",
  4031  						IQN:          "naa.62004567BA_4-78D.123456789ABCDEF",
  4032  						Lun:          1,
  4033  						FSType:       "ext4",
  4034  						ReadOnly:     false,
  4035  					},
  4036  				},
  4037  			},
  4038  			errs: []verr{{
  4039  				etype: field.ErrorTypeInvalid,
  4040  				field: "iscsi.iqn",
  4041  			}},
  4042  		}, {
  4043  			name: "valid initiatorName",
  4044  			vol: core.Volume{
  4045  				Name: "iscsi",
  4046  				VolumeSource: core.VolumeSource{
  4047  					ISCSI: &core.ISCSIVolumeSource{
  4048  						TargetPortal:  "127.0.0.1",
  4049  						IQN:           "iqn.2015-02.example.com:test",
  4050  						Lun:           1,
  4051  						InitiatorName: &validInitiatorName,
  4052  						FSType:        "ext4",
  4053  						ReadOnly:      false,
  4054  					},
  4055  				},
  4056  			},
  4057  		}, {
  4058  			name: "invalid initiatorName",
  4059  			vol: core.Volume{
  4060  				Name: "iscsi",
  4061  				VolumeSource: core.VolumeSource{
  4062  					ISCSI: &core.ISCSIVolumeSource{
  4063  						TargetPortal:  "127.0.0.1",
  4064  						IQN:           "iqn.2015-02.example.com:test",
  4065  						Lun:           1,
  4066  						InitiatorName: &invalidInitiatorName,
  4067  						FSType:        "ext4",
  4068  						ReadOnly:      false,
  4069  					},
  4070  				},
  4071  			},
  4072  			errs: []verr{{
  4073  				etype: field.ErrorTypeInvalid,
  4074  				field: "iscsi.initiatorname",
  4075  			}},
  4076  		}, {
  4077  			name: "empty secret",
  4078  			vol: core.Volume{
  4079  				Name: "iscsi",
  4080  				VolumeSource: core.VolumeSource{
  4081  					ISCSI: &core.ISCSIVolumeSource{
  4082  						TargetPortal:      "127.0.0.1",
  4083  						IQN:               "iqn.2015-02.example.com:test",
  4084  						Lun:               1,
  4085  						FSType:            "ext4",
  4086  						ReadOnly:          false,
  4087  						DiscoveryCHAPAuth: true,
  4088  					},
  4089  				},
  4090  			},
  4091  			errs: []verr{{
  4092  				etype: field.ErrorTypeRequired,
  4093  				field: "iscsi.secretRef",
  4094  			}},
  4095  		}, {
  4096  			name: "empty secret",
  4097  			vol: core.Volume{
  4098  				Name: "iscsi",
  4099  				VolumeSource: core.VolumeSource{
  4100  					ISCSI: &core.ISCSIVolumeSource{
  4101  						TargetPortal:    "127.0.0.1",
  4102  						IQN:             "iqn.2015-02.example.com:test",
  4103  						Lun:             1,
  4104  						FSType:          "ext4",
  4105  						ReadOnly:        false,
  4106  						SessionCHAPAuth: true,
  4107  					},
  4108  				},
  4109  			},
  4110  			errs: []verr{{
  4111  				etype: field.ErrorTypeRequired,
  4112  				field: "iscsi.secretRef",
  4113  			}},
  4114  		},
  4115  		// Secret
  4116  		{
  4117  			name: "valid Secret",
  4118  			vol: core.Volume{
  4119  				Name: "secret",
  4120  				VolumeSource: core.VolumeSource{
  4121  					Secret: &core.SecretVolumeSource{
  4122  						SecretName: "my-secret",
  4123  					},
  4124  				},
  4125  			},
  4126  		}, {
  4127  			name: "valid Secret with defaultMode",
  4128  			vol: core.Volume{
  4129  				Name: "secret",
  4130  				VolumeSource: core.VolumeSource{
  4131  					Secret: &core.SecretVolumeSource{
  4132  						SecretName:  "my-secret",
  4133  						DefaultMode: utilpointer.Int32(0644),
  4134  					},
  4135  				},
  4136  			},
  4137  		}, {
  4138  			name: "valid Secret with projection and mode",
  4139  			vol: core.Volume{
  4140  				Name: "secret",
  4141  				VolumeSource: core.VolumeSource{
  4142  					Secret: &core.SecretVolumeSource{
  4143  						SecretName: "my-secret",
  4144  						Items: []core.KeyToPath{{
  4145  							Key:  "key",
  4146  							Path: "filename",
  4147  							Mode: utilpointer.Int32(0644),
  4148  						}},
  4149  					},
  4150  				},
  4151  			},
  4152  		}, {
  4153  			name: "valid Secret with subdir projection",
  4154  			vol: core.Volume{
  4155  				Name: "secret",
  4156  				VolumeSource: core.VolumeSource{
  4157  					Secret: &core.SecretVolumeSource{
  4158  						SecretName: "my-secret",
  4159  						Items: []core.KeyToPath{{
  4160  							Key:  "key",
  4161  							Path: "dir/filename",
  4162  						}},
  4163  					},
  4164  				},
  4165  			},
  4166  		}, {
  4167  			name: "secret with missing path",
  4168  			vol: core.Volume{
  4169  				Name: "secret",
  4170  				VolumeSource: core.VolumeSource{
  4171  					Secret: &core.SecretVolumeSource{
  4172  						SecretName: "s",
  4173  						Items:      []core.KeyToPath{{Key: "key", Path: ""}},
  4174  					},
  4175  				},
  4176  			},
  4177  			errs: []verr{{
  4178  				etype: field.ErrorTypeRequired,
  4179  				field: "secret.items[0].path",
  4180  			}},
  4181  		}, {
  4182  			name: "secret with leading ..",
  4183  			vol: core.Volume{
  4184  				Name: "secret",
  4185  				VolumeSource: core.VolumeSource{
  4186  					Secret: &core.SecretVolumeSource{
  4187  						SecretName: "s",
  4188  						Items:      []core.KeyToPath{{Key: "key", Path: "../foo"}},
  4189  					},
  4190  				},
  4191  			},
  4192  			errs: []verr{{
  4193  				etype: field.ErrorTypeInvalid,
  4194  				field: "secret.items[0].path",
  4195  			}},
  4196  		}, {
  4197  			name: "secret with .. inside",
  4198  			vol: core.Volume{
  4199  				Name: "secret",
  4200  				VolumeSource: core.VolumeSource{
  4201  					Secret: &core.SecretVolumeSource{
  4202  						SecretName: "s",
  4203  						Items:      []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
  4204  					},
  4205  				},
  4206  			},
  4207  			errs: []verr{{
  4208  				etype: field.ErrorTypeInvalid,
  4209  				field: "secret.items[0].path",
  4210  			}},
  4211  		}, {
  4212  			name: "secret with invalid positive defaultMode",
  4213  			vol: core.Volume{
  4214  				Name: "secret",
  4215  				VolumeSource: core.VolumeSource{
  4216  					Secret: &core.SecretVolumeSource{
  4217  						SecretName:  "s",
  4218  						DefaultMode: utilpointer.Int32(01000),
  4219  					},
  4220  				},
  4221  			},
  4222  			errs: []verr{{
  4223  				etype: field.ErrorTypeInvalid,
  4224  				field: "secret.defaultMode",
  4225  			}},
  4226  		}, {
  4227  			name: "secret with invalid negative defaultMode",
  4228  			vol: core.Volume{
  4229  				Name: "secret",
  4230  				VolumeSource: core.VolumeSource{
  4231  					Secret: &core.SecretVolumeSource{
  4232  						SecretName:  "s",
  4233  						DefaultMode: utilpointer.Int32(-1),
  4234  					},
  4235  				},
  4236  			},
  4237  			errs: []verr{{
  4238  				etype: field.ErrorTypeInvalid,
  4239  				field: "secret.defaultMode",
  4240  			}},
  4241  		},
  4242  		// ConfigMap
  4243  		{
  4244  			name: "valid ConfigMap",
  4245  			vol: core.Volume{
  4246  				Name: "cfgmap",
  4247  				VolumeSource: core.VolumeSource{
  4248  					ConfigMap: &core.ConfigMapVolumeSource{
  4249  						LocalObjectReference: core.LocalObjectReference{
  4250  							Name: "my-cfgmap",
  4251  						},
  4252  					},
  4253  				},
  4254  			},
  4255  		}, {
  4256  			name: "valid ConfigMap with defaultMode",
  4257  			vol: core.Volume{
  4258  				Name: "cfgmap",
  4259  				VolumeSource: core.VolumeSource{
  4260  					ConfigMap: &core.ConfigMapVolumeSource{
  4261  						LocalObjectReference: core.LocalObjectReference{
  4262  							Name: "my-cfgmap",
  4263  						},
  4264  						DefaultMode: utilpointer.Int32(0644),
  4265  					},
  4266  				},
  4267  			},
  4268  		}, {
  4269  			name: "valid ConfigMap with projection and mode",
  4270  			vol: core.Volume{
  4271  				Name: "cfgmap",
  4272  				VolumeSource: core.VolumeSource{
  4273  					ConfigMap: &core.ConfigMapVolumeSource{
  4274  						LocalObjectReference: core.LocalObjectReference{
  4275  							Name: "my-cfgmap"},
  4276  						Items: []core.KeyToPath{{
  4277  							Key:  "key",
  4278  							Path: "filename",
  4279  							Mode: utilpointer.Int32(0644),
  4280  						}},
  4281  					},
  4282  				},
  4283  			},
  4284  		}, {
  4285  			name: "valid ConfigMap with subdir projection",
  4286  			vol: core.Volume{
  4287  				Name: "cfgmap",
  4288  				VolumeSource: core.VolumeSource{
  4289  					ConfigMap: &core.ConfigMapVolumeSource{
  4290  						LocalObjectReference: core.LocalObjectReference{
  4291  							Name: "my-cfgmap"},
  4292  						Items: []core.KeyToPath{{
  4293  							Key:  "key",
  4294  							Path: "dir/filename",
  4295  						}},
  4296  					},
  4297  				},
  4298  			},
  4299  		}, {
  4300  			name: "configmap with missing path",
  4301  			vol: core.Volume{
  4302  				Name: "cfgmap",
  4303  				VolumeSource: core.VolumeSource{
  4304  					ConfigMap: &core.ConfigMapVolumeSource{
  4305  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4306  						Items:                []core.KeyToPath{{Key: "key", Path: ""}},
  4307  					},
  4308  				},
  4309  			},
  4310  			errs: []verr{{
  4311  				etype: field.ErrorTypeRequired,
  4312  				field: "configMap.items[0].path",
  4313  			}},
  4314  		}, {
  4315  			name: "configmap with leading ..",
  4316  			vol: core.Volume{
  4317  				Name: "cfgmap",
  4318  				VolumeSource: core.VolumeSource{
  4319  					ConfigMap: &core.ConfigMapVolumeSource{
  4320  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4321  						Items:                []core.KeyToPath{{Key: "key", Path: "../foo"}},
  4322  					},
  4323  				},
  4324  			},
  4325  			errs: []verr{{
  4326  				etype: field.ErrorTypeInvalid,
  4327  				field: "configMap.items[0].path",
  4328  			}},
  4329  		}, {
  4330  			name: "configmap with .. inside",
  4331  			vol: core.Volume{
  4332  				Name: "cfgmap",
  4333  				VolumeSource: core.VolumeSource{
  4334  					ConfigMap: &core.ConfigMapVolumeSource{
  4335  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4336  						Items:                []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
  4337  					},
  4338  				},
  4339  			},
  4340  			errs: []verr{{
  4341  				etype: field.ErrorTypeInvalid,
  4342  				field: "configMap.items[0].path",
  4343  			}},
  4344  		}, {
  4345  			name: "configmap with invalid positive defaultMode",
  4346  			vol: core.Volume{
  4347  				Name: "cfgmap",
  4348  				VolumeSource: core.VolumeSource{
  4349  					ConfigMap: &core.ConfigMapVolumeSource{
  4350  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4351  						DefaultMode:          utilpointer.Int32(01000),
  4352  					},
  4353  				},
  4354  			},
  4355  			errs: []verr{{
  4356  				etype: field.ErrorTypeInvalid,
  4357  				field: "configMap.defaultMode",
  4358  			}},
  4359  		}, {
  4360  			name: "configmap with invalid negative defaultMode",
  4361  			vol: core.Volume{
  4362  				Name: "cfgmap",
  4363  				VolumeSource: core.VolumeSource{
  4364  					ConfigMap: &core.ConfigMapVolumeSource{
  4365  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4366  						DefaultMode:          utilpointer.Int32(-1),
  4367  					},
  4368  				},
  4369  			},
  4370  			errs: []verr{{
  4371  				etype: field.ErrorTypeInvalid,
  4372  				field: "configMap.defaultMode",
  4373  			}},
  4374  		},
  4375  		// Glusterfs
  4376  		{
  4377  			name: "valid Glusterfs",
  4378  			vol: core.Volume{
  4379  				Name: "glusterfs",
  4380  				VolumeSource: core.VolumeSource{
  4381  					Glusterfs: &core.GlusterfsVolumeSource{
  4382  						EndpointsName: "host1",
  4383  						Path:          "path",
  4384  						ReadOnly:      false,
  4385  					},
  4386  				},
  4387  			},
  4388  		}, {
  4389  			name: "empty hosts",
  4390  			vol: core.Volume{
  4391  				Name: "glusterfs",
  4392  				VolumeSource: core.VolumeSource{
  4393  					Glusterfs: &core.GlusterfsVolumeSource{
  4394  						EndpointsName: "",
  4395  						Path:          "path",
  4396  						ReadOnly:      false,
  4397  					},
  4398  				},
  4399  			},
  4400  			errs: []verr{{
  4401  				etype: field.ErrorTypeRequired,
  4402  				field: "glusterfs.endpoints",
  4403  			}},
  4404  		}, {
  4405  			name: "empty path",
  4406  			vol: core.Volume{
  4407  				Name: "glusterfs",
  4408  				VolumeSource: core.VolumeSource{
  4409  					Glusterfs: &core.GlusterfsVolumeSource{
  4410  						EndpointsName: "host",
  4411  						Path:          "",
  4412  						ReadOnly:      false,
  4413  					},
  4414  				},
  4415  			},
  4416  			errs: []verr{{
  4417  				etype: field.ErrorTypeRequired,
  4418  				field: "glusterfs.path",
  4419  			}},
  4420  		},
  4421  		// Flocker
  4422  		{
  4423  			name: "valid Flocker -- datasetUUID",
  4424  			vol: core.Volume{
  4425  				Name: "flocker",
  4426  				VolumeSource: core.VolumeSource{
  4427  					Flocker: &core.FlockerVolumeSource{
  4428  						DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
  4429  					},
  4430  				},
  4431  			},
  4432  		}, {
  4433  			name: "valid Flocker -- datasetName",
  4434  			vol: core.Volume{
  4435  				Name: "flocker",
  4436  				VolumeSource: core.VolumeSource{
  4437  					Flocker: &core.FlockerVolumeSource{
  4438  						DatasetName: "datasetName",
  4439  					},
  4440  				},
  4441  			},
  4442  		}, {
  4443  			name: "both empty",
  4444  			vol: core.Volume{
  4445  				Name: "flocker",
  4446  				VolumeSource: core.VolumeSource{
  4447  					Flocker: &core.FlockerVolumeSource{
  4448  						DatasetName: "",
  4449  					},
  4450  				},
  4451  			},
  4452  			errs: []verr{{
  4453  				etype: field.ErrorTypeRequired,
  4454  				field: "flocker",
  4455  			}},
  4456  		}, {
  4457  			name: "both specified",
  4458  			vol: core.Volume{
  4459  				Name: "flocker",
  4460  				VolumeSource: core.VolumeSource{
  4461  					Flocker: &core.FlockerVolumeSource{
  4462  						DatasetName: "datasetName",
  4463  						DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
  4464  					},
  4465  				},
  4466  			},
  4467  			errs: []verr{{
  4468  				etype: field.ErrorTypeInvalid,
  4469  				field: "flocker",
  4470  			}},
  4471  		}, {
  4472  			name: "slash in flocker datasetName",
  4473  			vol: core.Volume{
  4474  				Name: "flocker",
  4475  				VolumeSource: core.VolumeSource{
  4476  					Flocker: &core.FlockerVolumeSource{
  4477  						DatasetName: "foo/bar",
  4478  					},
  4479  				},
  4480  			},
  4481  			errs: []verr{{
  4482  				etype:  field.ErrorTypeInvalid,
  4483  				field:  "flocker.datasetName",
  4484  				detail: "must not contain '/'",
  4485  			}},
  4486  		},
  4487  		// RBD
  4488  		{
  4489  			name: "valid RBD",
  4490  			vol: core.Volume{
  4491  				Name: "rbd",
  4492  				VolumeSource: core.VolumeSource{
  4493  					RBD: &core.RBDVolumeSource{
  4494  						CephMonitors: []string{"foo"},
  4495  						RBDImage:     "bar",
  4496  						FSType:       "ext4",
  4497  					},
  4498  				},
  4499  			},
  4500  		}, {
  4501  			name: "empty rbd monitors",
  4502  			vol: core.Volume{
  4503  				Name: "rbd",
  4504  				VolumeSource: core.VolumeSource{
  4505  					RBD: &core.RBDVolumeSource{
  4506  						CephMonitors: []string{},
  4507  						RBDImage:     "bar",
  4508  						FSType:       "ext4",
  4509  					},
  4510  				},
  4511  			},
  4512  			errs: []verr{{
  4513  				etype: field.ErrorTypeRequired,
  4514  				field: "rbd.monitors",
  4515  			}},
  4516  		}, {
  4517  			name: "empty image",
  4518  			vol: core.Volume{
  4519  				Name: "rbd",
  4520  				VolumeSource: core.VolumeSource{
  4521  					RBD: &core.RBDVolumeSource{
  4522  						CephMonitors: []string{"foo"},
  4523  						RBDImage:     "",
  4524  						FSType:       "ext4",
  4525  					},
  4526  				},
  4527  			},
  4528  			errs: []verr{{
  4529  				etype: field.ErrorTypeRequired,
  4530  				field: "rbd.image",
  4531  			}},
  4532  		},
  4533  		// Cinder
  4534  		{
  4535  			name: "valid Cinder",
  4536  			vol: core.Volume{
  4537  				Name: "cinder",
  4538  				VolumeSource: core.VolumeSource{
  4539  					Cinder: &core.CinderVolumeSource{
  4540  						VolumeID: "29ea5088-4f60-4757-962e-dba678767887",
  4541  						FSType:   "ext4",
  4542  						ReadOnly: false,
  4543  					},
  4544  				},
  4545  			},
  4546  		},
  4547  		// CephFS
  4548  		{
  4549  			name: "valid CephFS",
  4550  			vol: core.Volume{
  4551  				Name: "cephfs",
  4552  				VolumeSource: core.VolumeSource{
  4553  					CephFS: &core.CephFSVolumeSource{
  4554  						Monitors: []string{"foo"},
  4555  					},
  4556  				},
  4557  			},
  4558  		}, {
  4559  			name: "empty cephfs monitors",
  4560  			vol: core.Volume{
  4561  				Name: "cephfs",
  4562  				VolumeSource: core.VolumeSource{
  4563  					CephFS: &core.CephFSVolumeSource{
  4564  						Monitors: []string{},
  4565  					},
  4566  				},
  4567  			},
  4568  			errs: []verr{{
  4569  				etype: field.ErrorTypeRequired,
  4570  				field: "cephfs.monitors",
  4571  			}},
  4572  		},
  4573  		// DownwardAPI
  4574  		{
  4575  			name: "valid DownwardAPI",
  4576  			vol: core.Volume{
  4577  				Name: "downwardapi",
  4578  				VolumeSource: core.VolumeSource{
  4579  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4580  						Items: []core.DownwardAPIVolumeFile{{
  4581  							Path: "labels",
  4582  							FieldRef: &core.ObjectFieldSelector{
  4583  								APIVersion: "v1",
  4584  								FieldPath:  "metadata.labels",
  4585  							},
  4586  						}, {
  4587  							Path: "labels with subscript",
  4588  							FieldRef: &core.ObjectFieldSelector{
  4589  								APIVersion: "v1",
  4590  								FieldPath:  "metadata.labels['key']",
  4591  							},
  4592  						}, {
  4593  							Path: "labels with complex subscript",
  4594  							FieldRef: &core.ObjectFieldSelector{
  4595  								APIVersion: "v1",
  4596  								FieldPath:  "metadata.labels['test.example.com/key']",
  4597  							},
  4598  						}, {
  4599  							Path: "annotations",
  4600  							FieldRef: &core.ObjectFieldSelector{
  4601  								APIVersion: "v1",
  4602  								FieldPath:  "metadata.annotations",
  4603  							},
  4604  						}, {
  4605  							Path: "annotations with subscript",
  4606  							FieldRef: &core.ObjectFieldSelector{
  4607  								APIVersion: "v1",
  4608  								FieldPath:  "metadata.annotations['key']",
  4609  							},
  4610  						}, {
  4611  							Path: "annotations with complex subscript",
  4612  							FieldRef: &core.ObjectFieldSelector{
  4613  								APIVersion: "v1",
  4614  								FieldPath:  "metadata.annotations['TEST.EXAMPLE.COM/key']",
  4615  							},
  4616  						}, {
  4617  							Path: "namespace",
  4618  							FieldRef: &core.ObjectFieldSelector{
  4619  								APIVersion: "v1",
  4620  								FieldPath:  "metadata.namespace",
  4621  							},
  4622  						}, {
  4623  							Path: "name",
  4624  							FieldRef: &core.ObjectFieldSelector{
  4625  								APIVersion: "v1",
  4626  								FieldPath:  "metadata.name",
  4627  							},
  4628  						}, {
  4629  							Path: "path/with/subdirs",
  4630  							FieldRef: &core.ObjectFieldSelector{
  4631  								APIVersion: "v1",
  4632  								FieldPath:  "metadata.labels",
  4633  							},
  4634  						}, {
  4635  							Path: "path/./withdot",
  4636  							FieldRef: &core.ObjectFieldSelector{
  4637  								APIVersion: "v1",
  4638  								FieldPath:  "metadata.labels",
  4639  							},
  4640  						}, {
  4641  							Path: "path/with/embedded..dotdot",
  4642  							FieldRef: &core.ObjectFieldSelector{
  4643  								APIVersion: "v1",
  4644  								FieldPath:  "metadata.labels",
  4645  							},
  4646  						}, {
  4647  							Path: "path/with/leading/..dotdot",
  4648  							FieldRef: &core.ObjectFieldSelector{
  4649  								APIVersion: "v1",
  4650  								FieldPath:  "metadata.labels",
  4651  							},
  4652  						}, {
  4653  							Path: "cpu_limit",
  4654  							ResourceFieldRef: &core.ResourceFieldSelector{
  4655  								ContainerName: "test-container",
  4656  								Resource:      "limits.cpu",
  4657  							},
  4658  						}, {
  4659  							Path: "cpu_request",
  4660  							ResourceFieldRef: &core.ResourceFieldSelector{
  4661  								ContainerName: "test-container",
  4662  								Resource:      "requests.cpu",
  4663  							},
  4664  						}, {
  4665  							Path: "memory_limit",
  4666  							ResourceFieldRef: &core.ResourceFieldSelector{
  4667  								ContainerName: "test-container",
  4668  								Resource:      "limits.memory",
  4669  							},
  4670  						}, {
  4671  							Path: "memory_request",
  4672  							ResourceFieldRef: &core.ResourceFieldSelector{
  4673  								ContainerName: "test-container",
  4674  								Resource:      "requests.memory",
  4675  							},
  4676  						}},
  4677  					},
  4678  				},
  4679  			},
  4680  		}, {
  4681  			name: "hugepages-downwardAPI-enabled",
  4682  			vol: core.Volume{
  4683  				Name: "downwardapi",
  4684  				VolumeSource: core.VolumeSource{
  4685  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4686  						Items: []core.DownwardAPIVolumeFile{{
  4687  							Path: "hugepages_request",
  4688  							ResourceFieldRef: &core.ResourceFieldSelector{
  4689  								ContainerName: "test-container",
  4690  								Resource:      "requests.hugepages-2Mi",
  4691  							},
  4692  						}, {
  4693  							Path: "hugepages_limit",
  4694  							ResourceFieldRef: &core.ResourceFieldSelector{
  4695  								ContainerName: "test-container",
  4696  								Resource:      "limits.hugepages-2Mi",
  4697  							},
  4698  						}},
  4699  					},
  4700  				},
  4701  			},
  4702  		}, {
  4703  			name: "downapi valid defaultMode",
  4704  			vol: core.Volume{
  4705  				Name: "downapi",
  4706  				VolumeSource: core.VolumeSource{
  4707  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4708  						DefaultMode: utilpointer.Int32(0644),
  4709  					},
  4710  				},
  4711  			},
  4712  		}, {
  4713  			name: "downapi valid item mode",
  4714  			vol: core.Volume{
  4715  				Name: "downapi",
  4716  				VolumeSource: core.VolumeSource{
  4717  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4718  						Items: []core.DownwardAPIVolumeFile{{
  4719  							Mode: utilpointer.Int32(0644),
  4720  							Path: "path",
  4721  							FieldRef: &core.ObjectFieldSelector{
  4722  								APIVersion: "v1",
  4723  								FieldPath:  "metadata.labels",
  4724  							},
  4725  						}},
  4726  					},
  4727  				},
  4728  			},
  4729  		}, {
  4730  			name: "downapi invalid positive item mode",
  4731  			vol: core.Volume{
  4732  				Name: "downapi",
  4733  				VolumeSource: core.VolumeSource{
  4734  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4735  						Items: []core.DownwardAPIVolumeFile{{
  4736  							Mode: utilpointer.Int32(01000),
  4737  							Path: "path",
  4738  							FieldRef: &core.ObjectFieldSelector{
  4739  								APIVersion: "v1",
  4740  								FieldPath:  "metadata.labels",
  4741  							},
  4742  						}},
  4743  					},
  4744  				},
  4745  			},
  4746  			errs: []verr{{
  4747  				etype: field.ErrorTypeInvalid,
  4748  				field: "downwardAPI.mode",
  4749  			}},
  4750  		}, {
  4751  			name: "downapi invalid negative item mode",
  4752  			vol: core.Volume{
  4753  				Name: "downapi",
  4754  				VolumeSource: core.VolumeSource{
  4755  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4756  						Items: []core.DownwardAPIVolumeFile{{
  4757  							Mode: utilpointer.Int32(-1),
  4758  							Path: "path",
  4759  							FieldRef: &core.ObjectFieldSelector{
  4760  								APIVersion: "v1",
  4761  								FieldPath:  "metadata.labels",
  4762  							},
  4763  						}},
  4764  					},
  4765  				},
  4766  			},
  4767  			errs: []verr{{
  4768  				etype: field.ErrorTypeInvalid,
  4769  				field: "downwardAPI.mode",
  4770  			}},
  4771  		}, {
  4772  			name: "downapi empty metatada path",
  4773  			vol: core.Volume{
  4774  				Name: "downapi",
  4775  				VolumeSource: core.VolumeSource{
  4776  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4777  						Items: []core.DownwardAPIVolumeFile{{
  4778  							Path: "",
  4779  							FieldRef: &core.ObjectFieldSelector{
  4780  								APIVersion: "v1",
  4781  								FieldPath:  "metadata.labels",
  4782  							},
  4783  						}},
  4784  					},
  4785  				},
  4786  			},
  4787  			errs: []verr{{
  4788  				etype: field.ErrorTypeRequired,
  4789  				field: "downwardAPI.path",
  4790  			}},
  4791  		}, {
  4792  			name: "downapi absolute path",
  4793  			vol: core.Volume{
  4794  				Name: "downapi",
  4795  				VolumeSource: core.VolumeSource{
  4796  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4797  						Items: []core.DownwardAPIVolumeFile{{
  4798  							Path: "/absolutepath",
  4799  							FieldRef: &core.ObjectFieldSelector{
  4800  								APIVersion: "v1",
  4801  								FieldPath:  "metadata.labels",
  4802  							},
  4803  						}},
  4804  					},
  4805  				},
  4806  			},
  4807  			errs: []verr{{
  4808  				etype: field.ErrorTypeInvalid,
  4809  				field: "downwardAPI.path",
  4810  			}},
  4811  		}, {
  4812  			name: "downapi dot dot path",
  4813  			vol: core.Volume{
  4814  				Name: "downapi",
  4815  				VolumeSource: core.VolumeSource{
  4816  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4817  						Items: []core.DownwardAPIVolumeFile{{
  4818  							Path: "../../passwd",
  4819  							FieldRef: &core.ObjectFieldSelector{
  4820  								APIVersion: "v1",
  4821  								FieldPath:  "metadata.labels",
  4822  							},
  4823  						}},
  4824  					},
  4825  				},
  4826  			},
  4827  			errs: []verr{{
  4828  				etype:  field.ErrorTypeInvalid,
  4829  				field:  "downwardAPI.path",
  4830  				detail: `must not contain '..'`,
  4831  			}},
  4832  		}, {
  4833  			name: "downapi dot dot file name",
  4834  			vol: core.Volume{
  4835  				Name: "downapi",
  4836  				VolumeSource: core.VolumeSource{
  4837  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4838  						Items: []core.DownwardAPIVolumeFile{{
  4839  							Path: "..badFileName",
  4840  							FieldRef: &core.ObjectFieldSelector{
  4841  								APIVersion: "v1",
  4842  								FieldPath:  "metadata.labels",
  4843  							},
  4844  						}},
  4845  					},
  4846  				},
  4847  			},
  4848  			errs: []verr{{
  4849  				etype:  field.ErrorTypeInvalid,
  4850  				field:  "downwardAPI.path",
  4851  				detail: `must not start with '..'`,
  4852  			}},
  4853  		}, {
  4854  			name: "downapi dot dot first level dirent",
  4855  			vol: core.Volume{
  4856  				Name: "downapi",
  4857  				VolumeSource: core.VolumeSource{
  4858  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4859  						Items: []core.DownwardAPIVolumeFile{{
  4860  							Path: "..badDirName/goodFileName",
  4861  							FieldRef: &core.ObjectFieldSelector{
  4862  								APIVersion: "v1",
  4863  								FieldPath:  "metadata.labels",
  4864  							},
  4865  						}},
  4866  					},
  4867  				},
  4868  			},
  4869  			errs: []verr{{
  4870  				etype:  field.ErrorTypeInvalid,
  4871  				field:  "downwardAPI.path",
  4872  				detail: `must not start with '..'`,
  4873  			}},
  4874  		}, {
  4875  			name: "downapi fieldRef and ResourceFieldRef together",
  4876  			vol: core.Volume{
  4877  				Name: "downapi",
  4878  				VolumeSource: core.VolumeSource{
  4879  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4880  						Items: []core.DownwardAPIVolumeFile{{
  4881  							Path: "test",
  4882  							FieldRef: &core.ObjectFieldSelector{
  4883  								APIVersion: "v1",
  4884  								FieldPath:  "metadata.labels",
  4885  							},
  4886  							ResourceFieldRef: &core.ResourceFieldSelector{
  4887  								ContainerName: "test-container",
  4888  								Resource:      "requests.memory",
  4889  							},
  4890  						}},
  4891  					},
  4892  				},
  4893  			},
  4894  			errs: []verr{{
  4895  				etype:  field.ErrorTypeInvalid,
  4896  				field:  "downwardAPI",
  4897  				detail: "fieldRef and resourceFieldRef can not be specified simultaneously",
  4898  			}},
  4899  		}, {
  4900  			name: "downapi invalid positive defaultMode",
  4901  			vol: core.Volume{
  4902  				Name: "downapi",
  4903  				VolumeSource: core.VolumeSource{
  4904  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4905  						DefaultMode: utilpointer.Int32(01000),
  4906  					},
  4907  				},
  4908  			},
  4909  			errs: []verr{{
  4910  				etype: field.ErrorTypeInvalid,
  4911  				field: "downwardAPI.defaultMode",
  4912  			}},
  4913  		}, {
  4914  			name: "downapi invalid negative defaultMode",
  4915  			vol: core.Volume{
  4916  				Name: "downapi",
  4917  				VolumeSource: core.VolumeSource{
  4918  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4919  						DefaultMode: utilpointer.Int32(-1),
  4920  					},
  4921  				},
  4922  			},
  4923  			errs: []verr{{
  4924  				etype: field.ErrorTypeInvalid,
  4925  				field: "downwardAPI.defaultMode",
  4926  			}},
  4927  		},
  4928  		// FC
  4929  		{
  4930  			name: "FC valid targetWWNs and lun",
  4931  			vol: core.Volume{
  4932  				Name: "fc",
  4933  				VolumeSource: core.VolumeSource{
  4934  					FC: &core.FCVolumeSource{
  4935  						TargetWWNs: []string{"some_wwn"},
  4936  						Lun:        utilpointer.Int32(1),
  4937  						FSType:     "ext4",
  4938  						ReadOnly:   false,
  4939  					},
  4940  				},
  4941  			},
  4942  		}, {
  4943  			name: "FC valid wwids",
  4944  			vol: core.Volume{
  4945  				Name: "fc",
  4946  				VolumeSource: core.VolumeSource{
  4947  					FC: &core.FCVolumeSource{
  4948  						WWIDs:    []string{"some_wwid"},
  4949  						FSType:   "ext4",
  4950  						ReadOnly: false,
  4951  					},
  4952  				},
  4953  			},
  4954  		}, {
  4955  			name: "FC empty targetWWNs and wwids",
  4956  			vol: core.Volume{
  4957  				Name: "fc",
  4958  				VolumeSource: core.VolumeSource{
  4959  					FC: &core.FCVolumeSource{
  4960  						TargetWWNs: []string{},
  4961  						Lun:        utilpointer.Int32(1),
  4962  						WWIDs:      []string{},
  4963  						FSType:     "ext4",
  4964  						ReadOnly:   false,
  4965  					},
  4966  				},
  4967  			},
  4968  			errs: []verr{{
  4969  				etype:  field.ErrorTypeRequired,
  4970  				field:  "fc.targetWWNs",
  4971  				detail: "must specify either targetWWNs or wwids",
  4972  			}},
  4973  		}, {
  4974  			name: "FC invalid: both targetWWNs and wwids simultaneously",
  4975  			vol: core.Volume{
  4976  				Name: "fc",
  4977  				VolumeSource: core.VolumeSource{
  4978  					FC: &core.FCVolumeSource{
  4979  						TargetWWNs: []string{"some_wwn"},
  4980  						Lun:        utilpointer.Int32(1),
  4981  						WWIDs:      []string{"some_wwid"},
  4982  						FSType:     "ext4",
  4983  						ReadOnly:   false,
  4984  					},
  4985  				},
  4986  			},
  4987  			errs: []verr{{
  4988  				etype:  field.ErrorTypeInvalid,
  4989  				field:  "fc.targetWWNs",
  4990  				detail: "targetWWNs and wwids can not be specified simultaneously",
  4991  			}},
  4992  		}, {
  4993  			name: "FC valid targetWWNs and empty lun",
  4994  			vol: core.Volume{
  4995  				Name: "fc",
  4996  				VolumeSource: core.VolumeSource{
  4997  					FC: &core.FCVolumeSource{
  4998  						TargetWWNs: []string{"wwn"},
  4999  						Lun:        nil,
  5000  						FSType:     "ext4",
  5001  						ReadOnly:   false,
  5002  					},
  5003  				},
  5004  			},
  5005  			errs: []verr{{
  5006  				etype:  field.ErrorTypeRequired,
  5007  				field:  "fc.lun",
  5008  				detail: "lun is required if targetWWNs is specified",
  5009  			}},
  5010  		}, {
  5011  			name: "FC valid targetWWNs and invalid lun",
  5012  			vol: core.Volume{
  5013  				Name: "fc",
  5014  				VolumeSource: core.VolumeSource{
  5015  					FC: &core.FCVolumeSource{
  5016  						TargetWWNs: []string{"wwn"},
  5017  						Lun:        utilpointer.Int32(256),
  5018  						FSType:     "ext4",
  5019  						ReadOnly:   false,
  5020  					},
  5021  				},
  5022  			},
  5023  			errs: []verr{{
  5024  				etype:  field.ErrorTypeInvalid,
  5025  				field:  "fc.lun",
  5026  				detail: validation.InclusiveRangeError(0, 255),
  5027  			}},
  5028  		},
  5029  		// FlexVolume
  5030  		{
  5031  			name: "valid FlexVolume",
  5032  			vol: core.Volume{
  5033  				Name: "flex-volume",
  5034  				VolumeSource: core.VolumeSource{
  5035  					FlexVolume: &core.FlexVolumeSource{
  5036  						Driver: "kubernetes.io/blue",
  5037  						FSType: "ext4",
  5038  					},
  5039  				},
  5040  			},
  5041  		},
  5042  		// AzureFile
  5043  		{
  5044  			name: "valid AzureFile",
  5045  			vol: core.Volume{
  5046  				Name: "azure-file",
  5047  				VolumeSource: core.VolumeSource{
  5048  					AzureFile: &core.AzureFileVolumeSource{
  5049  						SecretName: "key",
  5050  						ShareName:  "share",
  5051  						ReadOnly:   false,
  5052  					},
  5053  				},
  5054  			},
  5055  		}, {
  5056  			name: "AzureFile empty secret",
  5057  			vol: core.Volume{
  5058  				Name: "azure-file",
  5059  				VolumeSource: core.VolumeSource{
  5060  					AzureFile: &core.AzureFileVolumeSource{
  5061  						SecretName: "",
  5062  						ShareName:  "share",
  5063  						ReadOnly:   false,
  5064  					},
  5065  				},
  5066  			},
  5067  			errs: []verr{{
  5068  				etype: field.ErrorTypeRequired,
  5069  				field: "azureFile.secretName",
  5070  			}},
  5071  		}, {
  5072  			name: "AzureFile empty share",
  5073  			vol: core.Volume{
  5074  				Name: "azure-file",
  5075  				VolumeSource: core.VolumeSource{
  5076  					AzureFile: &core.AzureFileVolumeSource{
  5077  						SecretName: "name",
  5078  						ShareName:  "",
  5079  						ReadOnly:   false,
  5080  					},
  5081  				},
  5082  			},
  5083  			errs: []verr{{
  5084  				etype: field.ErrorTypeRequired,
  5085  				field: "azureFile.shareName",
  5086  			}},
  5087  		},
  5088  		// Quobyte
  5089  		{
  5090  			name: "valid Quobyte",
  5091  			vol: core.Volume{
  5092  				Name: "quobyte",
  5093  				VolumeSource: core.VolumeSource{
  5094  					Quobyte: &core.QuobyteVolumeSource{
  5095  						Registry: "registry:7861",
  5096  						Volume:   "volume",
  5097  						ReadOnly: false,
  5098  						User:     "root",
  5099  						Group:    "root",
  5100  						Tenant:   "ThisIsSomeTenantUUID",
  5101  					},
  5102  				},
  5103  			},
  5104  		}, {
  5105  			name: "empty registry quobyte",
  5106  			vol: core.Volume{
  5107  				Name: "quobyte",
  5108  				VolumeSource: core.VolumeSource{
  5109  					Quobyte: &core.QuobyteVolumeSource{
  5110  						Volume: "/test",
  5111  						Tenant: "ThisIsSomeTenantUUID",
  5112  					},
  5113  				},
  5114  			},
  5115  			errs: []verr{{
  5116  				etype: field.ErrorTypeRequired,
  5117  				field: "quobyte.registry",
  5118  			}},
  5119  		}, {
  5120  			name: "wrong format registry quobyte",
  5121  			vol: core.Volume{
  5122  				Name: "quobyte",
  5123  				VolumeSource: core.VolumeSource{
  5124  					Quobyte: &core.QuobyteVolumeSource{
  5125  						Registry: "registry7861",
  5126  						Volume:   "/test",
  5127  						Tenant:   "ThisIsSomeTenantUUID",
  5128  					},
  5129  				},
  5130  			},
  5131  			errs: []verr{{
  5132  				etype: field.ErrorTypeInvalid,
  5133  				field: "quobyte.registry",
  5134  			}},
  5135  		}, {
  5136  			name: "wrong format multiple registries quobyte",
  5137  			vol: core.Volume{
  5138  				Name: "quobyte",
  5139  				VolumeSource: core.VolumeSource{
  5140  					Quobyte: &core.QuobyteVolumeSource{
  5141  						Registry: "registry:7861,reg2",
  5142  						Volume:   "/test",
  5143  						Tenant:   "ThisIsSomeTenantUUID",
  5144  					},
  5145  				},
  5146  			},
  5147  			errs: []verr{{
  5148  				etype: field.ErrorTypeInvalid,
  5149  				field: "quobyte.registry",
  5150  			}},
  5151  		}, {
  5152  			name: "empty volume quobyte",
  5153  			vol: core.Volume{
  5154  				Name: "quobyte",
  5155  				VolumeSource: core.VolumeSource{
  5156  					Quobyte: &core.QuobyteVolumeSource{
  5157  						Registry: "registry:7861",
  5158  						Tenant:   "ThisIsSomeTenantUUID",
  5159  					},
  5160  				},
  5161  			},
  5162  			errs: []verr{{
  5163  				etype: field.ErrorTypeRequired,
  5164  				field: "quobyte.volume",
  5165  			}},
  5166  		}, {
  5167  			name: "empty tenant quobyte",
  5168  			vol: core.Volume{
  5169  				Name: "quobyte",
  5170  				VolumeSource: core.VolumeSource{
  5171  					Quobyte: &core.QuobyteVolumeSource{
  5172  						Registry: "registry:7861",
  5173  						Volume:   "/test",
  5174  						Tenant:   "",
  5175  					},
  5176  				},
  5177  			},
  5178  		}, {
  5179  			name: "too long tenant quobyte",
  5180  			vol: core.Volume{
  5181  				Name: "quobyte",
  5182  				VolumeSource: core.VolumeSource{
  5183  					Quobyte: &core.QuobyteVolumeSource{
  5184  						Registry: "registry:7861",
  5185  						Volume:   "/test",
  5186  						Tenant:   "this is too long to be a valid uuid so this test has to fail on the maximum length validation of the tenant.",
  5187  					},
  5188  				},
  5189  			},
  5190  			errs: []verr{{
  5191  				etype: field.ErrorTypeRequired,
  5192  				field: "quobyte.tenant",
  5193  			}},
  5194  		},
  5195  		// AzureDisk
  5196  		{
  5197  			name: "valid AzureDisk",
  5198  			vol: core.Volume{
  5199  				Name: "azure-disk",
  5200  				VolumeSource: core.VolumeSource{
  5201  					AzureDisk: &core.AzureDiskVolumeSource{
  5202  						DiskName:    "foo",
  5203  						DataDiskURI: "https://blob/vhds/bar.vhd",
  5204  					},
  5205  				},
  5206  			},
  5207  		}, {
  5208  			name: "AzureDisk empty disk name",
  5209  			vol: core.Volume{
  5210  				Name: "azure-disk",
  5211  				VolumeSource: core.VolumeSource{
  5212  					AzureDisk: &core.AzureDiskVolumeSource{
  5213  						DiskName:    "",
  5214  						DataDiskURI: "https://blob/vhds/bar.vhd",
  5215  					},
  5216  				},
  5217  			},
  5218  			errs: []verr{{
  5219  				etype: field.ErrorTypeRequired,
  5220  				field: "azureDisk.diskName",
  5221  			}},
  5222  		}, {
  5223  			name: "AzureDisk empty disk uri",
  5224  			vol: core.Volume{
  5225  				Name: "azure-disk",
  5226  				VolumeSource: core.VolumeSource{
  5227  					AzureDisk: &core.AzureDiskVolumeSource{
  5228  						DiskName:    "foo",
  5229  						DataDiskURI: "",
  5230  					},
  5231  				},
  5232  			},
  5233  			errs: []verr{{
  5234  				etype: field.ErrorTypeRequired,
  5235  				field: "azureDisk.diskURI",
  5236  			}},
  5237  		},
  5238  		// ScaleIO
  5239  		{
  5240  			name: "valid scaleio volume",
  5241  			vol: core.Volume{
  5242  				Name: "scaleio-volume",
  5243  				VolumeSource: core.VolumeSource{
  5244  					ScaleIO: &core.ScaleIOVolumeSource{
  5245  						Gateway:    "http://abcd/efg",
  5246  						System:     "test-system",
  5247  						VolumeName: "test-vol-1",
  5248  					},
  5249  				},
  5250  			},
  5251  		}, {
  5252  			name: "ScaleIO with empty name",
  5253  			vol: core.Volume{
  5254  				Name: "scaleio-volume",
  5255  				VolumeSource: core.VolumeSource{
  5256  					ScaleIO: &core.ScaleIOVolumeSource{
  5257  						Gateway:    "http://abcd/efg",
  5258  						System:     "test-system",
  5259  						VolumeName: "",
  5260  					},
  5261  				},
  5262  			},
  5263  			errs: []verr{{
  5264  				etype: field.ErrorTypeRequired,
  5265  				field: "scaleIO.volumeName",
  5266  			}},
  5267  		}, {
  5268  			name: "ScaleIO with empty gateway",
  5269  			vol: core.Volume{
  5270  				Name: "scaleio-volume",
  5271  				VolumeSource: core.VolumeSource{
  5272  					ScaleIO: &core.ScaleIOVolumeSource{
  5273  						Gateway:    "",
  5274  						System:     "test-system",
  5275  						VolumeName: "test-vol-1",
  5276  					},
  5277  				},
  5278  			},
  5279  			errs: []verr{{
  5280  				etype: field.ErrorTypeRequired,
  5281  				field: "scaleIO.gateway",
  5282  			}},
  5283  		}, {
  5284  			name: "ScaleIO with empty system",
  5285  			vol: core.Volume{
  5286  				Name: "scaleio-volume",
  5287  				VolumeSource: core.VolumeSource{
  5288  					ScaleIO: &core.ScaleIOVolumeSource{
  5289  						Gateway:    "http://agc/efg/gateway",
  5290  						System:     "",
  5291  						VolumeName: "test-vol-1",
  5292  					},
  5293  				},
  5294  			},
  5295  			errs: []verr{{
  5296  				etype: field.ErrorTypeRequired,
  5297  				field: "scaleIO.system",
  5298  			}},
  5299  		},
  5300  		// ProjectedVolumeSource
  5301  		{
  5302  			name: "ProjectedVolumeSource more than one projection in a source",
  5303  			vol: core.Volume{
  5304  				Name: "projected-volume",
  5305  				VolumeSource: core.VolumeSource{
  5306  					Projected: &core.ProjectedVolumeSource{
  5307  						Sources: []core.VolumeProjection{{
  5308  							Secret: &core.SecretProjection{
  5309  								LocalObjectReference: core.LocalObjectReference{
  5310  									Name: "foo",
  5311  								},
  5312  							},
  5313  						}, {
  5314  							Secret: &core.SecretProjection{
  5315  								LocalObjectReference: core.LocalObjectReference{
  5316  									Name: "foo",
  5317  								},
  5318  							},
  5319  							DownwardAPI: &core.DownwardAPIProjection{},
  5320  						}},
  5321  					},
  5322  				},
  5323  			},
  5324  			errs: []verr{{
  5325  				etype: field.ErrorTypeForbidden,
  5326  				field: "projected.sources[1]",
  5327  			}},
  5328  		}, {
  5329  			name: "ProjectedVolumeSource more than one projection in a source",
  5330  			vol: core.Volume{
  5331  				Name: "projected-volume",
  5332  				VolumeSource: core.VolumeSource{
  5333  					Projected: &core.ProjectedVolumeSource{
  5334  						Sources: []core.VolumeProjection{{
  5335  							Secret: &core.SecretProjection{},
  5336  						}, {
  5337  							Secret:      &core.SecretProjection{},
  5338  							DownwardAPI: &core.DownwardAPIProjection{},
  5339  						}},
  5340  					},
  5341  				},
  5342  			},
  5343  			errs: []verr{{
  5344  				etype: field.ErrorTypeRequired,
  5345  				field: "projected.sources[0].secret.name",
  5346  			}, {
  5347  				etype: field.ErrorTypeRequired,
  5348  				field: "projected.sources[1].secret.name",
  5349  			}, {
  5350  				etype: field.ErrorTypeForbidden,
  5351  				field: "projected.sources[1]",
  5352  			}},
  5353  		},
  5354  	}
  5355  
  5356  	for _, tc := range testCases {
  5357  		t.Run(tc.name, func(t *testing.T) {
  5358  			names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"), tc.opts)
  5359  			if len(errs) != len(tc.errs) {
  5360  				t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
  5361  			}
  5362  			if len(errs) == 0 && (len(names) > 1 || !IsMatchedVolume(tc.vol.Name, names)) {
  5363  				t.Errorf("wrong names result: %v", names)
  5364  			}
  5365  			for i, err := range errs {
  5366  				expErr := tc.errs[i]
  5367  				if err.Type != expErr.etype {
  5368  					t.Errorf("unexpected error type:\n\twant: %q\n\t got: %q", expErr.etype, err.Type)
  5369  				}
  5370  				if !strings.HasSuffix(err.Field, "."+expErr.field) {
  5371  					t.Errorf("unexpected error field:\n\twant: %q\n\t got: %q", expErr.field, err.Field)
  5372  				}
  5373  				if !strings.Contains(err.Detail, expErr.detail) {
  5374  					t.Errorf("unexpected error detail:\n\twant: %q\n\t got: %q", expErr.detail, err.Detail)
  5375  				}
  5376  			}
  5377  		})
  5378  	}
  5379  
  5380  	dupsCase := []core.Volume{
  5381  		{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  5382  		{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  5383  	}
  5384  	_, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"), PodValidationOptions{})
  5385  	if len(errs) == 0 {
  5386  		t.Errorf("expected error")
  5387  	} else if len(errs) != 1 {
  5388  		t.Errorf("expected 1 error, got %d: %v", len(errs), errs)
  5389  	} else if errs[0].Type != field.ErrorTypeDuplicate {
  5390  		t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
  5391  	}
  5392  
  5393  	// Validate HugePages medium type for EmptyDir
  5394  	hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
  5395  
  5396  	// Enable HugePages
  5397  	if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil, PodValidationOptions{}); len(errs) != 0 {
  5398  		t.Errorf("Unexpected error when HugePages feature is enabled.")
  5399  	}
  5400  
  5401  }
  5402  
  5403  func TestHugePagesIsolation(t *testing.T) {
  5404  	testCases := map[string]struct {
  5405  		pod         *core.Pod
  5406  		expectError bool
  5407  	}{
  5408  		"Valid: request hugepages-2Mi": {
  5409  			pod: &core.Pod{
  5410  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
  5411  				Spec: core.PodSpec{
  5412  					Containers: []core.Container{{
  5413  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5414  						Resources: core.ResourceRequirements{
  5415  							Requests: core.ResourceList{
  5416  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5417  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5418  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5419  							},
  5420  							Limits: core.ResourceList{
  5421  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5422  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5423  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5424  							},
  5425  						},
  5426  					}},
  5427  					RestartPolicy: core.RestartPolicyAlways,
  5428  					DNSPolicy:     core.DNSClusterFirst,
  5429  				},
  5430  			},
  5431  		},
  5432  		"Valid: request more than one hugepages size": {
  5433  			pod: &core.Pod{
  5434  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5435  				Spec: core.PodSpec{
  5436  					Containers: []core.Container{{
  5437  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5438  						Resources: core.ResourceRequirements{
  5439  							Requests: core.ResourceList{
  5440  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5441  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5442  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5443  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5444  							},
  5445  							Limits: core.ResourceList{
  5446  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5447  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5448  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5449  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5450  							},
  5451  						},
  5452  					}},
  5453  					RestartPolicy: core.RestartPolicyAlways,
  5454  					DNSPolicy:     core.DNSClusterFirst,
  5455  				},
  5456  			},
  5457  			expectError: false,
  5458  		},
  5459  		"Valid: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": {
  5460  			pod: &core.Pod{
  5461  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
  5462  				Spec: core.PodSpec{
  5463  					Containers: []core.Container{{
  5464  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5465  						Resources: core.ResourceRequirements{
  5466  							Requests: core.ResourceList{
  5467  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5468  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5469  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5470  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5471  							},
  5472  							Limits: core.ResourceList{
  5473  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5474  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5475  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5476  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5477  							},
  5478  						},
  5479  					}},
  5480  					RestartPolicy: core.RestartPolicyAlways,
  5481  					DNSPolicy:     core.DNSClusterFirst,
  5482  				},
  5483  			},
  5484  		},
  5485  		"Invalid: not requesting cpu and memory": {
  5486  			pod: &core.Pod{
  5487  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
  5488  				Spec: core.PodSpec{
  5489  					Containers: []core.Container{{
  5490  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5491  						Resources: core.ResourceRequirements{
  5492  							Requests: core.ResourceList{
  5493  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5494  							},
  5495  							Limits: core.ResourceList{
  5496  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5497  							},
  5498  						},
  5499  					}},
  5500  					RestartPolicy: core.RestartPolicyAlways,
  5501  					DNSPolicy:     core.DNSClusterFirst,
  5502  				},
  5503  			},
  5504  			expectError: true,
  5505  		},
  5506  		"Invalid: request 1Gi hugepages-2Mi but limit 2Gi": {
  5507  			pod: &core.Pod{
  5508  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5509  				Spec: core.PodSpec{
  5510  					Containers: []core.Container{{
  5511  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5512  						Resources: core.ResourceRequirements{
  5513  							Requests: core.ResourceList{
  5514  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5515  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5516  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5517  							},
  5518  							Limits: core.ResourceList{
  5519  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5520  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5521  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("2Gi"),
  5522  							},
  5523  						},
  5524  					}},
  5525  					RestartPolicy: core.RestartPolicyAlways,
  5526  					DNSPolicy:     core.DNSClusterFirst,
  5527  				},
  5528  			},
  5529  			expectError: true,
  5530  		},
  5531  	}
  5532  	for tcName, tc := range testCases {
  5533  		t.Run(tcName, func(t *testing.T) {
  5534  			errs := ValidatePodCreate(tc.pod, PodValidationOptions{})
  5535  			if tc.expectError && len(errs) == 0 {
  5536  				t.Errorf("Unexpected success")
  5537  			}
  5538  			if !tc.expectError && len(errs) != 0 {
  5539  				t.Errorf("Unexpected error(s): %v", errs)
  5540  			}
  5541  		})
  5542  	}
  5543  }
  5544  
  5545  func TestPVCVolumeMode(t *testing.T) {
  5546  	block := core.PersistentVolumeBlock
  5547  	file := core.PersistentVolumeFilesystem
  5548  	fake := core.PersistentVolumeMode("fake")
  5549  	empty := core.PersistentVolumeMode("")
  5550  
  5551  	// Success Cases
  5552  	successCasesPVC := map[string]*core.PersistentVolumeClaim{
  5553  		"valid block value":      createTestVolModePVC(&block),
  5554  		"valid filesystem value": createTestVolModePVC(&file),
  5555  		"valid nil value":        createTestVolModePVC(nil),
  5556  	}
  5557  	for k, v := range successCasesPVC {
  5558  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5559  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 {
  5560  			t.Errorf("expected success for %s", k)
  5561  		}
  5562  	}
  5563  
  5564  	// Error Cases
  5565  	errorCasesPVC := map[string]*core.PersistentVolumeClaim{
  5566  		"invalid value": createTestVolModePVC(&fake),
  5567  		"empty value":   createTestVolModePVC(&empty),
  5568  	}
  5569  	for k, v := range errorCasesPVC {
  5570  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5571  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 {
  5572  			t.Errorf("expected failure for %s", k)
  5573  		}
  5574  	}
  5575  }
  5576  
  5577  func TestPVVolumeMode(t *testing.T) {
  5578  	block := core.PersistentVolumeBlock
  5579  	file := core.PersistentVolumeFilesystem
  5580  	fake := core.PersistentVolumeMode("fake")
  5581  	empty := core.PersistentVolumeMode("")
  5582  
  5583  	// Success Cases
  5584  	successCasesPV := map[string]*core.PersistentVolume{
  5585  		"valid block value":      createTestVolModePV(&block),
  5586  		"valid filesystem value": createTestVolModePV(&file),
  5587  		"valid nil value":        createTestVolModePV(nil),
  5588  	}
  5589  	for k, v := range successCasesPV {
  5590  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5591  		if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 {
  5592  			t.Errorf("expected success for %s", k)
  5593  		}
  5594  	}
  5595  
  5596  	// Error Cases
  5597  	errorCasesPV := map[string]*core.PersistentVolume{
  5598  		"invalid value": createTestVolModePV(&fake),
  5599  		"empty value":   createTestVolModePV(&empty),
  5600  	}
  5601  	for k, v := range errorCasesPV {
  5602  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5603  		if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 {
  5604  			t.Errorf("expected failure for %s", k)
  5605  		}
  5606  	}
  5607  }
  5608  
  5609  func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
  5610  	validName := "valid-storage-class"
  5611  
  5612  	pvc := core.PersistentVolumeClaim{
  5613  		ObjectMeta: metav1.ObjectMeta{
  5614  			Name:      "foo",
  5615  			Namespace: "default",
  5616  		},
  5617  		Spec: core.PersistentVolumeClaimSpec{
  5618  			Resources: core.VolumeResourceRequirements{
  5619  				Requests: core.ResourceList{
  5620  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5621  				},
  5622  			},
  5623  			AccessModes:      []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5624  			StorageClassName: &validName,
  5625  			VolumeMode:       vmode,
  5626  		},
  5627  	}
  5628  	return &pvc
  5629  }
  5630  
  5631  func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
  5632  
  5633  	// PersistentVolume with VolumeMode set (valid and invalid)
  5634  	pv := core.PersistentVolume{
  5635  		ObjectMeta: metav1.ObjectMeta{
  5636  			Name:      "foo",
  5637  			Namespace: "",
  5638  		},
  5639  		Spec: core.PersistentVolumeSpec{
  5640  			Capacity: core.ResourceList{
  5641  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5642  			},
  5643  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5644  			PersistentVolumeSource: core.PersistentVolumeSource{
  5645  				HostPath: &core.HostPathVolumeSource{
  5646  					Path: "/foo",
  5647  					Type: newHostPathType(string(core.HostPathDirectory)),
  5648  				},
  5649  			},
  5650  			StorageClassName: "test-storage-class",
  5651  			VolumeMode:       vmode,
  5652  		},
  5653  	}
  5654  	return &pv
  5655  }
  5656  
  5657  func createTestPV() *core.PersistentVolume {
  5658  
  5659  	// PersistentVolume with VolumeMode set (valid and invalid)
  5660  	pv := core.PersistentVolume{
  5661  		ObjectMeta: metav1.ObjectMeta{
  5662  			Name:      "foo",
  5663  			Namespace: "",
  5664  		},
  5665  		Spec: core.PersistentVolumeSpec{
  5666  			Capacity: core.ResourceList{
  5667  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5668  			},
  5669  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5670  			PersistentVolumeSource: core.PersistentVolumeSource{
  5671  				HostPath: &core.HostPathVolumeSource{
  5672  					Path: "/foo",
  5673  					Type: newHostPathType(string(core.HostPathDirectory)),
  5674  				},
  5675  			},
  5676  			StorageClassName: "test-storage-class",
  5677  		},
  5678  	}
  5679  	return &pv
  5680  }
  5681  
  5682  func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5683  
  5684  	testCases := []core.VolumeSource{
  5685  		{EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}},
  5686  	}
  5687  
  5688  	for _, tc := range testCases {
  5689  		if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil, PodValidationOptions{}); len(errs) != 0 {
  5690  			t.Errorf("expected success: %v", errs)
  5691  		}
  5692  	}
  5693  
  5694  	containerLimitCase := core.ResourceRequirements{
  5695  		Limits: core.ResourceList{
  5696  			core.ResourceEphemeralStorage: *resource.NewMilliQuantity(
  5697  				int64(40000),
  5698  				resource.BinarySI),
  5699  		},
  5700  	}
  5701  	if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
  5702  		t.Errorf("expected success: %v", errs)
  5703  	}
  5704  }
  5705  
  5706  func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5707  	spec := core.ResourceQuotaSpec{
  5708  		Hard: core.ResourceList{
  5709  			core.ResourceCPU:                      resource.MustParse("100"),
  5710  			core.ResourceMemory:                   resource.MustParse("10000"),
  5711  			core.ResourceRequestsCPU:              resource.MustParse("100"),
  5712  			core.ResourceRequestsMemory:           resource.MustParse("10000"),
  5713  			core.ResourceLimitsCPU:                resource.MustParse("100"),
  5714  			core.ResourceLimitsMemory:             resource.MustParse("10000"),
  5715  			core.ResourcePods:                     resource.MustParse("10"),
  5716  			core.ResourceServices:                 resource.MustParse("0"),
  5717  			core.ResourceReplicationControllers:   resource.MustParse("10"),
  5718  			core.ResourceQuotas:                   resource.MustParse("10"),
  5719  			core.ResourceConfigMaps:               resource.MustParse("10"),
  5720  			core.ResourceSecrets:                  resource.MustParse("10"),
  5721  			core.ResourceEphemeralStorage:         resource.MustParse("10000"),
  5722  			core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"),
  5723  			core.ResourceLimitsEphemeralStorage:   resource.MustParse("10000"),
  5724  		},
  5725  	}
  5726  	resourceQuota := &core.ResourceQuota{
  5727  		ObjectMeta: metav1.ObjectMeta{
  5728  			Name:      "abc",
  5729  			Namespace: "foo",
  5730  		},
  5731  		Spec: spec,
  5732  	}
  5733  
  5734  	if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
  5735  		t.Errorf("expected success: %v", errs)
  5736  	}
  5737  }
  5738  
  5739  func TestValidatePorts(t *testing.T) {
  5740  	successCase := []core.ContainerPort{
  5741  		{Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  5742  		{Name: "easy", ContainerPort: 82, Protocol: "TCP"},
  5743  		{Name: "as", ContainerPort: 83, Protocol: "UDP"},
  5744  		{Name: "do-re-me", ContainerPort: 84, Protocol: "SCTP"},
  5745  		{ContainerPort: 85, Protocol: "TCP"},
  5746  	}
  5747  	if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 {
  5748  		t.Errorf("expected success: %v", errs)
  5749  	}
  5750  
  5751  	nonCanonicalCase := []core.ContainerPort{
  5752  		{ContainerPort: 80, Protocol: "TCP"},
  5753  	}
  5754  	if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 {
  5755  		t.Errorf("expected success: %v", errs)
  5756  	}
  5757  
  5758  	errorCases := map[string]struct {
  5759  		P []core.ContainerPort
  5760  		T field.ErrorType
  5761  		F string
  5762  		D string
  5763  	}{
  5764  		"name > 15 characters": {
  5765  			[]core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
  5766  			field.ErrorTypeInvalid,
  5767  			"name", "15",
  5768  		},
  5769  		"name contains invalid characters": {
  5770  			[]core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
  5771  			field.ErrorTypeInvalid,
  5772  			"name", "alpha-numeric",
  5773  		},
  5774  		"name is a number": {
  5775  			[]core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
  5776  			field.ErrorTypeInvalid,
  5777  			"name", "at least one letter",
  5778  		},
  5779  		"name not unique": {
  5780  			[]core.ContainerPort{
  5781  				{Name: "abc", ContainerPort: 80, Protocol: "TCP"},
  5782  				{Name: "abc", ContainerPort: 81, Protocol: "TCP"},
  5783  			},
  5784  			field.ErrorTypeDuplicate,
  5785  			"[1].name", "",
  5786  		},
  5787  		"zero container port": {
  5788  			[]core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
  5789  			field.ErrorTypeRequired,
  5790  			"containerPort", "",
  5791  		},
  5792  		"invalid container port": {
  5793  			[]core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
  5794  			field.ErrorTypeInvalid,
  5795  			"containerPort", "between",
  5796  		},
  5797  		"invalid host port": {
  5798  			[]core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
  5799  			field.ErrorTypeInvalid,
  5800  			"hostPort", "between",
  5801  		},
  5802  		"invalid protocol case": {
  5803  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
  5804  			field.ErrorTypeNotSupported,
  5805  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5806  		},
  5807  		"invalid protocol": {
  5808  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
  5809  			field.ErrorTypeNotSupported,
  5810  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5811  		},
  5812  		"protocol required": {
  5813  			[]core.ContainerPort{{Name: "abc", ContainerPort: 80}},
  5814  			field.ErrorTypeRequired,
  5815  			"protocol", "",
  5816  		},
  5817  	}
  5818  	for k, v := range errorCases {
  5819  		errs := validateContainerPorts(v.P, field.NewPath("field"))
  5820  		if len(errs) == 0 {
  5821  			t.Errorf("expected failure for %s", k)
  5822  		}
  5823  		for i := range errs {
  5824  			if errs[i].Type != v.T {
  5825  				t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
  5826  			}
  5827  			if !strings.Contains(errs[i].Field, v.F) {
  5828  				t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
  5829  			}
  5830  			if !strings.Contains(errs[i].Detail, v.D) {
  5831  				t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
  5832  			}
  5833  		}
  5834  	}
  5835  }
  5836  
  5837  func TestLocalStorageEnvWithFeatureGate(t *testing.T) {
  5838  	testCases := []core.EnvVar{{
  5839  		Name: "ephemeral-storage-limits",
  5840  		ValueFrom: &core.EnvVarSource{
  5841  			ResourceFieldRef: &core.ResourceFieldSelector{
  5842  				ContainerName: "test-container",
  5843  				Resource:      "limits.ephemeral-storage",
  5844  			},
  5845  		},
  5846  	}, {
  5847  		Name: "ephemeral-storage-requests",
  5848  		ValueFrom: &core.EnvVarSource{
  5849  			ResourceFieldRef: &core.ResourceFieldSelector{
  5850  				ContainerName: "test-container",
  5851  				Resource:      "requests.ephemeral-storage",
  5852  			},
  5853  		},
  5854  	},
  5855  	}
  5856  	for _, testCase := range testCases {
  5857  		if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  5858  			t.Errorf("expected success, got: %v", errs)
  5859  		}
  5860  	}
  5861  }
  5862  
  5863  func TestHugePagesEnv(t *testing.T) {
  5864  	testCases := []core.EnvVar{{
  5865  		Name: "hugepages-limits",
  5866  		ValueFrom: &core.EnvVarSource{
  5867  			ResourceFieldRef: &core.ResourceFieldSelector{
  5868  				ContainerName: "test-container",
  5869  				Resource:      "limits.hugepages-2Mi",
  5870  			},
  5871  		},
  5872  	}, {
  5873  		Name: "hugepages-requests",
  5874  		ValueFrom: &core.EnvVarSource{
  5875  			ResourceFieldRef: &core.ResourceFieldSelector{
  5876  				ContainerName: "test-container",
  5877  				Resource:      "requests.hugepages-2Mi",
  5878  			},
  5879  		},
  5880  	},
  5881  	}
  5882  	// enable gate
  5883  	for _, testCase := range testCases {
  5884  		t.Run(testCase.Name, func(t *testing.T) {
  5885  			opts := PodValidationOptions{}
  5886  			if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), opts); len(errs) != 0 {
  5887  				t.Errorf("expected success, got: %v", errs)
  5888  			}
  5889  		})
  5890  	}
  5891  }
  5892  
  5893  func TestRelaxedValidateEnv(t *testing.T) {
  5894  	successCase := []core.EnvVar{
  5895  		{Name: "!\"#$%&'()", Value: "value"},
  5896  		{Name: "* +,-./0123456789", Value: "value"},
  5897  		{Name: ":;<>?@", Value: "value"},
  5898  		{Name: "ABCDEFG", Value: "value"},
  5899  		{Name: "abcdefghijklmn", Value: "value"},
  5900  		{Name: "[\\]^_`{}|~", Value: "value"},
  5901  		{
  5902  			Name: "!\"#$%&'()",
  5903  			ValueFrom: &core.EnvVarSource{
  5904  				FieldRef: &core.ObjectFieldSelector{
  5905  					APIVersion: "v1",
  5906  					FieldPath:  "metadata.annotations['key']",
  5907  				},
  5908  			},
  5909  		}, {
  5910  			Name: "!\"#$%&'()",
  5911  			ValueFrom: &core.EnvVarSource{
  5912  				FieldRef: &core.ObjectFieldSelector{
  5913  					APIVersion: "v1",
  5914  					FieldPath:  "metadata.labels['key']",
  5915  				},
  5916  			},
  5917  		}, {
  5918  			Name: "* +,-./0123456789",
  5919  			ValueFrom: &core.EnvVarSource{
  5920  				FieldRef: &core.ObjectFieldSelector{
  5921  					APIVersion: "v1",
  5922  					FieldPath:  "metadata.name",
  5923  				},
  5924  			},
  5925  		}, {
  5926  			Name: "* +,-./0123456789",
  5927  			ValueFrom: &core.EnvVarSource{
  5928  				FieldRef: &core.ObjectFieldSelector{
  5929  					APIVersion: "v1",
  5930  					FieldPath:  "metadata.namespace",
  5931  				},
  5932  			},
  5933  		}, {
  5934  			Name: "* +,-./0123456789",
  5935  			ValueFrom: &core.EnvVarSource{
  5936  				FieldRef: &core.ObjectFieldSelector{
  5937  					APIVersion: "v1",
  5938  					FieldPath:  "metadata.uid",
  5939  				},
  5940  			},
  5941  		}, {
  5942  			Name: ":;<>?@",
  5943  			ValueFrom: &core.EnvVarSource{
  5944  				FieldRef: &core.ObjectFieldSelector{
  5945  					APIVersion: "v1",
  5946  					FieldPath:  "spec.nodeName",
  5947  				},
  5948  			},
  5949  		}, {
  5950  			Name: ":;<>?@",
  5951  			ValueFrom: &core.EnvVarSource{
  5952  				FieldRef: &core.ObjectFieldSelector{
  5953  					APIVersion: "v1",
  5954  					FieldPath:  "spec.serviceAccountName",
  5955  				},
  5956  			},
  5957  		}, {
  5958  			Name: ":;<>?@",
  5959  			ValueFrom: &core.EnvVarSource{
  5960  				FieldRef: &core.ObjectFieldSelector{
  5961  					APIVersion: "v1",
  5962  					FieldPath:  "status.hostIP",
  5963  				},
  5964  			},
  5965  		}, {
  5966  			Name: ":;<>?@",
  5967  			ValueFrom: &core.EnvVarSource{
  5968  				FieldRef: &core.ObjectFieldSelector{
  5969  					APIVersion: "v1",
  5970  					FieldPath:  "status.podIP",
  5971  				},
  5972  			},
  5973  		}, {
  5974  			Name: "abcdefghijklmn",
  5975  			ValueFrom: &core.EnvVarSource{
  5976  				FieldRef: &core.ObjectFieldSelector{
  5977  					APIVersion: "v1",
  5978  					FieldPath:  "status.podIPs",
  5979  				},
  5980  			},
  5981  		},
  5982  		{
  5983  			Name: "abcdefghijklmn",
  5984  			ValueFrom: &core.EnvVarSource{
  5985  				SecretKeyRef: &core.SecretKeySelector{
  5986  					LocalObjectReference: core.LocalObjectReference{
  5987  						Name: "some-secret",
  5988  					},
  5989  					Key: "secret-key",
  5990  				},
  5991  			},
  5992  		}, {
  5993  			Name: "!\"#$%&'()",
  5994  			ValueFrom: &core.EnvVarSource{
  5995  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  5996  					LocalObjectReference: core.LocalObjectReference{
  5997  						Name: "some-config-map",
  5998  					},
  5999  					Key: "some-key",
  6000  				},
  6001  			},
  6002  		},
  6003  	}
  6004  	if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6005  		t.Errorf("expected success, got: %v", errs)
  6006  	}
  6007  
  6008  	errorCases := []struct {
  6009  		name          string
  6010  		envs          []core.EnvVar
  6011  		expectedError string
  6012  	}{{
  6013  		name:          "illegal character",
  6014  		envs:          []core.EnvVar{{Name: "=abc"}},
  6015  		expectedError: `[0].name: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
  6016  	}, {
  6017  		name:          "zero-length name",
  6018  		envs:          []core.EnvVar{{Name: ""}},
  6019  		expectedError: "[0].name: Required value",
  6020  	}, {
  6021  		name: "value and valueFrom specified",
  6022  		envs: []core.EnvVar{{
  6023  			Name:  "abc",
  6024  			Value: "foo",
  6025  			ValueFrom: &core.EnvVarSource{
  6026  				FieldRef: &core.ObjectFieldSelector{
  6027  					APIVersion: "v1",
  6028  					FieldPath:  "metadata.name",
  6029  				},
  6030  			},
  6031  		}},
  6032  		expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
  6033  	}, {
  6034  		name: "valueFrom without a source",
  6035  		envs: []core.EnvVar{{
  6036  			Name:      "abc",
  6037  			ValueFrom: &core.EnvVarSource{},
  6038  		}},
  6039  		expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
  6040  	}, {
  6041  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6042  		envs: []core.EnvVar{{
  6043  			Name: "abc",
  6044  			ValueFrom: &core.EnvVarSource{
  6045  				FieldRef: &core.ObjectFieldSelector{
  6046  					APIVersion: "v1",
  6047  					FieldPath:  "metadata.name",
  6048  				},
  6049  				SecretKeyRef: &core.SecretKeySelector{
  6050  					LocalObjectReference: core.LocalObjectReference{
  6051  						Name: "a-secret",
  6052  					},
  6053  					Key: "a-key",
  6054  				},
  6055  			},
  6056  		}},
  6057  		expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
  6058  	}, {
  6059  		name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
  6060  		envs: []core.EnvVar{{
  6061  			Name: "some_var_name",
  6062  			ValueFrom: &core.EnvVarSource{
  6063  				FieldRef: &core.ObjectFieldSelector{
  6064  					APIVersion: "v1",
  6065  					FieldPath:  "metadata.name",
  6066  				},
  6067  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6068  					LocalObjectReference: core.LocalObjectReference{
  6069  						Name: "some-config-map",
  6070  					},
  6071  					Key: "some-key",
  6072  				},
  6073  			},
  6074  		}},
  6075  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6076  	}, {
  6077  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6078  		envs: []core.EnvVar{{
  6079  			Name: "abc",
  6080  			ValueFrom: &core.EnvVarSource{
  6081  				FieldRef: &core.ObjectFieldSelector{
  6082  					APIVersion: "v1",
  6083  					FieldPath:  "metadata.name",
  6084  				},
  6085  				SecretKeyRef: &core.SecretKeySelector{
  6086  					LocalObjectReference: core.LocalObjectReference{
  6087  						Name: "a-secret",
  6088  					},
  6089  					Key: "a-key",
  6090  				},
  6091  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6092  					LocalObjectReference: core.LocalObjectReference{
  6093  						Name: "some-config-map",
  6094  					},
  6095  					Key: "some-key",
  6096  				},
  6097  			},
  6098  		}},
  6099  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6100  	}, {
  6101  		name: "valueFrom.secretKeyRef.name invalid",
  6102  		envs: []core.EnvVar{{
  6103  			Name: "abc",
  6104  			ValueFrom: &core.EnvVarSource{
  6105  				SecretKeyRef: &core.SecretKeySelector{
  6106  					LocalObjectReference: core.LocalObjectReference{
  6107  						Name: "$%^&*#",
  6108  					},
  6109  					Key: "a-key",
  6110  				},
  6111  			},
  6112  		}},
  6113  	}, {
  6114  		name: "valueFrom.configMapKeyRef.name invalid",
  6115  		envs: []core.EnvVar{{
  6116  			Name: "abc",
  6117  			ValueFrom: &core.EnvVarSource{
  6118  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6119  					LocalObjectReference: core.LocalObjectReference{
  6120  						Name: "$%^&*#",
  6121  					},
  6122  					Key: "some-key",
  6123  				},
  6124  			},
  6125  		}},
  6126  	}, {
  6127  		name: "missing FieldPath on ObjectFieldSelector",
  6128  		envs: []core.EnvVar{{
  6129  			Name: "abc",
  6130  			ValueFrom: &core.EnvVarSource{
  6131  				FieldRef: &core.ObjectFieldSelector{
  6132  					APIVersion: "v1",
  6133  				},
  6134  			},
  6135  		}},
  6136  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
  6137  	}, {
  6138  		name: "missing APIVersion on ObjectFieldSelector",
  6139  		envs: []core.EnvVar{{
  6140  			Name: "abc",
  6141  			ValueFrom: &core.EnvVarSource{
  6142  				FieldRef: &core.ObjectFieldSelector{
  6143  					FieldPath: "metadata.name",
  6144  				},
  6145  			},
  6146  		}},
  6147  		expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
  6148  	}, {
  6149  		name: "invalid fieldPath",
  6150  		envs: []core.EnvVar{{
  6151  			Name: "abc",
  6152  			ValueFrom: &core.EnvVarSource{
  6153  				FieldRef: &core.ObjectFieldSelector{
  6154  					FieldPath:  "metadata.whoops",
  6155  					APIVersion: "v1",
  6156  				},
  6157  			},
  6158  		}},
  6159  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
  6160  	}, {
  6161  		name: "metadata.name with subscript",
  6162  		envs: []core.EnvVar{{
  6163  			Name: "labels",
  6164  			ValueFrom: &core.EnvVarSource{
  6165  				FieldRef: &core.ObjectFieldSelector{
  6166  					FieldPath:  "metadata.name['key']",
  6167  					APIVersion: "v1",
  6168  				},
  6169  			},
  6170  		}},
  6171  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
  6172  	}, {
  6173  		name: "metadata.labels without subscript",
  6174  		envs: []core.EnvVar{{
  6175  			Name: "labels",
  6176  			ValueFrom: &core.EnvVarSource{
  6177  				FieldRef: &core.ObjectFieldSelector{
  6178  					FieldPath:  "metadata.labels",
  6179  					APIVersion: "v1",
  6180  				},
  6181  			},
  6182  		}},
  6183  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6184  	}, {
  6185  		name: "metadata.annotations without subscript",
  6186  		envs: []core.EnvVar{{
  6187  			Name: "abc",
  6188  			ValueFrom: &core.EnvVarSource{
  6189  				FieldRef: &core.ObjectFieldSelector{
  6190  					FieldPath:  "metadata.annotations",
  6191  					APIVersion: "v1",
  6192  				},
  6193  			},
  6194  		}},
  6195  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6196  	}, {
  6197  		name: "metadata.annotations with invalid key",
  6198  		envs: []core.EnvVar{{
  6199  			Name: "abc",
  6200  			ValueFrom: &core.EnvVarSource{
  6201  				FieldRef: &core.ObjectFieldSelector{
  6202  					FieldPath:  "metadata.annotations['invalid~key']",
  6203  					APIVersion: "v1",
  6204  				},
  6205  			},
  6206  		}},
  6207  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
  6208  	}, {
  6209  		name: "metadata.labels with invalid key",
  6210  		envs: []core.EnvVar{{
  6211  			Name: "abc",
  6212  			ValueFrom: &core.EnvVarSource{
  6213  				FieldRef: &core.ObjectFieldSelector{
  6214  					FieldPath:  "metadata.labels['Www.k8s.io/test']",
  6215  					APIVersion: "v1",
  6216  				},
  6217  			},
  6218  		}},
  6219  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
  6220  	}, {
  6221  		name: "unsupported fieldPath",
  6222  		envs: []core.EnvVar{{
  6223  			Name: "abc",
  6224  			ValueFrom: &core.EnvVarSource{
  6225  				FieldRef: &core.ObjectFieldSelector{
  6226  					FieldPath:  "status.phase",
  6227  					APIVersion: "v1",
  6228  				},
  6229  			},
  6230  		}},
  6231  		expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6232  	},
  6233  	}
  6234  	for _, tc := range errorCases {
  6235  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
  6236  			t.Errorf("expected failure for %s", tc.name)
  6237  		} else {
  6238  			for i := range errs {
  6239  				str := errs[i].Error()
  6240  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6241  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6242  				}
  6243  			}
  6244  		}
  6245  	}
  6246  }
  6247  
  6248  func TestValidateEnv(t *testing.T) {
  6249  	successCase := []core.EnvVar{
  6250  		{Name: "abc", Value: "value"},
  6251  		{Name: "ABC", Value: "value"},
  6252  		{Name: "AbC_123", Value: "value"},
  6253  		{Name: "abc", Value: ""},
  6254  		{Name: "a.b.c", Value: "value"},
  6255  		{Name: "a-b-c", Value: "value"}, {
  6256  			Name: "abc",
  6257  			ValueFrom: &core.EnvVarSource{
  6258  				FieldRef: &core.ObjectFieldSelector{
  6259  					APIVersion: "v1",
  6260  					FieldPath:  "metadata.annotations['key']",
  6261  				},
  6262  			},
  6263  		}, {
  6264  			Name: "abc",
  6265  			ValueFrom: &core.EnvVarSource{
  6266  				FieldRef: &core.ObjectFieldSelector{
  6267  					APIVersion: "v1",
  6268  					FieldPath:  "metadata.labels['key']",
  6269  				},
  6270  			},
  6271  		}, {
  6272  			Name: "abc",
  6273  			ValueFrom: &core.EnvVarSource{
  6274  				FieldRef: &core.ObjectFieldSelector{
  6275  					APIVersion: "v1",
  6276  					FieldPath:  "metadata.name",
  6277  				},
  6278  			},
  6279  		}, {
  6280  			Name: "abc",
  6281  			ValueFrom: &core.EnvVarSource{
  6282  				FieldRef: &core.ObjectFieldSelector{
  6283  					APIVersion: "v1",
  6284  					FieldPath:  "metadata.namespace",
  6285  				},
  6286  			},
  6287  		}, {
  6288  			Name: "abc",
  6289  			ValueFrom: &core.EnvVarSource{
  6290  				FieldRef: &core.ObjectFieldSelector{
  6291  					APIVersion: "v1",
  6292  					FieldPath:  "metadata.uid",
  6293  				},
  6294  			},
  6295  		}, {
  6296  			Name: "abc",
  6297  			ValueFrom: &core.EnvVarSource{
  6298  				FieldRef: &core.ObjectFieldSelector{
  6299  					APIVersion: "v1",
  6300  					FieldPath:  "spec.nodeName",
  6301  				},
  6302  			},
  6303  		}, {
  6304  			Name: "abc",
  6305  			ValueFrom: &core.EnvVarSource{
  6306  				FieldRef: &core.ObjectFieldSelector{
  6307  					APIVersion: "v1",
  6308  					FieldPath:  "spec.serviceAccountName",
  6309  				},
  6310  			},
  6311  		}, {
  6312  			Name: "abc",
  6313  			ValueFrom: &core.EnvVarSource{
  6314  				FieldRef: &core.ObjectFieldSelector{
  6315  					APIVersion: "v1",
  6316  					FieldPath:  "status.hostIP",
  6317  				},
  6318  			},
  6319  		}, {
  6320  			Name: "abc",
  6321  			ValueFrom: &core.EnvVarSource{
  6322  				FieldRef: &core.ObjectFieldSelector{
  6323  					APIVersion: "v1",
  6324  					FieldPath:  "status.podIP",
  6325  				},
  6326  			},
  6327  		}, {
  6328  			Name: "abc",
  6329  			ValueFrom: &core.EnvVarSource{
  6330  				FieldRef: &core.ObjectFieldSelector{
  6331  					APIVersion: "v1",
  6332  					FieldPath:  "status.podIPs",
  6333  				},
  6334  			},
  6335  		}, {
  6336  			Name: "secret_value",
  6337  			ValueFrom: &core.EnvVarSource{
  6338  				SecretKeyRef: &core.SecretKeySelector{
  6339  					LocalObjectReference: core.LocalObjectReference{
  6340  						Name: "some-secret",
  6341  					},
  6342  					Key: "secret-key",
  6343  				},
  6344  			},
  6345  		}, {
  6346  			Name: "ENV_VAR_1",
  6347  			ValueFrom: &core.EnvVarSource{
  6348  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6349  					LocalObjectReference: core.LocalObjectReference{
  6350  						Name: "some-config-map",
  6351  					},
  6352  					Key: "some-key",
  6353  				},
  6354  			},
  6355  		},
  6356  	}
  6357  	if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  6358  		t.Errorf("expected success, got: %v", errs)
  6359  	}
  6360  
  6361  	updateSuccessCase := []core.EnvVar{
  6362  		{Name: "!\"#$%&'()", Value: "value"},
  6363  		{Name: "* +,-./0123456789", Value: "value"},
  6364  		{Name: ":;<>?@", Value: "value"},
  6365  		{Name: "ABCDEFG", Value: "value"},
  6366  		{Name: "abcdefghijklmn", Value: "value"},
  6367  		{Name: "[\\]^_`{}|~", Value: "value"},
  6368  	}
  6369  
  6370  	if errs := ValidateEnv(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6371  		t.Errorf("expected success, got: %v", errs)
  6372  	}
  6373  
  6374  	updateErrorCase := []struct {
  6375  		name          string
  6376  		envs          []core.EnvVar
  6377  		expectedError string
  6378  	}{
  6379  		{
  6380  			name: "invalid name a",
  6381  			envs: []core.EnvVar{
  6382  				{Name: "!\"#$%&'()", Value: "value"},
  6383  			},
  6384  			expectedError: `field[0].name: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
  6385  		},
  6386  		{
  6387  			name: "invalid name b",
  6388  			envs: []core.EnvVar{
  6389  				{Name: "* +,-./0123456789", Value: "value"},
  6390  			},
  6391  			expectedError: `field[0].name: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
  6392  		},
  6393  		{
  6394  			name: "invalid name c",
  6395  			envs: []core.EnvVar{
  6396  				{Name: ":;<>?@", Value: "value"},
  6397  			},
  6398  			expectedError: `field[0].name: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
  6399  		},
  6400  		{
  6401  			name: "invalid name d",
  6402  			envs: []core.EnvVar{
  6403  				{Name: "[\\]^_{}|~", Value: "value"},
  6404  			},
  6405  			expectedError: `field[0].name: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
  6406  		},
  6407  	}
  6408  
  6409  	for _, tc := range updateErrorCase {
  6410  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6411  			t.Errorf("expected failure for %s", tc.name)
  6412  		} else {
  6413  			for i := range errs {
  6414  				str := errs[i].Error()
  6415  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6416  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6417  				}
  6418  			}
  6419  		}
  6420  	}
  6421  
  6422  	errorCases := []struct {
  6423  		name          string
  6424  		envs          []core.EnvVar
  6425  		expectedError string
  6426  	}{{
  6427  		name:          "zero-length name",
  6428  		envs:          []core.EnvVar{{Name: ""}},
  6429  		expectedError: "[0].name: Required value",
  6430  	}, {
  6431  		name: "value and valueFrom specified",
  6432  		envs: []core.EnvVar{{
  6433  			Name:  "abc",
  6434  			Value: "foo",
  6435  			ValueFrom: &core.EnvVarSource{
  6436  				FieldRef: &core.ObjectFieldSelector{
  6437  					APIVersion: "v1",
  6438  					FieldPath:  "metadata.name",
  6439  				},
  6440  			},
  6441  		}},
  6442  		expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
  6443  	}, {
  6444  		name: "valueFrom without a source",
  6445  		envs: []core.EnvVar{{
  6446  			Name:      "abc",
  6447  			ValueFrom: &core.EnvVarSource{},
  6448  		}},
  6449  		expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
  6450  	}, {
  6451  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6452  		envs: []core.EnvVar{{
  6453  			Name: "abc",
  6454  			ValueFrom: &core.EnvVarSource{
  6455  				FieldRef: &core.ObjectFieldSelector{
  6456  					APIVersion: "v1",
  6457  					FieldPath:  "metadata.name",
  6458  				},
  6459  				SecretKeyRef: &core.SecretKeySelector{
  6460  					LocalObjectReference: core.LocalObjectReference{
  6461  						Name: "a-secret",
  6462  					},
  6463  					Key: "a-key",
  6464  				},
  6465  			},
  6466  		}},
  6467  		expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
  6468  	}, {
  6469  		name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
  6470  		envs: []core.EnvVar{{
  6471  			Name: "some_var_name",
  6472  			ValueFrom: &core.EnvVarSource{
  6473  				FieldRef: &core.ObjectFieldSelector{
  6474  					APIVersion: "v1",
  6475  					FieldPath:  "metadata.name",
  6476  				},
  6477  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6478  					LocalObjectReference: core.LocalObjectReference{
  6479  						Name: "some-config-map",
  6480  					},
  6481  					Key: "some-key",
  6482  				},
  6483  			},
  6484  		}},
  6485  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6486  	}, {
  6487  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6488  		envs: []core.EnvVar{{
  6489  			Name: "abc",
  6490  			ValueFrom: &core.EnvVarSource{
  6491  				FieldRef: &core.ObjectFieldSelector{
  6492  					APIVersion: "v1",
  6493  					FieldPath:  "metadata.name",
  6494  				},
  6495  				SecretKeyRef: &core.SecretKeySelector{
  6496  					LocalObjectReference: core.LocalObjectReference{
  6497  						Name: "a-secret",
  6498  					},
  6499  					Key: "a-key",
  6500  				},
  6501  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6502  					LocalObjectReference: core.LocalObjectReference{
  6503  						Name: "some-config-map",
  6504  					},
  6505  					Key: "some-key",
  6506  				},
  6507  			},
  6508  		}},
  6509  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6510  	}, {
  6511  		name: "valueFrom.secretKeyRef.name invalid",
  6512  		envs: []core.EnvVar{{
  6513  			Name: "abc",
  6514  			ValueFrom: &core.EnvVarSource{
  6515  				SecretKeyRef: &core.SecretKeySelector{
  6516  					LocalObjectReference: core.LocalObjectReference{
  6517  						Name: "$%^&*#",
  6518  					},
  6519  					Key: "a-key",
  6520  				},
  6521  			},
  6522  		}},
  6523  	}, {
  6524  		name: "valueFrom.configMapKeyRef.name invalid",
  6525  		envs: []core.EnvVar{{
  6526  			Name: "abc",
  6527  			ValueFrom: &core.EnvVarSource{
  6528  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6529  					LocalObjectReference: core.LocalObjectReference{
  6530  						Name: "$%^&*#",
  6531  					},
  6532  					Key: "some-key",
  6533  				},
  6534  			},
  6535  		}},
  6536  	}, {
  6537  		name: "missing FieldPath on ObjectFieldSelector",
  6538  		envs: []core.EnvVar{{
  6539  			Name: "abc",
  6540  			ValueFrom: &core.EnvVarSource{
  6541  				FieldRef: &core.ObjectFieldSelector{
  6542  					APIVersion: "v1",
  6543  				},
  6544  			},
  6545  		}},
  6546  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
  6547  	}, {
  6548  		name: "missing APIVersion on ObjectFieldSelector",
  6549  		envs: []core.EnvVar{{
  6550  			Name: "abc",
  6551  			ValueFrom: &core.EnvVarSource{
  6552  				FieldRef: &core.ObjectFieldSelector{
  6553  					FieldPath: "metadata.name",
  6554  				},
  6555  			},
  6556  		}},
  6557  		expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
  6558  	}, {
  6559  		name: "invalid fieldPath",
  6560  		envs: []core.EnvVar{{
  6561  			Name: "abc",
  6562  			ValueFrom: &core.EnvVarSource{
  6563  				FieldRef: &core.ObjectFieldSelector{
  6564  					FieldPath:  "metadata.whoops",
  6565  					APIVersion: "v1",
  6566  				},
  6567  			},
  6568  		}},
  6569  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
  6570  	}, {
  6571  		name: "metadata.name with subscript",
  6572  		envs: []core.EnvVar{{
  6573  			Name: "labels",
  6574  			ValueFrom: &core.EnvVarSource{
  6575  				FieldRef: &core.ObjectFieldSelector{
  6576  					FieldPath:  "metadata.name['key']",
  6577  					APIVersion: "v1",
  6578  				},
  6579  			},
  6580  		}},
  6581  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
  6582  	}, {
  6583  		name: "metadata.labels without subscript",
  6584  		envs: []core.EnvVar{{
  6585  			Name: "labels",
  6586  			ValueFrom: &core.EnvVarSource{
  6587  				FieldRef: &core.ObjectFieldSelector{
  6588  					FieldPath:  "metadata.labels",
  6589  					APIVersion: "v1",
  6590  				},
  6591  			},
  6592  		}},
  6593  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6594  	}, {
  6595  		name: "metadata.annotations without subscript",
  6596  		envs: []core.EnvVar{{
  6597  			Name: "abc",
  6598  			ValueFrom: &core.EnvVarSource{
  6599  				FieldRef: &core.ObjectFieldSelector{
  6600  					FieldPath:  "metadata.annotations",
  6601  					APIVersion: "v1",
  6602  				},
  6603  			},
  6604  		}},
  6605  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6606  	}, {
  6607  		name: "metadata.annotations with invalid key",
  6608  		envs: []core.EnvVar{{
  6609  			Name: "abc",
  6610  			ValueFrom: &core.EnvVarSource{
  6611  				FieldRef: &core.ObjectFieldSelector{
  6612  					FieldPath:  "metadata.annotations['invalid~key']",
  6613  					APIVersion: "v1",
  6614  				},
  6615  			},
  6616  		}},
  6617  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
  6618  	}, {
  6619  		name: "metadata.labels with invalid key",
  6620  		envs: []core.EnvVar{{
  6621  			Name: "abc",
  6622  			ValueFrom: &core.EnvVarSource{
  6623  				FieldRef: &core.ObjectFieldSelector{
  6624  					FieldPath:  "metadata.labels['Www.k8s.io/test']",
  6625  					APIVersion: "v1",
  6626  				},
  6627  			},
  6628  		}},
  6629  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
  6630  	}, {
  6631  		name: "unsupported fieldPath",
  6632  		envs: []core.EnvVar{{
  6633  			Name: "abc",
  6634  			ValueFrom: &core.EnvVarSource{
  6635  				FieldRef: &core.ObjectFieldSelector{
  6636  					FieldPath:  "status.phase",
  6637  					APIVersion: "v1",
  6638  				},
  6639  			},
  6640  		}},
  6641  		expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
  6642  	},
  6643  	}
  6644  	for _, tc := range errorCases {
  6645  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6646  			t.Errorf("expected failure for %s", tc.name)
  6647  		} else {
  6648  			for i := range errs {
  6649  				str := errs[i].Error()
  6650  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6651  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6652  				}
  6653  			}
  6654  		}
  6655  	}
  6656  }
  6657  
  6658  func TestValidateEnvFrom(t *testing.T) {
  6659  	successCase := []core.EnvFromSource{{
  6660  		ConfigMapRef: &core.ConfigMapEnvSource{
  6661  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6662  		},
  6663  	}, {
  6664  		Prefix: "pre_",
  6665  		ConfigMapRef: &core.ConfigMapEnvSource{
  6666  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6667  		},
  6668  	}, {
  6669  		Prefix: "a.b",
  6670  		ConfigMapRef: &core.ConfigMapEnvSource{
  6671  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6672  		},
  6673  	}, {
  6674  		SecretRef: &core.SecretEnvSource{
  6675  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6676  		},
  6677  	}, {
  6678  		Prefix: "pre_",
  6679  		SecretRef: &core.SecretEnvSource{
  6680  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6681  		},
  6682  	}, {
  6683  		Prefix: "a.b",
  6684  		SecretRef: &core.SecretEnvSource{
  6685  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6686  		},
  6687  	},
  6688  	}
  6689  	if errs := ValidateEnvFrom(successCase, nil, PodValidationOptions{}); len(errs) != 0 {
  6690  		t.Errorf("expected success: %v", errs)
  6691  	}
  6692  
  6693  	updateSuccessCase := []core.EnvFromSource{{
  6694  		ConfigMapRef: &core.ConfigMapEnvSource{
  6695  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6696  		},
  6697  	}, {
  6698  		Prefix: "* +,-./0123456789",
  6699  		ConfigMapRef: &core.ConfigMapEnvSource{
  6700  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6701  		},
  6702  	}, {
  6703  		Prefix: ":;<>?@",
  6704  		ConfigMapRef: &core.ConfigMapEnvSource{
  6705  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6706  		},
  6707  	}, {
  6708  		SecretRef: &core.SecretEnvSource{
  6709  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6710  		},
  6711  	}, {
  6712  		Prefix: "abcdefghijklmn",
  6713  		SecretRef: &core.SecretEnvSource{
  6714  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6715  		},
  6716  	}, {
  6717  		Prefix: "[\\]^_`{}|~",
  6718  		SecretRef: &core.SecretEnvSource{
  6719  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6720  		},
  6721  	}}
  6722  
  6723  	if errs := ValidateEnvFrom(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6724  		t.Errorf("expected success, got: %v", errs)
  6725  	}
  6726  
  6727  	updateErrorCase := []struct {
  6728  		name          string
  6729  		envs          []core.EnvFromSource
  6730  		expectedError string
  6731  	}{
  6732  		{
  6733  			name: "invalid name a",
  6734  			envs: []core.EnvFromSource{
  6735  				{
  6736  					Prefix: "!\"#$%&'()",
  6737  					SecretRef: &core.SecretEnvSource{
  6738  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6739  					},
  6740  				},
  6741  			},
  6742  			expectedError: `field[0].prefix: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
  6743  		},
  6744  		{
  6745  			name: "invalid name b",
  6746  			envs: []core.EnvFromSource{
  6747  				{
  6748  					Prefix: "* +,-./0123456789",
  6749  					SecretRef: &core.SecretEnvSource{
  6750  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6751  					},
  6752  				},
  6753  			},
  6754  			expectedError: `field[0].prefix: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
  6755  		},
  6756  		{
  6757  			name: "invalid name c",
  6758  			envs: []core.EnvFromSource{
  6759  				{
  6760  					Prefix: ":;<>?@",
  6761  					SecretRef: &core.SecretEnvSource{
  6762  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6763  					},
  6764  				},
  6765  			},
  6766  			expectedError: `field[0].prefix: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
  6767  		},
  6768  		{
  6769  			name: "invalid name d",
  6770  			envs: []core.EnvFromSource{
  6771  				{
  6772  					Prefix: "[\\]^_{}|~",
  6773  					SecretRef: &core.SecretEnvSource{
  6774  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6775  					},
  6776  				},
  6777  			},
  6778  			expectedError: `field[0].prefix: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
  6779  		},
  6780  	}
  6781  
  6782  	for _, tc := range updateErrorCase {
  6783  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6784  			t.Errorf("expected failure for %s", tc.name)
  6785  		} else {
  6786  			for i := range errs {
  6787  				str := errs[i].Error()
  6788  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6789  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6790  				}
  6791  			}
  6792  		}
  6793  	}
  6794  
  6795  	errorCases := []struct {
  6796  		name          string
  6797  		envs          []core.EnvFromSource
  6798  		expectedError string
  6799  	}{{
  6800  		name: "zero-length name",
  6801  		envs: []core.EnvFromSource{{
  6802  			ConfigMapRef: &core.ConfigMapEnvSource{
  6803  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6804  		}},
  6805  		expectedError: "field[0].configMapRef.name: Required value",
  6806  	}, {
  6807  		name: "invalid name",
  6808  		envs: []core.EnvFromSource{{
  6809  			ConfigMapRef: &core.ConfigMapEnvSource{
  6810  				LocalObjectReference: core.LocalObjectReference{Name: "$"}},
  6811  		}},
  6812  		expectedError: "field[0].configMapRef.name: Invalid value",
  6813  	}, {
  6814  		name: "zero-length name",
  6815  		envs: []core.EnvFromSource{{
  6816  			SecretRef: &core.SecretEnvSource{
  6817  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6818  		}},
  6819  		expectedError: "field[0].secretRef.name: Required value",
  6820  	}, {
  6821  		name: "invalid name",
  6822  		envs: []core.EnvFromSource{{
  6823  			SecretRef: &core.SecretEnvSource{
  6824  				LocalObjectReference: core.LocalObjectReference{Name: "&"}},
  6825  		}},
  6826  		expectedError: "field[0].secretRef.name: Invalid value",
  6827  	}, {
  6828  		name: "no refs",
  6829  		envs: []core.EnvFromSource{
  6830  			{},
  6831  		},
  6832  		expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
  6833  	}, {
  6834  		name: "multiple refs",
  6835  		envs: []core.EnvFromSource{{
  6836  			SecretRef: &core.SecretEnvSource{
  6837  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6838  			ConfigMapRef: &core.ConfigMapEnvSource{
  6839  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6840  		}},
  6841  		expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
  6842  	}, {
  6843  		name: "invalid secret ref name",
  6844  		envs: []core.EnvFromSource{{
  6845  			SecretRef: &core.SecretEnvSource{
  6846  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6847  		}},
  6848  		expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6849  	}, {
  6850  		name: "invalid config ref name",
  6851  		envs: []core.EnvFromSource{{
  6852  			ConfigMapRef: &core.ConfigMapEnvSource{
  6853  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6854  		}},
  6855  		expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6856  	},
  6857  	}
  6858  	for _, tc := range errorCases {
  6859  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6860  			t.Errorf("expected failure for %s", tc.name)
  6861  		} else {
  6862  			for i := range errs {
  6863  				str := errs[i].Error()
  6864  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6865  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6866  				}
  6867  			}
  6868  		}
  6869  	}
  6870  }
  6871  
  6872  func TestRelaxedValidateEnvFrom(t *testing.T) {
  6873  	successCase := []core.EnvFromSource{{
  6874  		ConfigMapRef: &core.ConfigMapEnvSource{
  6875  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6876  		},
  6877  	}, {
  6878  		Prefix: "!\"#$%&'()",
  6879  		ConfigMapRef: &core.ConfigMapEnvSource{
  6880  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6881  		},
  6882  	}, {
  6883  		Prefix: "* +,-./0123456789",
  6884  		ConfigMapRef: &core.ConfigMapEnvSource{
  6885  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6886  		},
  6887  	}, {
  6888  		SecretRef: &core.SecretEnvSource{
  6889  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6890  		},
  6891  	}, {
  6892  		Prefix: ":;<>?@",
  6893  		SecretRef: &core.SecretEnvSource{
  6894  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6895  		},
  6896  	}, {
  6897  		Prefix: "[\\]^_`{}|~",
  6898  		SecretRef: &core.SecretEnvSource{
  6899  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6900  		},
  6901  	},
  6902  	}
  6903  	if errs := ValidateEnvFrom(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6904  		t.Errorf("expected success: %v", errs)
  6905  	}
  6906  
  6907  	errorCases := []struct {
  6908  		name          string
  6909  		envs          []core.EnvFromSource
  6910  		expectedError string
  6911  	}{
  6912  		{
  6913  			name: "zero-length name",
  6914  			envs: []core.EnvFromSource{{
  6915  				ConfigMapRef: &core.ConfigMapEnvSource{
  6916  					LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6917  			}},
  6918  			expectedError: "field[0].configMapRef.name: Required value",
  6919  		},
  6920  		{
  6921  			name: "invalid prefix",
  6922  			envs: []core.EnvFromSource{{
  6923  				Prefix: "=abc",
  6924  				ConfigMapRef: &core.ConfigMapEnvSource{
  6925  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6926  			}},
  6927  			expectedError: `field[0].prefix: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
  6928  		},
  6929  		{
  6930  			name: "zero-length name",
  6931  			envs: []core.EnvFromSource{{
  6932  				SecretRef: &core.SecretEnvSource{
  6933  					LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6934  			}},
  6935  			expectedError: "field[0].secretRef.name: Required value",
  6936  		}, {
  6937  			name: "invalid name",
  6938  			envs: []core.EnvFromSource{{
  6939  				SecretRef: &core.SecretEnvSource{
  6940  					LocalObjectReference: core.LocalObjectReference{Name: "&"}},
  6941  			}},
  6942  			expectedError: "field[0].secretRef.name: Invalid value",
  6943  		}, {
  6944  			name: "no refs",
  6945  			envs: []core.EnvFromSource{
  6946  				{},
  6947  			},
  6948  			expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
  6949  		}, {
  6950  			name: "multiple refs",
  6951  			envs: []core.EnvFromSource{{
  6952  				SecretRef: &core.SecretEnvSource{
  6953  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6954  				ConfigMapRef: &core.ConfigMapEnvSource{
  6955  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6956  			}},
  6957  			expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
  6958  		}, {
  6959  			name: "invalid secret ref name",
  6960  			envs: []core.EnvFromSource{{
  6961  				SecretRef: &core.SecretEnvSource{
  6962  					LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6963  			}},
  6964  			expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6965  		}, {
  6966  			name: "invalid config ref name",
  6967  			envs: []core.EnvFromSource{{
  6968  				ConfigMapRef: &core.ConfigMapEnvSource{
  6969  					LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6970  			}},
  6971  			expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6972  		},
  6973  	}
  6974  	for _, tc := range errorCases {
  6975  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
  6976  			t.Errorf("expected failure for %s", tc.name)
  6977  		} else {
  6978  			for i := range errs {
  6979  				str := errs[i].Error()
  6980  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6981  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6982  				}
  6983  			}
  6984  		}
  6985  	}
  6986  }
  6987  
  6988  func TestValidateVolumeMounts(t *testing.T) {
  6989  	volumes := []core.Volume{
  6990  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  6991  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  6992  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6993  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  6994  			Spec: core.PersistentVolumeClaimSpec{
  6995  				AccessModes: []core.PersistentVolumeAccessMode{
  6996  					core.ReadWriteOnce,
  6997  				},
  6998  				Resources: core.VolumeResourceRequirements{
  6999  					Requests: core.ResourceList{
  7000  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  7001  					},
  7002  				},
  7003  			},
  7004  		}}}},
  7005  	}
  7006  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7007  	if len(v1err) > 0 {
  7008  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7009  		return
  7010  	}
  7011  	container := core.Container{
  7012  		SecurityContext: nil,
  7013  	}
  7014  	propagation := core.MountPropagationBidirectional
  7015  
  7016  	successCase := []core.VolumeMount{
  7017  		{Name: "abc", MountPath: "/foo"},
  7018  		{Name: "123", MountPath: "/bar"},
  7019  		{Name: "abc-123", MountPath: "/baz"},
  7020  		{Name: "abc-123", MountPath: "/baa", SubPath: ""},
  7021  		{Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
  7022  		{Name: "abc-123", MountPath: "d:", SubPath: ""},
  7023  		{Name: "abc-123", MountPath: "F:", SubPath: ""},
  7024  		{Name: "abc-123", MountPath: "G:\\mount", SubPath: ""},
  7025  		{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
  7026  		{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
  7027  		{Name: "ephemeral", MountPath: "/foobar"},
  7028  		{Name: "123", MountPath: "/rro-nil", ReadOnly: true, RecursiveReadOnly: nil},
  7029  		{Name: "123", MountPath: "/rro-disabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
  7030  		{Name: "123", MountPath: "/rro-disabled-2", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
  7031  		{Name: "123", MountPath: "/rro-ifpossible", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)},
  7032  		{Name: "123", MountPath: "/rro-enabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)},
  7033  		{Name: "123", MountPath: "/rro-enabled-2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationNone)},
  7034  	}
  7035  	goodVolumeDevices := []core.VolumeDevice{
  7036  		{Name: "xyz", DevicePath: "/foofoo"},
  7037  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7038  	}
  7039  	if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
  7040  		t.Errorf("expected success: %v", errs)
  7041  	}
  7042  
  7043  	errorCases := map[string][]core.VolumeMount{
  7044  		"empty name":                             {{Name: "", MountPath: "/foo"}},
  7045  		"name not found":                         {{Name: "", MountPath: "/foo"}},
  7046  		"empty mountpath":                        {{Name: "abc", MountPath: ""}},
  7047  		"mountpath collision":                    {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
  7048  		"absolute subpath":                       {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
  7049  		"subpath in ..":                          {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
  7050  		"subpath contains ..":                    {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
  7051  		"subpath ends in ..":                     {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
  7052  		"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
  7053  		"name exists in volumeDevice":            {{Name: "xyz", MountPath: "/bar"}},
  7054  		"mountpath exists in volumeDevice":       {{Name: "uvw", MountPath: "/mnt/exists"}},
  7055  		"both exist in volumeDevice":             {{Name: "xyz", MountPath: "/mnt/exists"}},
  7056  		"rro but not ro":                         {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}},
  7057  		"rro with incompatible propagation":      {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}},
  7058  		"rro-if-possible but not ro":             {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}},
  7059  	}
  7060  	badVolumeDevice := []core.VolumeDevice{
  7061  		{Name: "xyz", DevicePath: "/mnt/exists"},
  7062  	}
  7063  
  7064  	for k, v := range errorCases {
  7065  		if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
  7066  			t.Errorf("expected failure for %s", k)
  7067  		}
  7068  	}
  7069  }
  7070  
  7071  func TestValidateSubpathMutuallyExclusive(t *testing.T) {
  7072  	volumes := []core.Volume{
  7073  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7074  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7075  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7076  	}
  7077  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7078  	if len(v1err) > 0 {
  7079  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7080  		return
  7081  	}
  7082  
  7083  	container := core.Container{
  7084  		SecurityContext: nil,
  7085  	}
  7086  
  7087  	goodVolumeDevices := []core.VolumeDevice{
  7088  		{Name: "xyz", DevicePath: "/foofoo"},
  7089  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7090  	}
  7091  
  7092  	cases := map[string]struct {
  7093  		mounts      []core.VolumeMount
  7094  		expectError bool
  7095  	}{
  7096  		"subpath and subpathexpr not specified": {
  7097  			[]core.VolumeMount{{
  7098  				Name:      "abc-123",
  7099  				MountPath: "/bab",
  7100  			}},
  7101  			false,
  7102  		},
  7103  		"subpath expr specified": {
  7104  			[]core.VolumeMount{{
  7105  				Name:        "abc-123",
  7106  				MountPath:   "/bab",
  7107  				SubPathExpr: "$(POD_NAME)",
  7108  			}},
  7109  			false,
  7110  		},
  7111  		"subpath specified": {
  7112  			[]core.VolumeMount{{
  7113  				Name:      "abc-123",
  7114  				MountPath: "/bab",
  7115  				SubPath:   "baz",
  7116  			}},
  7117  			false,
  7118  		},
  7119  		"subpath and subpathexpr specified": {
  7120  			[]core.VolumeMount{{
  7121  				Name:        "abc-123",
  7122  				MountPath:   "/bab",
  7123  				SubPath:     "baz",
  7124  				SubPathExpr: "$(POD_NAME)",
  7125  			}},
  7126  			true,
  7127  		},
  7128  	}
  7129  
  7130  	for name, test := range cases {
  7131  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  7132  
  7133  		if len(errs) != 0 && !test.expectError {
  7134  			t.Errorf("test %v failed: %+v", name, errs)
  7135  		}
  7136  
  7137  		if len(errs) == 0 && test.expectError {
  7138  			t.Errorf("test %v failed, expected error", name)
  7139  		}
  7140  	}
  7141  }
  7142  
  7143  func TestValidateDisabledSubpathExpr(t *testing.T) {
  7144  
  7145  	volumes := []core.Volume{
  7146  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7147  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7148  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7149  	}
  7150  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7151  	if len(v1err) > 0 {
  7152  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7153  		return
  7154  	}
  7155  
  7156  	container := core.Container{
  7157  		SecurityContext: nil,
  7158  	}
  7159  
  7160  	goodVolumeDevices := []core.VolumeDevice{
  7161  		{Name: "xyz", DevicePath: "/foofoo"},
  7162  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7163  	}
  7164  
  7165  	cases := map[string]struct {
  7166  		mounts      []core.VolumeMount
  7167  		expectError bool
  7168  	}{
  7169  		"subpath expr not specified": {
  7170  			[]core.VolumeMount{{
  7171  				Name:      "abc-123",
  7172  				MountPath: "/bab",
  7173  			}},
  7174  			false,
  7175  		},
  7176  		"subpath expr specified": {
  7177  			[]core.VolumeMount{{
  7178  				Name:        "abc-123",
  7179  				MountPath:   "/bab",
  7180  				SubPathExpr: "$(POD_NAME)",
  7181  			}},
  7182  			false,
  7183  		},
  7184  	}
  7185  
  7186  	for name, test := range cases {
  7187  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  7188  
  7189  		if len(errs) != 0 && !test.expectError {
  7190  			t.Errorf("test %v failed: %+v", name, errs)
  7191  		}
  7192  
  7193  		if len(errs) == 0 && test.expectError {
  7194  			t.Errorf("test %v failed, expected error", name)
  7195  		}
  7196  	}
  7197  }
  7198  
  7199  func TestValidateMountPropagation(t *testing.T) {
  7200  	bTrue := true
  7201  	bFalse := false
  7202  	privilegedContainer := &core.Container{
  7203  		SecurityContext: &core.SecurityContext{
  7204  			Privileged: &bTrue,
  7205  		},
  7206  	}
  7207  	nonPrivilegedContainer := &core.Container{
  7208  		SecurityContext: &core.SecurityContext{
  7209  			Privileged: &bFalse,
  7210  		},
  7211  	}
  7212  	defaultContainer := &core.Container{}
  7213  
  7214  	propagationBidirectional := core.MountPropagationBidirectional
  7215  	propagationHostToContainer := core.MountPropagationHostToContainer
  7216  	propagationNone := core.MountPropagationNone
  7217  	propagationInvalid := core.MountPropagationMode("invalid")
  7218  
  7219  	tests := []struct {
  7220  		mount       core.VolumeMount
  7221  		container   *core.Container
  7222  		expectError bool
  7223  	}{{
  7224  		// implicitly non-privileged container + no propagation
  7225  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7226  		defaultContainer,
  7227  		false,
  7228  	}, {
  7229  		// implicitly non-privileged container + HostToContainer
  7230  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7231  		defaultContainer,
  7232  		false,
  7233  	}, {
  7234  		// non-privileged container + None
  7235  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone},
  7236  		defaultContainer,
  7237  		false,
  7238  	}, {
  7239  		// error: implicitly non-privileged container + Bidirectional
  7240  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7241  		defaultContainer,
  7242  		true,
  7243  	}, {
  7244  		// explicitly non-privileged container + no propagation
  7245  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7246  		nonPrivilegedContainer,
  7247  		false,
  7248  	}, {
  7249  		// explicitly non-privileged container + HostToContainer
  7250  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7251  		nonPrivilegedContainer,
  7252  		false,
  7253  	}, {
  7254  		// explicitly non-privileged container + HostToContainer
  7255  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7256  		nonPrivilegedContainer,
  7257  		true,
  7258  	}, {
  7259  		// privileged container + no propagation
  7260  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7261  		privilegedContainer,
  7262  		false,
  7263  	}, {
  7264  		// privileged container + HostToContainer
  7265  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7266  		privilegedContainer,
  7267  		false,
  7268  	}, {
  7269  		// privileged container + Bidirectional
  7270  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7271  		privilegedContainer,
  7272  		false,
  7273  	}, {
  7274  		// error: privileged container + invalid mount propagation
  7275  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
  7276  		privilegedContainer,
  7277  		true,
  7278  	}, {
  7279  		// no container + Bidirectional
  7280  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7281  		nil,
  7282  		false,
  7283  	},
  7284  	}
  7285  
  7286  	volumes := []core.Volume{
  7287  		{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7288  	}
  7289  	vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7290  	if len(v2err) > 0 {
  7291  		t.Errorf("Invalid test volume - expected success %v", v2err)
  7292  		return
  7293  	}
  7294  	for i, test := range tests {
  7295  		errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
  7296  		if test.expectError && len(errs) == 0 {
  7297  			t.Errorf("test %d expected error, got none", i)
  7298  		}
  7299  		if !test.expectError && len(errs) != 0 {
  7300  			t.Errorf("test %d expected success, got error: %v", i, errs)
  7301  		}
  7302  	}
  7303  }
  7304  
  7305  func TestAlphaValidateVolumeDevices(t *testing.T) {
  7306  	volumes := []core.Volume{
  7307  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7308  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7309  		{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7310  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  7311  			Spec: core.PersistentVolumeClaimSpec{
  7312  				AccessModes: []core.PersistentVolumeAccessMode{
  7313  					core.ReadWriteOnce,
  7314  				},
  7315  				Resources: core.VolumeResourceRequirements{
  7316  					Requests: core.ResourceList{
  7317  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  7318  					},
  7319  				},
  7320  			},
  7321  		}}}},
  7322  	}
  7323  
  7324  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7325  	if len(v1err) > 0 {
  7326  		t.Errorf("Invalid test volumes - expected success %v", v1err)
  7327  		return
  7328  	}
  7329  
  7330  	successCase := []core.VolumeDevice{
  7331  		{Name: "abc", DevicePath: "/foo"},
  7332  		{Name: "abc-123", DevicePath: "/usr/share/test"},
  7333  		{Name: "ephemeral", DevicePath: "/disk"},
  7334  	}
  7335  	goodVolumeMounts := []core.VolumeMount{
  7336  		{Name: "xyz", MountPath: "/foofoo"},
  7337  		{Name: "ghi", MountPath: "/foo/usr/share/test"},
  7338  	}
  7339  
  7340  	errorCases := map[string][]core.VolumeDevice{
  7341  		"empty name":                    {{Name: "", DevicePath: "/foo"}},
  7342  		"duplicate name":                {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
  7343  		"name not found":                {{Name: "not-found", DevicePath: "/usr/share/test"}},
  7344  		"name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
  7345  		"empty devicepath":              {{Name: "abc", DevicePath: ""}},
  7346  		"relative devicepath":           {{Name: "abc-123", DevicePath: "baz"}},
  7347  		"duplicate devicepath":          {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
  7348  		"no backsteps":                  {{Name: "def", DevicePath: "/baz/../"}},
  7349  		"name exists in volumemounts":   {{Name: "abc", DevicePath: "/baz/../"}},
  7350  		"path exists in volumemounts":   {{Name: "xyz", DevicePath: "/this/path/exists"}},
  7351  		"both exist in volumemounts":    {{Name: "abc", DevicePath: "/this/path/exists"}},
  7352  	}
  7353  	badVolumeMounts := []core.VolumeMount{
  7354  		{Name: "abc", MountPath: "/foo"},
  7355  		{Name: "abc-123", MountPath: "/this/path/exists"},
  7356  	}
  7357  
  7358  	// Success Cases:
  7359  	// Validate normal success cases - only PVC volumeSource or generic ephemeral volume
  7360  	if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
  7361  		t.Errorf("expected success: %v", errs)
  7362  	}
  7363  
  7364  	// Error Cases:
  7365  	// Validate normal error cases - only PVC volumeSource
  7366  	for k, v := range errorCases {
  7367  		if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
  7368  			t.Errorf("expected failure for %s", k)
  7369  		}
  7370  	}
  7371  }
  7372  
  7373  func TestValidateProbe(t *testing.T) {
  7374  	handler := core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}
  7375  	// These fields must be positive.
  7376  	positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"}
  7377  	successCases := []*core.Probe{nil}
  7378  	for _, field := range positiveFields {
  7379  		probe := &core.Probe{ProbeHandler: handler}
  7380  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10)
  7381  		successCases = append(successCases, probe)
  7382  	}
  7383  
  7384  	for _, p := range successCases {
  7385  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  7386  			t.Errorf("expected success: %v", errs)
  7387  		}
  7388  	}
  7389  
  7390  	errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}}
  7391  	for _, field := range positiveFields {
  7392  		probe := &core.Probe{ProbeHandler: handler}
  7393  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10)
  7394  		errorCases = append(errorCases, probe)
  7395  	}
  7396  	for _, p := range errorCases {
  7397  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  7398  			t.Errorf("expected failure for %v", p)
  7399  		}
  7400  	}
  7401  }
  7402  
  7403  func Test_validateProbe(t *testing.T) {
  7404  	fldPath := field.NewPath("test")
  7405  	type args struct {
  7406  		probe   *core.Probe
  7407  		fldPath *field.Path
  7408  	}
  7409  	tests := []struct {
  7410  		name string
  7411  		args args
  7412  		want field.ErrorList
  7413  	}{{
  7414  		args: args{
  7415  			probe:   &core.Probe{},
  7416  			fldPath: fldPath,
  7417  		},
  7418  		want: field.ErrorList{field.Required(fldPath, "must specify a handler type")},
  7419  	}, {
  7420  		args: args{
  7421  			probe: &core.Probe{
  7422  				ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7423  			},
  7424  			fldPath: fldPath,
  7425  		},
  7426  		want: field.ErrorList{},
  7427  	}, {
  7428  		args: args{
  7429  			probe: &core.Probe{
  7430  				ProbeHandler:        core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7431  				InitialDelaySeconds: -1,
  7432  			},
  7433  			fldPath: fldPath,
  7434  		},
  7435  		want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")},
  7436  	}, {
  7437  		args: args{
  7438  			probe: &core.Probe{
  7439  				ProbeHandler:   core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7440  				TimeoutSeconds: -1,
  7441  			},
  7442  			fldPath: fldPath,
  7443  		},
  7444  		want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")},
  7445  	}, {
  7446  		args: args{
  7447  			probe: &core.Probe{
  7448  				ProbeHandler:  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7449  				PeriodSeconds: -1,
  7450  			},
  7451  			fldPath: fldPath,
  7452  		},
  7453  		want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")},
  7454  	}, {
  7455  		args: args{
  7456  			probe: &core.Probe{
  7457  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7458  				SuccessThreshold: -1,
  7459  			},
  7460  			fldPath: fldPath,
  7461  		},
  7462  		want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")},
  7463  	}, {
  7464  		args: args{
  7465  			probe: &core.Probe{
  7466  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7467  				FailureThreshold: -1,
  7468  			},
  7469  			fldPath: fldPath,
  7470  		},
  7471  		want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")},
  7472  	}, {
  7473  		args: args{
  7474  			probe: &core.Probe{
  7475  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7476  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  7477  			},
  7478  			fldPath: fldPath,
  7479  		},
  7480  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")},
  7481  	}, {
  7482  		args: args{
  7483  			probe: &core.Probe{
  7484  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7485  				TerminationGracePeriodSeconds: utilpointer.Int64(0),
  7486  			},
  7487  			fldPath: fldPath,
  7488  		},
  7489  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")},
  7490  	}, {
  7491  		args: args{
  7492  			probe: &core.Probe{
  7493  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7494  				TerminationGracePeriodSeconds: utilpointer.Int64(1),
  7495  			},
  7496  			fldPath: fldPath,
  7497  		},
  7498  		want: field.ErrorList{},
  7499  	},
  7500  	}
  7501  	for _, tt := range tests {
  7502  		t.Run(tt.name, func(t *testing.T) {
  7503  			got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath)
  7504  			if len(got) != len(tt.want) {
  7505  				t.Errorf("validateProbe() = %v, want %v", got, tt.want)
  7506  				return
  7507  			}
  7508  			for i := range got {
  7509  				if got[i].Type != tt.want[i].Type ||
  7510  					got[i].Field != tt.want[i].Field {
  7511  					t.Errorf("validateProbe()[%d] = %v, want %v", i, got[i], tt.want[i])
  7512  				}
  7513  			}
  7514  		})
  7515  	}
  7516  }
  7517  
  7518  func TestValidateHandler(t *testing.T) {
  7519  	successCases := []core.ProbeHandler{
  7520  		{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7521  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt32(1), Host: "", Scheme: "HTTP"}},
  7522  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65535), Host: "host", Scheme: "HTTP"}},
  7523  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
  7524  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}},
  7525  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}},
  7526  	}
  7527  	for _, h := range successCases {
  7528  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  7529  			t.Errorf("expected success: %v", errs)
  7530  		}
  7531  	}
  7532  
  7533  	errorCases := []core.ProbeHandler{
  7534  		{},
  7535  		{Exec: &core.ExecAction{Command: []string{}}},
  7536  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt32(0), Host: ""}},
  7537  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65536), Host: "host"}},
  7538  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
  7539  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}},
  7540  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
  7541  	}
  7542  	for _, h := range errorCases {
  7543  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  7544  			t.Errorf("expected failure for %#v", h)
  7545  		}
  7546  	}
  7547  }
  7548  
  7549  func TestValidatePullPolicy(t *testing.T) {
  7550  	type T struct {
  7551  		Container      core.Container
  7552  		ExpectedPolicy core.PullPolicy
  7553  	}
  7554  	testCases := map[string]T{
  7555  		"NotPresent1": {
  7556  			core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7557  			core.PullIfNotPresent,
  7558  		},
  7559  		"NotPresent2": {
  7560  			core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7561  			core.PullIfNotPresent,
  7562  		},
  7563  		"Always1": {
  7564  			core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"},
  7565  			core.PullAlways,
  7566  		},
  7567  		"Always2": {
  7568  			core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"},
  7569  			core.PullAlways,
  7570  		},
  7571  		"Never1": {
  7572  			core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"},
  7573  			core.PullNever,
  7574  		},
  7575  		"Never2": {
  7576  			core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
  7577  			core.PullNever,
  7578  		},
  7579  	}
  7580  	for k, v := range testCases {
  7581  		ctr := &v.Container
  7582  		errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field"))
  7583  		if len(errs) != 0 {
  7584  			t.Errorf("case[%s] expected success, got %#v", k, errs)
  7585  		}
  7586  		if ctr.ImagePullPolicy != v.ExpectedPolicy {
  7587  			t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
  7588  		}
  7589  	}
  7590  }
  7591  
  7592  func TestValidateResizePolicy(t *testing.T) {
  7593  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
  7594  	tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
  7595  	tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer))
  7596  	type T struct {
  7597  		PolicyList       []core.ContainerResizePolicy
  7598  		ExpectError      bool
  7599  		Errors           field.ErrorList
  7600  		PodRestartPolicy core.RestartPolicy
  7601  	}
  7602  
  7603  	testCases := map[string]T{
  7604  		"ValidCPUandMemoryPolicies": {
  7605  			PolicyList: []core.ContainerResizePolicy{
  7606  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7607  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7608  			},
  7609  			ExpectError:      false,
  7610  			Errors:           nil,
  7611  			PodRestartPolicy: "Always",
  7612  		},
  7613  		"ValidCPUPolicy": {
  7614  			PolicyList: []core.ContainerResizePolicy{
  7615  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7616  			},
  7617  			ExpectError:      false,
  7618  			Errors:           nil,
  7619  			PodRestartPolicy: "Always",
  7620  		},
  7621  		"ValidMemoryPolicy": {
  7622  			PolicyList: []core.ContainerResizePolicy{
  7623  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7624  			},
  7625  			ExpectError:      false,
  7626  			Errors:           nil,
  7627  			PodRestartPolicy: "Always",
  7628  		},
  7629  		"NoPolicy": {
  7630  			PolicyList:       []core.ContainerResizePolicy{},
  7631  			ExpectError:      false,
  7632  			Errors:           nil,
  7633  			PodRestartPolicy: "Always",
  7634  		},
  7635  		"ValidCPUandInvalidMemoryPolicy": {
  7636  			PolicyList: []core.ContainerResizePolicy{
  7637  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7638  				{ResourceName: "memory", RestartPolicy: "Restarrrt"},
  7639  			},
  7640  			ExpectError:      true,
  7641  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("Restarrrt"), tSupportedResizePolicies.List())},
  7642  			PodRestartPolicy: "Always",
  7643  		},
  7644  		"ValidMemoryandInvalidCPUPolicy": {
  7645  			PolicyList: []core.ContainerResizePolicy{
  7646  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequirrred"},
  7647  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7648  			},
  7649  			ExpectError:      true,
  7650  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartNotRequirrred"), tSupportedResizePolicies.List())},
  7651  			PodRestartPolicy: "Always",
  7652  		},
  7653  		"InvalidResourceNameValidPolicy": {
  7654  			PolicyList: []core.ContainerResizePolicy{
  7655  				{ResourceName: "cpuuu", RestartPolicy: "NotRequired"},
  7656  			},
  7657  			ExpectError:      true,
  7658  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceName("cpuuu"), tSupportedResizeResources.List())},
  7659  			PodRestartPolicy: "Always",
  7660  		},
  7661  		"ValidResourceNameMissingPolicy": {
  7662  			PolicyList: []core.ContainerResizePolicy{
  7663  				{ResourceName: "memory", RestartPolicy: ""},
  7664  			},
  7665  			ExpectError:      true,
  7666  			Errors:           field.ErrorList{field.Required(field.NewPath("field"), "")},
  7667  			PodRestartPolicy: "Always",
  7668  		},
  7669  		"RepeatedPolicies": {
  7670  			PolicyList: []core.ContainerResizePolicy{
  7671  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7672  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7673  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7674  			},
  7675  			ExpectError:      true,
  7676  			Errors:           field.ErrorList{field.Duplicate(field.NewPath("field").Index(2), core.ResourceCPU)},
  7677  			PodRestartPolicy: "Always",
  7678  		},
  7679  		"InvalidCPUPolicyWithPodRestartPolicy": {
  7680  			PolicyList: []core.ContainerResizePolicy{
  7681  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7682  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7683  			},
  7684  			ExpectError:      true,
  7685  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7686  			PodRestartPolicy: "Never",
  7687  		},
  7688  		"InvalidMemoryPolicyWithPodRestartPolicy": {
  7689  			PolicyList: []core.ContainerResizePolicy{
  7690  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7691  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7692  			},
  7693  			ExpectError:      true,
  7694  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7695  			PodRestartPolicy: "Never",
  7696  		},
  7697  		"InvalidMemoryCPUPolicyWithPodRestartPolicy": {
  7698  			PolicyList: []core.ContainerResizePolicy{
  7699  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7700  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7701  			},
  7702  			ExpectError:      true,
  7703  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'"), field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7704  			PodRestartPolicy: "Never",
  7705  		},
  7706  		"ValidMemoryCPUPolicyWithPodRestartPolicy": {
  7707  			PolicyList: []core.ContainerResizePolicy{
  7708  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7709  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7710  			},
  7711  			ExpectError:      false,
  7712  			Errors:           nil,
  7713  			PodRestartPolicy: "Never",
  7714  		},
  7715  	}
  7716  	for k, v := range testCases {
  7717  		errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
  7718  		if !v.ExpectError && len(errs) > 0 {
  7719  			t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
  7720  		}
  7721  		if v.ExpectError {
  7722  			if len(errs) == 0 {
  7723  				t.Errorf("Testcase %s - expected error, got success", k)
  7724  			}
  7725  			delta := cmp.Diff(errs, v.Errors)
  7726  			if delta != "" {
  7727  				t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
  7728  			}
  7729  		}
  7730  	}
  7731  }
  7732  
  7733  func getResourceLimits(cpu, memory string) core.ResourceList {
  7734  	res := core.ResourceList{}
  7735  	res[core.ResourceCPU] = resource.MustParse(cpu)
  7736  	res[core.ResourceMemory] = resource.MustParse(memory)
  7737  	return res
  7738  }
  7739  
  7740  func getResources(cpu, memory, storage string) core.ResourceList {
  7741  	res := core.ResourceList{}
  7742  	if cpu != "" {
  7743  		res[core.ResourceCPU] = resource.MustParse(cpu)
  7744  	}
  7745  	if memory != "" {
  7746  		res[core.ResourceMemory] = resource.MustParse(memory)
  7747  	}
  7748  	if storage != "" {
  7749  		res[core.ResourceEphemeralStorage] = resource.MustParse(storage)
  7750  	}
  7751  	return res
  7752  }
  7753  
  7754  func TestValidateEphemeralContainers(t *testing.T) {
  7755  	containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7756  	initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7757  	vols := map[string]core.VolumeSource{
  7758  		"blk": {PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}},
  7759  		"vol": {EmptyDir: &core.EmptyDirVolumeSource{}},
  7760  	}
  7761  
  7762  	// Success Cases
  7763  	for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
  7764  		"Empty Ephemeral Containers": {},
  7765  		"Single Container": {
  7766  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7767  		},
  7768  		"Multiple Containers": {
  7769  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7770  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7771  		},
  7772  		"Single Container with Target": {{
  7773  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7774  			TargetContainerName:      "ctr",
  7775  		}},
  7776  		"All allowed fields": {{
  7777  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7778  
  7779  				Name:       "debug",
  7780  				Image:      "image",
  7781  				Command:    []string{"bash"},
  7782  				Args:       []string{"bash"},
  7783  				WorkingDir: "/",
  7784  				EnvFrom: []core.EnvFromSource{{
  7785  					ConfigMapRef: &core.ConfigMapEnvSource{
  7786  						LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
  7787  						Optional:             &[]bool{true}[0],
  7788  					},
  7789  				}},
  7790  				Env: []core.EnvVar{
  7791  					{Name: "TEST", Value: "TRUE"},
  7792  				},
  7793  				VolumeMounts: []core.VolumeMount{
  7794  					{Name: "vol", MountPath: "/vol"},
  7795  				},
  7796  				VolumeDevices: []core.VolumeDevice{
  7797  					{Name: "blk", DevicePath: "/dev/block"},
  7798  				},
  7799  				TerminationMessagePath:   "/dev/termination-log",
  7800  				TerminationMessagePolicy: "File",
  7801  				ImagePullPolicy:          "IfNotPresent",
  7802  				SecurityContext: &core.SecurityContext{
  7803  					Capabilities: &core.Capabilities{
  7804  						Add: []core.Capability{"SYS_ADMIN"},
  7805  					},
  7806  				},
  7807  				Stdin:     true,
  7808  				StdinOnce: true,
  7809  				TTY:       true,
  7810  			},
  7811  		}},
  7812  	} {
  7813  		var PodRestartPolicy core.RestartPolicy
  7814  		PodRestartPolicy = "Never"
  7815  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7816  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7817  		}
  7818  
  7819  		PodRestartPolicy = "Always"
  7820  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7821  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7822  		}
  7823  
  7824  		PodRestartPolicy = "OnFailure"
  7825  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7826  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7827  		}
  7828  	}
  7829  
  7830  	// Failure Cases
  7831  	tcs := []struct {
  7832  		title, line         string
  7833  		ephemeralContainers []core.EphemeralContainer
  7834  		expectedErrors      field.ErrorList
  7835  	}{{
  7836  		"Name Collision with Container.Containers",
  7837  		line(),
  7838  		[]core.EphemeralContainer{
  7839  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7840  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7841  		},
  7842  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7843  	}, {
  7844  		"Name Collision with Container.InitContainers",
  7845  		line(),
  7846  		[]core.EphemeralContainer{
  7847  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7848  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7849  		},
  7850  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7851  	}, {
  7852  		"Name Collision with EphemeralContainers",
  7853  		line(),
  7854  		[]core.EphemeralContainer{
  7855  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7856  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7857  		},
  7858  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}},
  7859  	}, {
  7860  		"empty Container",
  7861  		line(),
  7862  		[]core.EphemeralContainer{
  7863  			{EphemeralContainerCommon: core.EphemeralContainerCommon{}},
  7864  		},
  7865  		field.ErrorList{
  7866  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"},
  7867  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"},
  7868  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"},
  7869  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"},
  7870  		},
  7871  	}, {
  7872  		"empty Container Name",
  7873  		line(),
  7874  		[]core.EphemeralContainer{
  7875  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7876  		},
  7877  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}},
  7878  	}, {
  7879  		"whitespace padded image name",
  7880  		line(),
  7881  		[]core.EphemeralContainer{
  7882  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7883  		},
  7884  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}},
  7885  	}, {
  7886  		"invalid image pull policy",
  7887  		line(),
  7888  		[]core.EphemeralContainer{
  7889  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}},
  7890  		},
  7891  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}},
  7892  	}, {
  7893  		"TargetContainerName doesn't exist",
  7894  		line(),
  7895  		[]core.EphemeralContainer{{
  7896  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7897  			TargetContainerName:      "bogus",
  7898  		}},
  7899  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}},
  7900  	}, {
  7901  		"Targets an ephemeral container",
  7902  		line(),
  7903  		[]core.EphemeralContainer{{
  7904  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7905  		}, {
  7906  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7907  			TargetContainerName:      "debug",
  7908  		}},
  7909  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}},
  7910  	}, {
  7911  		"Container uses disallowed field: Lifecycle",
  7912  		line(),
  7913  		[]core.EphemeralContainer{{
  7914  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7915  				Name:                     "debug",
  7916  				Image:                    "image",
  7917  				ImagePullPolicy:          "IfNotPresent",
  7918  				TerminationMessagePolicy: "File",
  7919  				Lifecycle: &core.Lifecycle{
  7920  					PreStop: &core.LifecycleHandler{
  7921  						Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  7922  					},
  7923  				},
  7924  			},
  7925  		}},
  7926  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  7927  	}, {
  7928  		"Container uses disallowed field: LivenessProbe",
  7929  		line(),
  7930  		[]core.EphemeralContainer{{
  7931  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7932  				Name:                     "debug",
  7933  				Image:                    "image",
  7934  				ImagePullPolicy:          "IfNotPresent",
  7935  				TerminationMessagePolicy: "File",
  7936  				LivenessProbe: &core.Probe{
  7937  					ProbeHandler: core.ProbeHandler{
  7938  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7939  					},
  7940  					SuccessThreshold: 1,
  7941  				},
  7942  			},
  7943  		}},
  7944  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}},
  7945  	}, {
  7946  		"Container uses disallowed field: Ports",
  7947  		line(),
  7948  		[]core.EphemeralContainer{{
  7949  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7950  				Name:                     "debug",
  7951  				Image:                    "image",
  7952  				ImagePullPolicy:          "IfNotPresent",
  7953  				TerminationMessagePolicy: "File",
  7954  				Ports: []core.ContainerPort{
  7955  					{Protocol: "TCP", ContainerPort: 80},
  7956  				},
  7957  			},
  7958  		}},
  7959  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}},
  7960  	}, {
  7961  		"Container uses disallowed field: ReadinessProbe",
  7962  		line(),
  7963  		[]core.EphemeralContainer{{
  7964  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7965  				Name:                     "debug",
  7966  				Image:                    "image",
  7967  				ImagePullPolicy:          "IfNotPresent",
  7968  				TerminationMessagePolicy: "File",
  7969  				ReadinessProbe: &core.Probe{
  7970  					ProbeHandler: core.ProbeHandler{
  7971  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7972  					},
  7973  				},
  7974  			},
  7975  		}},
  7976  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}},
  7977  	}, {
  7978  		"Container uses disallowed field: StartupProbe",
  7979  		line(),
  7980  		[]core.EphemeralContainer{{
  7981  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7982  				Name:                     "debug",
  7983  				Image:                    "image",
  7984  				ImagePullPolicy:          "IfNotPresent",
  7985  				TerminationMessagePolicy: "File",
  7986  				StartupProbe: &core.Probe{
  7987  					ProbeHandler: core.ProbeHandler{
  7988  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7989  					},
  7990  					SuccessThreshold: 1,
  7991  				},
  7992  			},
  7993  		}},
  7994  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}},
  7995  	}, {
  7996  		"Container uses disallowed field: Resources",
  7997  		line(),
  7998  		[]core.EphemeralContainer{{
  7999  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8000  				Name:                     "debug",
  8001  				Image:                    "image",
  8002  				ImagePullPolicy:          "IfNotPresent",
  8003  				TerminationMessagePolicy: "File",
  8004  				Resources: core.ResourceRequirements{
  8005  					Limits: core.ResourceList{
  8006  						core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  8007  					},
  8008  				},
  8009  			},
  8010  		}},
  8011  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}},
  8012  	}, {
  8013  		"Container uses disallowed field: VolumeMount.SubPath",
  8014  		line(),
  8015  		[]core.EphemeralContainer{{
  8016  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8017  				Name:                     "debug",
  8018  				Image:                    "image",
  8019  				ImagePullPolicy:          "IfNotPresent",
  8020  				TerminationMessagePolicy: "File",
  8021  				VolumeMounts: []core.VolumeMount{
  8022  					{Name: "vol", MountPath: "/vol"},
  8023  					{Name: "vol", MountPath: "/volsub", SubPath: "foo"},
  8024  				},
  8025  			},
  8026  		}},
  8027  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}},
  8028  	}, {
  8029  		"Container uses disallowed field: VolumeMount.SubPathExpr",
  8030  		line(),
  8031  		[]core.EphemeralContainer{{
  8032  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8033  				Name:                     "debug",
  8034  				Image:                    "image",
  8035  				ImagePullPolicy:          "IfNotPresent",
  8036  				TerminationMessagePolicy: "File",
  8037  				VolumeMounts: []core.VolumeMount{
  8038  					{Name: "vol", MountPath: "/vol"},
  8039  					{Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"},
  8040  				},
  8041  			},
  8042  		}},
  8043  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}},
  8044  	}, {
  8045  		"Disallowed field with other errors should only return a single Forbidden",
  8046  		line(),
  8047  		[]core.EphemeralContainer{{
  8048  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8049  				Name:                     "debug",
  8050  				Image:                    "image",
  8051  				ImagePullPolicy:          "IfNotPresent",
  8052  				TerminationMessagePolicy: "File",
  8053  				Lifecycle: &core.Lifecycle{
  8054  					PreStop: &core.LifecycleHandler{
  8055  						Exec: &core.ExecAction{Command: []string{}},
  8056  					},
  8057  				},
  8058  			},
  8059  		}},
  8060  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  8061  	}, {
  8062  		"Container uses disallowed field: ResizePolicy",
  8063  		line(),
  8064  		[]core.EphemeralContainer{{
  8065  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8066  				Name:                     "resources-resize-policy",
  8067  				Image:                    "image",
  8068  				ImagePullPolicy:          "IfNotPresent",
  8069  				TerminationMessagePolicy: "File",
  8070  				ResizePolicy: []core.ContainerResizePolicy{
  8071  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8072  				},
  8073  			},
  8074  		}},
  8075  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}},
  8076  	}, {
  8077  		"Forbidden RestartPolicy: Always",
  8078  		line(),
  8079  		[]core.EphemeralContainer{{
  8080  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8081  				Name:                     "foo",
  8082  				Image:                    "image",
  8083  				ImagePullPolicy:          "IfNotPresent",
  8084  				TerminationMessagePolicy: "File",
  8085  				RestartPolicy:            &containerRestartPolicyAlways,
  8086  			},
  8087  		}},
  8088  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8089  	}, {
  8090  		"Forbidden RestartPolicy: OnFailure",
  8091  		line(),
  8092  		[]core.EphemeralContainer{{
  8093  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8094  				Name:                     "foo",
  8095  				Image:                    "image",
  8096  				ImagePullPolicy:          "IfNotPresent",
  8097  				TerminationMessagePolicy: "File",
  8098  				RestartPolicy:            &containerRestartPolicyOnFailure,
  8099  			},
  8100  		}},
  8101  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8102  	}, {
  8103  		"Forbidden RestartPolicy: Never",
  8104  		line(),
  8105  		[]core.EphemeralContainer{{
  8106  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8107  				Name:                     "foo",
  8108  				Image:                    "image",
  8109  				ImagePullPolicy:          "IfNotPresent",
  8110  				TerminationMessagePolicy: "File",
  8111  				RestartPolicy:            &containerRestartPolicyNever,
  8112  			},
  8113  		}},
  8114  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8115  	}, {
  8116  		"Forbidden RestartPolicy: invalid",
  8117  		line(),
  8118  		[]core.EphemeralContainer{{
  8119  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8120  				Name:                     "foo",
  8121  				Image:                    "image",
  8122  				ImagePullPolicy:          "IfNotPresent",
  8123  				TerminationMessagePolicy: "File",
  8124  				RestartPolicy:            &containerRestartPolicyInvalid,
  8125  			},
  8126  		}},
  8127  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8128  	}, {
  8129  		"Forbidden RestartPolicy: empty",
  8130  		line(),
  8131  		[]core.EphemeralContainer{{
  8132  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8133  				Name:                     "foo",
  8134  				Image:                    "image",
  8135  				ImagePullPolicy:          "IfNotPresent",
  8136  				TerminationMessagePolicy: "File",
  8137  				RestartPolicy:            &containerRestartPolicyEmpty,
  8138  			},
  8139  		}},
  8140  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8141  	},
  8142  	}
  8143  
  8144  	var PodRestartPolicy core.RestartPolicy
  8145  
  8146  	for _, tc := range tcs {
  8147  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  8148  
  8149  			PodRestartPolicy = "Never"
  8150  			errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8151  			if len(errs) == 0 {
  8152  				t.Fatal("expected error but received none")
  8153  			}
  8154  
  8155  			PodRestartPolicy = "Always"
  8156  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8157  			if len(errs) == 0 {
  8158  				t.Fatal("expected error but received none")
  8159  			}
  8160  
  8161  			PodRestartPolicy = "OnFailure"
  8162  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8163  			if len(errs) == 0 {
  8164  				t.Fatal("expected error but received none")
  8165  			}
  8166  
  8167  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  8168  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  8169  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  8170  			}
  8171  		})
  8172  	}
  8173  }
  8174  
  8175  func TestValidateWindowsPodSecurityContext(t *testing.T) {
  8176  	validWindowsSC := &core.PodSecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}
  8177  	invalidWindowsSC := &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummyRole"}}
  8178  	cases := map[string]struct {
  8179  		podSec      *core.PodSpec
  8180  		expectErr   bool
  8181  		errorType   field.ErrorType
  8182  		errorDetail string
  8183  	}{
  8184  		"valid SC, windows, no error": {
  8185  			podSec:    &core.PodSpec{SecurityContext: validWindowsSC},
  8186  			expectErr: false,
  8187  		},
  8188  		"invalid SC, windows, error": {
  8189  			podSec:      &core.PodSpec{SecurityContext: invalidWindowsSC},
  8190  			errorType:   "FieldValueForbidden",
  8191  			errorDetail: "cannot be set for a windows pod",
  8192  			expectErr:   true,
  8193  		},
  8194  	}
  8195  	for k, v := range cases {
  8196  		t.Run(k, func(t *testing.T) {
  8197  			errs := validateWindows(v.podSec, field.NewPath("field"))
  8198  			if v.expectErr && len(errs) > 0 {
  8199  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  8200  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  8201  				}
  8202  			} else if v.expectErr && len(errs) == 0 {
  8203  				t.Errorf("Unexpected success")
  8204  			}
  8205  			if !v.expectErr && len(errs) != 0 {
  8206  				t.Errorf("Unexpected error(s): %v", errs)
  8207  			}
  8208  		})
  8209  	}
  8210  }
  8211  
  8212  func TestValidateLinuxPodSecurityContext(t *testing.T) {
  8213  	runAsUser := int64(1)
  8214  	validLinuxSC := &core.PodSecurityContext{
  8215  		SELinuxOptions: &core.SELinuxOptions{
  8216  			User:  "user",
  8217  			Role:  "role",
  8218  			Type:  "type",
  8219  			Level: "level",
  8220  		},
  8221  		RunAsUser: &runAsUser,
  8222  	}
  8223  	invalidLinuxSC := &core.PodSecurityContext{
  8224  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
  8225  	}
  8226  
  8227  	cases := map[string]struct {
  8228  		podSpec     *core.PodSpec
  8229  		expectErr   bool
  8230  		errorType   field.ErrorType
  8231  		errorDetail string
  8232  	}{
  8233  		"valid SC, linux, no error": {
  8234  			podSpec:   &core.PodSpec{SecurityContext: validLinuxSC},
  8235  			expectErr: false,
  8236  		},
  8237  		"invalid SC, linux, error": {
  8238  			podSpec:     &core.PodSpec{SecurityContext: invalidLinuxSC},
  8239  			errorType:   "FieldValueForbidden",
  8240  			errorDetail: "windows options cannot be set for a linux pod",
  8241  			expectErr:   true,
  8242  		},
  8243  	}
  8244  	for k, v := range cases {
  8245  		t.Run(k, func(t *testing.T) {
  8246  			errs := validateLinux(v.podSpec, field.NewPath("field"))
  8247  			if v.expectErr && len(errs) > 0 {
  8248  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  8249  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  8250  				}
  8251  			} else if v.expectErr && len(errs) == 0 {
  8252  				t.Errorf("Unexpected success")
  8253  			}
  8254  			if !v.expectErr && len(errs) != 0 {
  8255  				t.Errorf("Unexpected error(s): %v", errs)
  8256  			}
  8257  		})
  8258  	}
  8259  }
  8260  
  8261  func TestValidateContainers(t *testing.T) {
  8262  	volumeDevices := make(map[string]core.VolumeSource)
  8263  	capabilities.SetForTests(capabilities.Capabilities{
  8264  		AllowPrivileged: true,
  8265  	})
  8266  
  8267  	successCase := []core.Container{
  8268  		{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8269  		// backwards compatibility to ensure containers in pod template spec do not check for this
  8270  		{Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8271  		{Name: "ghi", Image: " some  ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8272  		{Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8273  		{Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {
  8274  			Name:  "life-123",
  8275  			Image: "image",
  8276  			Lifecycle: &core.Lifecycle{
  8277  				PreStop: &core.LifecycleHandler{
  8278  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  8279  				},
  8280  			},
  8281  			ImagePullPolicy:          "IfNotPresent",
  8282  			TerminationMessagePolicy: "File",
  8283  		}, {
  8284  			Name:  "resources-test",
  8285  			Image: "image",
  8286  			Resources: core.ResourceRequirements{
  8287  				Limits: core.ResourceList{
  8288  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8289  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8290  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8291  				},
  8292  			},
  8293  			ImagePullPolicy:          "IfNotPresent",
  8294  			TerminationMessagePolicy: "File",
  8295  		}, {
  8296  			Name:  "resources-test-with-request-and-limit",
  8297  			Image: "image",
  8298  			Resources: core.ResourceRequirements{
  8299  				Requests: core.ResourceList{
  8300  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8301  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8302  				},
  8303  				Limits: core.ResourceList{
  8304  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8305  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8306  				},
  8307  			},
  8308  			ImagePullPolicy:          "IfNotPresent",
  8309  			TerminationMessagePolicy: "File",
  8310  		}, {
  8311  			Name:  "resources-request-limit-simple",
  8312  			Image: "image",
  8313  			Resources: core.ResourceRequirements{
  8314  				Requests: core.ResourceList{
  8315  					core.ResourceName(core.ResourceCPU): resource.MustParse("8"),
  8316  				},
  8317  				Limits: core.ResourceList{
  8318  					core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  8319  				},
  8320  			},
  8321  			ImagePullPolicy:          "IfNotPresent",
  8322  			TerminationMessagePolicy: "File",
  8323  		}, {
  8324  			Name:  "resources-request-limit-edge",
  8325  			Image: "image",
  8326  			Resources: core.ResourceRequirements{
  8327  				Requests: core.ResourceList{
  8328  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8329  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8330  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8331  				},
  8332  				Limits: core.ResourceList{
  8333  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8334  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8335  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8336  				},
  8337  			},
  8338  			ImagePullPolicy:          "IfNotPresent",
  8339  			TerminationMessagePolicy: "File",
  8340  		}, {
  8341  			Name:  "resources-request-limit-partials",
  8342  			Image: "image",
  8343  			Resources: core.ResourceRequirements{
  8344  				Requests: core.ResourceList{
  8345  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  8346  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8347  				},
  8348  				Limits: core.ResourceList{
  8349  					core.ResourceName(core.ResourceCPU):  resource.MustParse("10"),
  8350  					core.ResourceName("my.org/resource"): resource.MustParse("10"),
  8351  				},
  8352  			},
  8353  			ImagePullPolicy:          "IfNotPresent",
  8354  			TerminationMessagePolicy: "File",
  8355  		}, {
  8356  			Name:  "resources-request",
  8357  			Image: "image",
  8358  			Resources: core.ResourceRequirements{
  8359  				Requests: core.ResourceList{
  8360  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  8361  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8362  				},
  8363  			},
  8364  			ImagePullPolicy:          "IfNotPresent",
  8365  			TerminationMessagePolicy: "File",
  8366  		}, {
  8367  			Name:  "resources-resize-policy",
  8368  			Image: "image",
  8369  			ResizePolicy: []core.ContainerResizePolicy{
  8370  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8371  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  8372  			},
  8373  			ImagePullPolicy:          "IfNotPresent",
  8374  			TerminationMessagePolicy: "File",
  8375  		}, {
  8376  			Name:  "same-host-port-different-protocol",
  8377  			Image: "image",
  8378  			Ports: []core.ContainerPort{
  8379  				{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  8380  				{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  8381  			},
  8382  			ImagePullPolicy:          "IfNotPresent",
  8383  			TerminationMessagePolicy: "File",
  8384  		}, {
  8385  			Name:                     "fallback-to-logs-termination-message",
  8386  			Image:                    "image",
  8387  			ImagePullPolicy:          "IfNotPresent",
  8388  			TerminationMessagePolicy: "FallbackToLogsOnError",
  8389  		}, {
  8390  			Name:                     "file-termination-message",
  8391  			Image:                    "image",
  8392  			ImagePullPolicy:          "IfNotPresent",
  8393  			TerminationMessagePolicy: "File",
  8394  		}, {
  8395  			Name:                     "env-from-source",
  8396  			Image:                    "image",
  8397  			ImagePullPolicy:          "IfNotPresent",
  8398  			TerminationMessagePolicy: "File",
  8399  			EnvFrom: []core.EnvFromSource{{
  8400  				ConfigMapRef: &core.ConfigMapEnvSource{
  8401  					LocalObjectReference: core.LocalObjectReference{
  8402  						Name: "test",
  8403  					},
  8404  				},
  8405  			}},
  8406  		},
  8407  		{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, {
  8408  			Name:  "live-123",
  8409  			Image: "image",
  8410  			LivenessProbe: &core.Probe{
  8411  				ProbeHandler: core.ProbeHandler{
  8412  					TCPSocket: &core.TCPSocketAction{
  8413  						Port: intstr.FromInt32(80),
  8414  					},
  8415  				},
  8416  				SuccessThreshold: 1,
  8417  			},
  8418  			ImagePullPolicy:          "IfNotPresent",
  8419  			TerminationMessagePolicy: "File",
  8420  		}, {
  8421  			Name:  "startup-123",
  8422  			Image: "image",
  8423  			StartupProbe: &core.Probe{
  8424  				ProbeHandler: core.ProbeHandler{
  8425  					TCPSocket: &core.TCPSocketAction{
  8426  						Port: intstr.FromInt32(80),
  8427  					},
  8428  				},
  8429  				SuccessThreshold: 1,
  8430  			},
  8431  			ImagePullPolicy:          "IfNotPresent",
  8432  			TerminationMessagePolicy: "File",
  8433  		}, {
  8434  			Name:                     "resize-policy-cpu",
  8435  			Image:                    "image",
  8436  			ImagePullPolicy:          "IfNotPresent",
  8437  			TerminationMessagePolicy: "File",
  8438  			ResizePolicy: []core.ContainerResizePolicy{
  8439  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8440  			},
  8441  		}, {
  8442  			Name:                     "resize-policy-mem",
  8443  			Image:                    "image",
  8444  			ImagePullPolicy:          "IfNotPresent",
  8445  			TerminationMessagePolicy: "File",
  8446  			ResizePolicy: []core.ContainerResizePolicy{
  8447  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  8448  			},
  8449  		}, {
  8450  			Name:                     "resize-policy-cpu-and-mem",
  8451  			Image:                    "image",
  8452  			ImagePullPolicy:          "IfNotPresent",
  8453  			TerminationMessagePolicy: "File",
  8454  			ResizePolicy: []core.ContainerResizePolicy{
  8455  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  8456  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  8457  			},
  8458  		},
  8459  	}
  8460  
  8461  	var PodRestartPolicy core.RestartPolicy = "Always"
  8462  	if errs := validateContainers(successCase, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  8463  		t.Errorf("expected success: %v", errs)
  8464  	}
  8465  
  8466  	capabilities.SetForTests(capabilities.Capabilities{
  8467  		AllowPrivileged: false,
  8468  	})
  8469  	errorCases := []struct {
  8470  		title, line    string
  8471  		containers     []core.Container
  8472  		expectedErrors field.ErrorList
  8473  	}{{
  8474  		"zero-length name",
  8475  		line(),
  8476  		[]core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8477  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}},
  8478  	}, {
  8479  		"zero-length-image",
  8480  		line(),
  8481  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8482  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  8483  	}, {
  8484  		"name > 63 characters",
  8485  		line(),
  8486  		[]core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8487  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  8488  	}, {
  8489  		"name not a DNS label",
  8490  		line(),
  8491  		[]core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8492  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  8493  	}, {
  8494  		"name not unique",
  8495  		line(),
  8496  		[]core.Container{
  8497  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8498  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8499  		},
  8500  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}},
  8501  	}, {
  8502  		"zero-length image",
  8503  		line(),
  8504  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8505  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  8506  	}, {
  8507  		"host port not unique",
  8508  		line(),
  8509  		[]core.Container{
  8510  			{Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
  8511  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8512  			{Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
  8513  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8514  		},
  8515  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}},
  8516  	}, {
  8517  		"invalid env var name",
  8518  		line(),
  8519  		[]core.Container{
  8520  			{Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8521  		},
  8522  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}},
  8523  	}, {
  8524  		"unknown volume name",
  8525  		line(),
  8526  		[]core.Container{
  8527  			{Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}},
  8528  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8529  		},
  8530  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}},
  8531  	}, {
  8532  		"invalid lifecycle, no exec command.",
  8533  		line(),
  8534  		[]core.Container{{
  8535  			Name:  "life-123",
  8536  			Image: "image",
  8537  			Lifecycle: &core.Lifecycle{
  8538  				PreStop: &core.LifecycleHandler{
  8539  					Exec: &core.ExecAction{},
  8540  				},
  8541  			},
  8542  			ImagePullPolicy:          "IfNotPresent",
  8543  			TerminationMessagePolicy: "File",
  8544  		}},
  8545  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}},
  8546  	}, {
  8547  		"invalid lifecycle, no http path.",
  8548  		line(),
  8549  		[]core.Container{{
  8550  			Name:  "life-123",
  8551  			Image: "image",
  8552  			Lifecycle: &core.Lifecycle{
  8553  				PreStop: &core.LifecycleHandler{
  8554  					HTTPGet: &core.HTTPGetAction{
  8555  						Port:   intstr.FromInt32(80),
  8556  						Scheme: "HTTP",
  8557  					},
  8558  				},
  8559  			},
  8560  			ImagePullPolicy:          "IfNotPresent",
  8561  			TerminationMessagePolicy: "File",
  8562  		}},
  8563  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}},
  8564  	}, {
  8565  		"invalid lifecycle, no http port.",
  8566  		line(),
  8567  		[]core.Container{{
  8568  			Name:  "life-123",
  8569  			Image: "image",
  8570  			Lifecycle: &core.Lifecycle{
  8571  				PreStop: &core.LifecycleHandler{
  8572  					HTTPGet: &core.HTTPGetAction{
  8573  						Path:   "/",
  8574  						Scheme: "HTTP",
  8575  					},
  8576  				},
  8577  			},
  8578  			ImagePullPolicy:          "IfNotPresent",
  8579  			TerminationMessagePolicy: "File",
  8580  		}},
  8581  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}},
  8582  	}, {
  8583  		"invalid lifecycle, no http scheme.",
  8584  		line(),
  8585  		[]core.Container{{
  8586  			Name:  "life-123",
  8587  			Image: "image",
  8588  			Lifecycle: &core.Lifecycle{
  8589  				PreStop: &core.LifecycleHandler{
  8590  					HTTPGet: &core.HTTPGetAction{
  8591  						Path: "/",
  8592  						Port: intstr.FromInt32(80),
  8593  					},
  8594  				},
  8595  			},
  8596  			ImagePullPolicy:          "IfNotPresent",
  8597  			TerminationMessagePolicy: "File",
  8598  		}},
  8599  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}},
  8600  	}, {
  8601  		"invalid lifecycle, no tcp socket port.",
  8602  		line(),
  8603  		[]core.Container{{
  8604  			Name:  "life-123",
  8605  			Image: "image",
  8606  			Lifecycle: &core.Lifecycle{
  8607  				PreStop: &core.LifecycleHandler{
  8608  					TCPSocket: &core.TCPSocketAction{},
  8609  				},
  8610  			},
  8611  			ImagePullPolicy:          "IfNotPresent",
  8612  			TerminationMessagePolicy: "File",
  8613  		}},
  8614  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8615  	}, {
  8616  		"invalid lifecycle, zero tcp socket port.",
  8617  		line(),
  8618  		[]core.Container{{
  8619  			Name:  "life-123",
  8620  			Image: "image",
  8621  			Lifecycle: &core.Lifecycle{
  8622  				PreStop: &core.LifecycleHandler{
  8623  					TCPSocket: &core.TCPSocketAction{
  8624  						Port: intstr.FromInt32(0),
  8625  					},
  8626  				},
  8627  			},
  8628  			ImagePullPolicy:          "IfNotPresent",
  8629  			TerminationMessagePolicy: "File",
  8630  		}},
  8631  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8632  	}, {
  8633  		"invalid lifecycle, no action.",
  8634  		line(),
  8635  		[]core.Container{{
  8636  			Name:  "life-123",
  8637  			Image: "image",
  8638  			Lifecycle: &core.Lifecycle{
  8639  				PreStop: &core.LifecycleHandler{},
  8640  			},
  8641  			ImagePullPolicy:          "IfNotPresent",
  8642  			TerminationMessagePolicy: "File",
  8643  		}},
  8644  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}},
  8645  	}, {
  8646  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  8647  		line(),
  8648  		[]core.Container{{
  8649  			Name:  "life-123",
  8650  			Image: "image",
  8651  			ReadinessProbe: &core.Probe{
  8652  				ProbeHandler: core.ProbeHandler{
  8653  					TCPSocket: &core.TCPSocketAction{
  8654  						Port: intstr.FromInt32(80),
  8655  					},
  8656  				},
  8657  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  8658  			},
  8659  			ImagePullPolicy:          "IfNotPresent",
  8660  			TerminationMessagePolicy: "File",
  8661  		}},
  8662  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}},
  8663  	}, {
  8664  		"invalid liveness probe, no tcp socket port.",
  8665  		line(),
  8666  		[]core.Container{{
  8667  			Name:  "live-123",
  8668  			Image: "image",
  8669  			LivenessProbe: &core.Probe{
  8670  				ProbeHandler: core.ProbeHandler{
  8671  					TCPSocket: &core.TCPSocketAction{},
  8672  				},
  8673  				SuccessThreshold: 1,
  8674  			},
  8675  			ImagePullPolicy:          "IfNotPresent",
  8676  			TerminationMessagePolicy: "File",
  8677  		}},
  8678  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}},
  8679  	}, {
  8680  		"invalid liveness probe, no action.",
  8681  		line(),
  8682  		[]core.Container{{
  8683  			Name:  "live-123",
  8684  			Image: "image",
  8685  			LivenessProbe: &core.Probe{
  8686  				ProbeHandler:     core.ProbeHandler{},
  8687  				SuccessThreshold: 1,
  8688  			},
  8689  			ImagePullPolicy:          "IfNotPresent",
  8690  			TerminationMessagePolicy: "File",
  8691  		}},
  8692  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}},
  8693  	}, {
  8694  		"invalid liveness probe, successThreshold != 1",
  8695  		line(),
  8696  		[]core.Container{{
  8697  			Name:  "live-123",
  8698  			Image: "image",
  8699  			LivenessProbe: &core.Probe{
  8700  				ProbeHandler: core.ProbeHandler{
  8701  					TCPSocket: &core.TCPSocketAction{
  8702  						Port: intstr.FromInt32(80),
  8703  					},
  8704  				},
  8705  				SuccessThreshold: 2,
  8706  			},
  8707  			ImagePullPolicy:          "IfNotPresent",
  8708  			TerminationMessagePolicy: "File",
  8709  		}},
  8710  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}},
  8711  	}, {
  8712  		"invalid startup probe, successThreshold != 1",
  8713  		line(),
  8714  		[]core.Container{{
  8715  			Name:  "startup-123",
  8716  			Image: "image",
  8717  			StartupProbe: &core.Probe{
  8718  				ProbeHandler: core.ProbeHandler{
  8719  					TCPSocket: &core.TCPSocketAction{
  8720  						Port: intstr.FromInt32(80),
  8721  					},
  8722  				},
  8723  				SuccessThreshold: 2,
  8724  			},
  8725  			ImagePullPolicy:          "IfNotPresent",
  8726  			TerminationMessagePolicy: "File",
  8727  		}},
  8728  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}},
  8729  	}, {
  8730  		"invalid liveness probe, negative numbers",
  8731  		line(),
  8732  		[]core.Container{{
  8733  			Name:  "live-123",
  8734  			Image: "image",
  8735  			LivenessProbe: &core.Probe{
  8736  				ProbeHandler: core.ProbeHandler{
  8737  					TCPSocket: &core.TCPSocketAction{
  8738  						Port: intstr.FromInt32(80),
  8739  					},
  8740  				},
  8741  				InitialDelaySeconds:           -1,
  8742  				TimeoutSeconds:                -1,
  8743  				PeriodSeconds:                 -1,
  8744  				SuccessThreshold:              -1,
  8745  				FailureThreshold:              -1,
  8746  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8747  			},
  8748  			ImagePullPolicy:          "IfNotPresent",
  8749  			TerminationMessagePolicy: "File",
  8750  		}},
  8751  		field.ErrorList{
  8752  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"},
  8753  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"},
  8754  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"},
  8755  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8756  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"},
  8757  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"},
  8758  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8759  		},
  8760  	}, {
  8761  		"invalid readiness probe, negative numbers",
  8762  		line(),
  8763  		[]core.Container{{
  8764  			Name:  "ready-123",
  8765  			Image: "image",
  8766  			ReadinessProbe: &core.Probe{
  8767  				ProbeHandler: core.ProbeHandler{
  8768  					TCPSocket: &core.TCPSocketAction{
  8769  						Port: intstr.FromInt32(80),
  8770  					},
  8771  				},
  8772  				InitialDelaySeconds:           -1,
  8773  				TimeoutSeconds:                -1,
  8774  				PeriodSeconds:                 -1,
  8775  				SuccessThreshold:              -1,
  8776  				FailureThreshold:              -1,
  8777  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8778  			},
  8779  			ImagePullPolicy:          "IfNotPresent",
  8780  			TerminationMessagePolicy: "File",
  8781  		}},
  8782  		field.ErrorList{
  8783  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"},
  8784  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"},
  8785  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"},
  8786  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"},
  8787  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"},
  8788  			// terminationGracePeriodSeconds returns multiple validation errors here:
  8789  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0
  8790  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8791  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes
  8792  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8793  		},
  8794  	}, {
  8795  		"invalid startup probe, negative numbers",
  8796  		line(),
  8797  		[]core.Container{{
  8798  			Name:  "startup-123",
  8799  			Image: "image",
  8800  			StartupProbe: &core.Probe{
  8801  				ProbeHandler: core.ProbeHandler{
  8802  					TCPSocket: &core.TCPSocketAction{
  8803  						Port: intstr.FromInt32(80),
  8804  					},
  8805  				},
  8806  				InitialDelaySeconds:           -1,
  8807  				TimeoutSeconds:                -1,
  8808  				PeriodSeconds:                 -1,
  8809  				SuccessThreshold:              -1,
  8810  				FailureThreshold:              -1,
  8811  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8812  			},
  8813  			ImagePullPolicy:          "IfNotPresent",
  8814  			TerminationMessagePolicy: "File",
  8815  		}},
  8816  		field.ErrorList{
  8817  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"},
  8818  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"},
  8819  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"},
  8820  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8821  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"},
  8822  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"},
  8823  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8824  		},
  8825  	}, {
  8826  		"invalid message termination policy",
  8827  		line(),
  8828  		[]core.Container{{
  8829  			Name:                     "life-123",
  8830  			Image:                    "image",
  8831  			ImagePullPolicy:          "IfNotPresent",
  8832  			TerminationMessagePolicy: "Unknown",
  8833  		}},
  8834  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}},
  8835  	}, {
  8836  		"empty message termination policy",
  8837  		line(),
  8838  		[]core.Container{{
  8839  			Name:                     "life-123",
  8840  			Image:                    "image",
  8841  			ImagePullPolicy:          "IfNotPresent",
  8842  			TerminationMessagePolicy: "",
  8843  		}},
  8844  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}},
  8845  	}, {
  8846  		"privilege disabled",
  8847  		line(),
  8848  		[]core.Container{{
  8849  			Name:                     "abc",
  8850  			Image:                    "image",
  8851  			SecurityContext:          fakeValidSecurityContext(true),
  8852  			ImagePullPolicy:          "IfNotPresent",
  8853  			TerminationMessagePolicy: "File",
  8854  		}},
  8855  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}},
  8856  	}, {
  8857  		"invalid compute resource",
  8858  		line(),
  8859  		[]core.Container{{
  8860  			Name:  "abc-123",
  8861  			Image: "image",
  8862  			Resources: core.ResourceRequirements{
  8863  				Limits: core.ResourceList{
  8864  					"disk": resource.MustParse("10G"),
  8865  				},
  8866  			},
  8867  			ImagePullPolicy:          "IfNotPresent",
  8868  			TerminationMessagePolicy: "File",
  8869  		}},
  8870  		field.ErrorList{
  8871  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8872  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8873  		},
  8874  	}, {
  8875  		"Resource CPU invalid",
  8876  		line(),
  8877  		[]core.Container{{
  8878  			Name:  "abc-123",
  8879  			Image: "image",
  8880  			Resources: core.ResourceRequirements{
  8881  				Limits: getResourceLimits("-10", "0"),
  8882  			},
  8883  			ImagePullPolicy:          "IfNotPresent",
  8884  			TerminationMessagePolicy: "File",
  8885  		}},
  8886  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}},
  8887  	}, {
  8888  		"Resource Requests CPU invalid",
  8889  		line(),
  8890  		[]core.Container{{
  8891  			Name:  "abc-123",
  8892  			Image: "image",
  8893  			Resources: core.ResourceRequirements{
  8894  				Requests: getResourceLimits("-10", "0"),
  8895  			},
  8896  			ImagePullPolicy:          "IfNotPresent",
  8897  			TerminationMessagePolicy: "File",
  8898  		}},
  8899  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}},
  8900  	}, {
  8901  		"Resource Memory invalid",
  8902  		line(),
  8903  		[]core.Container{{
  8904  			Name:  "abc-123",
  8905  			Image: "image",
  8906  			Resources: core.ResourceRequirements{
  8907  				Limits: getResourceLimits("0", "-10"),
  8908  			},
  8909  			ImagePullPolicy:          "IfNotPresent",
  8910  			TerminationMessagePolicy: "File",
  8911  		}},
  8912  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}},
  8913  	}, {
  8914  		"Request limit simple invalid",
  8915  		line(),
  8916  		[]core.Container{{
  8917  			Name:  "abc-123",
  8918  			Image: "image",
  8919  			Resources: core.ResourceRequirements{
  8920  				Limits:   getResourceLimits("5", "3"),
  8921  				Requests: getResourceLimits("6", "3"),
  8922  			},
  8923  			ImagePullPolicy:          "IfNotPresent",
  8924  			TerminationMessagePolicy: "File",
  8925  		}},
  8926  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8927  	}, {
  8928  		"Invalid storage limit request",
  8929  		line(),
  8930  		[]core.Container{{
  8931  			Name:  "abc-123",
  8932  			Image: "image",
  8933  			Resources: core.ResourceRequirements{
  8934  				Limits: core.ResourceList{
  8935  					core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI),
  8936  				},
  8937  			},
  8938  			ImagePullPolicy:          "IfNotPresent",
  8939  			TerminationMessagePolicy: "File",
  8940  		}},
  8941  		field.ErrorList{
  8942  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  8943  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  8944  		},
  8945  	}, {
  8946  		"CPU request limit multiple invalid",
  8947  		line(),
  8948  		[]core.Container{{
  8949  			Name:  "abc-123",
  8950  			Image: "image",
  8951  			Resources: core.ResourceRequirements{
  8952  				Limits:   getResourceLimits("5", "3"),
  8953  				Requests: getResourceLimits("6", "3"),
  8954  			},
  8955  			ImagePullPolicy:          "IfNotPresent",
  8956  			TerminationMessagePolicy: "File",
  8957  		}},
  8958  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8959  	}, {
  8960  		"Memory request limit multiple invalid",
  8961  		line(),
  8962  		[]core.Container{{
  8963  			Name:  "abc-123",
  8964  			Image: "image",
  8965  			Resources: core.ResourceRequirements{
  8966  				Limits:   getResourceLimits("5", "3"),
  8967  				Requests: getResourceLimits("5", "4"),
  8968  			},
  8969  			ImagePullPolicy:          "IfNotPresent",
  8970  			TerminationMessagePolicy: "File",
  8971  		}},
  8972  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8973  	}, {
  8974  		"Invalid env from",
  8975  		line(),
  8976  		[]core.Container{{
  8977  			Name:                     "env-from-source",
  8978  			Image:                    "image",
  8979  			ImagePullPolicy:          "IfNotPresent",
  8980  			TerminationMessagePolicy: "File",
  8981  			EnvFrom: []core.EnvFromSource{{
  8982  				ConfigMapRef: &core.ConfigMapEnvSource{
  8983  					LocalObjectReference: core.LocalObjectReference{
  8984  						Name: "$%^&*#",
  8985  					},
  8986  				},
  8987  			}},
  8988  		}},
  8989  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}},
  8990  	}, {
  8991  		"Unsupported resize policy for memory",
  8992  		line(),
  8993  		[]core.Container{{
  8994  			Name:                     "resize-policy-mem-invalid",
  8995  			Image:                    "image",
  8996  			ImagePullPolicy:          "IfNotPresent",
  8997  			TerminationMessagePolicy: "File",
  8998  			ResizePolicy: []core.ContainerResizePolicy{
  8999  				{ResourceName: "memory", RestartPolicy: "RestartContainerrrr"},
  9000  			},
  9001  		}},
  9002  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  9003  	}, {
  9004  		"Unsupported resize policy for CPU",
  9005  		line(),
  9006  		[]core.Container{{
  9007  			Name:                     "resize-policy-cpu-invalid",
  9008  			Image:                    "image",
  9009  			ImagePullPolicy:          "IfNotPresent",
  9010  			TerminationMessagePolicy: "File",
  9011  			ResizePolicy: []core.ContainerResizePolicy{
  9012  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequired"},
  9013  			},
  9014  		}},
  9015  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  9016  	}, {
  9017  		"Forbidden RestartPolicy: Always",
  9018  		line(),
  9019  		[]core.Container{{
  9020  			Name:                     "foo",
  9021  			Image:                    "image",
  9022  			ImagePullPolicy:          "IfNotPresent",
  9023  			TerminationMessagePolicy: "File",
  9024  			RestartPolicy:            &containerRestartPolicyAlways,
  9025  		}},
  9026  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9027  	}, {
  9028  		"Forbidden RestartPolicy: OnFailure",
  9029  		line(),
  9030  		[]core.Container{{
  9031  			Name:                     "foo",
  9032  			Image:                    "image",
  9033  			ImagePullPolicy:          "IfNotPresent",
  9034  			TerminationMessagePolicy: "File",
  9035  			RestartPolicy:            &containerRestartPolicyOnFailure,
  9036  		}},
  9037  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9038  	}, {
  9039  		"Forbidden RestartPolicy: Never",
  9040  		line(),
  9041  		[]core.Container{{
  9042  			Name:                     "foo",
  9043  			Image:                    "image",
  9044  			ImagePullPolicy:          "IfNotPresent",
  9045  			TerminationMessagePolicy: "File",
  9046  			RestartPolicy:            &containerRestartPolicyNever,
  9047  		}},
  9048  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9049  	}, {
  9050  		"Forbidden RestartPolicy: invalid",
  9051  		line(),
  9052  		[]core.Container{{
  9053  			Name:                     "foo",
  9054  			Image:                    "image",
  9055  			ImagePullPolicy:          "IfNotPresent",
  9056  			TerminationMessagePolicy: "File",
  9057  			RestartPolicy:            &containerRestartPolicyInvalid,
  9058  		}},
  9059  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9060  	}, {
  9061  		"Forbidden RestartPolicy: empty",
  9062  		line(),
  9063  		[]core.Container{{
  9064  			Name:                     "foo",
  9065  			Image:                    "image",
  9066  			ImagePullPolicy:          "IfNotPresent",
  9067  			TerminationMessagePolicy: "File",
  9068  			RestartPolicy:            &containerRestartPolicyEmpty,
  9069  		}},
  9070  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9071  	},
  9072  	}
  9073  
  9074  	for _, tc := range errorCases {
  9075  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  9076  			errs := validateContainers(tc.containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("containers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  9077  			if len(errs) == 0 {
  9078  				t.Fatal("expected error but received none")
  9079  			}
  9080  
  9081  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  9082  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  9083  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  9084  			}
  9085  		})
  9086  	}
  9087  }
  9088  
  9089  func TestValidateInitContainers(t *testing.T) {
  9090  	volumeDevices := make(map[string]core.VolumeSource)
  9091  	capabilities.SetForTests(capabilities.Capabilities{
  9092  		AllowPrivileged: true,
  9093  	})
  9094  
  9095  	containers := []core.Container{{
  9096  		Name:                     "app",
  9097  		Image:                    "nginx",
  9098  		ImagePullPolicy:          "IfNotPresent",
  9099  		TerminationMessagePolicy: "File",
  9100  	},
  9101  	}
  9102  
  9103  	successCase := []core.Container{{
  9104  		Name:  "container-1-same-host-port-different-protocol",
  9105  		Image: "image",
  9106  		Ports: []core.ContainerPort{
  9107  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  9108  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  9109  		},
  9110  		ImagePullPolicy:          "IfNotPresent",
  9111  		TerminationMessagePolicy: "File",
  9112  	}, {
  9113  		Name:  "container-2-same-host-port-different-protocol",
  9114  		Image: "image",
  9115  		Ports: []core.ContainerPort{
  9116  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  9117  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  9118  		},
  9119  		ImagePullPolicy:          "IfNotPresent",
  9120  		TerminationMessagePolicy: "File",
  9121  	}, {
  9122  		Name:                     "container-3-restart-always-with-lifecycle-hook-and-probes",
  9123  		Image:                    "image",
  9124  		ImagePullPolicy:          "IfNotPresent",
  9125  		TerminationMessagePolicy: "File",
  9126  		RestartPolicy:            &containerRestartPolicyAlways,
  9127  		Lifecycle: &core.Lifecycle{
  9128  			PostStart: &core.LifecycleHandler{
  9129  				Exec: &core.ExecAction{
  9130  					Command: []string{"echo", "post start"},
  9131  				},
  9132  			},
  9133  			PreStop: &core.LifecycleHandler{
  9134  				Exec: &core.ExecAction{
  9135  					Command: []string{"echo", "pre stop"},
  9136  				},
  9137  			},
  9138  		},
  9139  		LivenessProbe: &core.Probe{
  9140  			ProbeHandler: core.ProbeHandler{
  9141  				TCPSocket: &core.TCPSocketAction{
  9142  					Port: intstr.FromInt32(80),
  9143  				},
  9144  			},
  9145  			SuccessThreshold: 1,
  9146  		},
  9147  		ReadinessProbe: &core.Probe{
  9148  			ProbeHandler: core.ProbeHandler{
  9149  				TCPSocket: &core.TCPSocketAction{
  9150  					Port: intstr.FromInt32(80),
  9151  				},
  9152  			},
  9153  		},
  9154  		StartupProbe: &core.Probe{
  9155  			ProbeHandler: core.ProbeHandler{
  9156  				TCPSocket: &core.TCPSocketAction{
  9157  					Port: intstr.FromInt32(80),
  9158  				},
  9159  			},
  9160  			SuccessThreshold: 1,
  9161  		},
  9162  	},
  9163  	}
  9164  	var PodRestartPolicy core.RestartPolicy = "Never"
  9165  	if errs := validateInitContainers(successCase, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  9166  		t.Errorf("expected success: %v", errs)
  9167  	}
  9168  
  9169  	capabilities.SetForTests(capabilities.Capabilities{
  9170  		AllowPrivileged: false,
  9171  	})
  9172  	errorCases := []struct {
  9173  		title, line    string
  9174  		initContainers []core.Container
  9175  		expectedErrors field.ErrorList
  9176  	}{{
  9177  		"empty name",
  9178  		line(),
  9179  		[]core.Container{{
  9180  			Name:                     "",
  9181  			Image:                    "image",
  9182  			ImagePullPolicy:          "IfNotPresent",
  9183  			TerminationMessagePolicy: "File",
  9184  		}},
  9185  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}},
  9186  	}, {
  9187  		"name collision with regular container",
  9188  		line(),
  9189  		[]core.Container{{
  9190  			Name:                     "app",
  9191  			Image:                    "image",
  9192  			ImagePullPolicy:          "IfNotPresent",
  9193  			TerminationMessagePolicy: "File",
  9194  		}},
  9195  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}},
  9196  	}, {
  9197  		"invalid termination message policy",
  9198  		line(),
  9199  		[]core.Container{{
  9200  			Name:                     "init",
  9201  			Image:                    "image",
  9202  			ImagePullPolicy:          "IfNotPresent",
  9203  			TerminationMessagePolicy: "Unknown",
  9204  		}},
  9205  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}},
  9206  	}, {
  9207  		"duplicate names",
  9208  		line(),
  9209  		[]core.Container{{
  9210  			Name:                     "init",
  9211  			Image:                    "image",
  9212  			ImagePullPolicy:          "IfNotPresent",
  9213  			TerminationMessagePolicy: "File",
  9214  		}, {
  9215  			Name:                     "init",
  9216  			Image:                    "image",
  9217  			ImagePullPolicy:          "IfNotPresent",
  9218  			TerminationMessagePolicy: "File",
  9219  		}},
  9220  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}},
  9221  	}, {
  9222  		"duplicate ports",
  9223  		line(),
  9224  		[]core.Container{{
  9225  			Name:  "abc",
  9226  			Image: "image",
  9227  			Ports: []core.ContainerPort{{
  9228  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  9229  			}, {
  9230  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  9231  			}},
  9232  			ImagePullPolicy:          "IfNotPresent",
  9233  			TerminationMessagePolicy: "File",
  9234  		}},
  9235  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}},
  9236  	}, {
  9237  		"uses disallowed field: Lifecycle",
  9238  		line(),
  9239  		[]core.Container{{
  9240  			Name:                     "debug",
  9241  			Image:                    "image",
  9242  			ImagePullPolicy:          "IfNotPresent",
  9243  			TerminationMessagePolicy: "File",
  9244  			Lifecycle: &core.Lifecycle{
  9245  				PreStop: &core.LifecycleHandler{
  9246  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  9247  				},
  9248  			},
  9249  		}},
  9250  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}},
  9251  	}, {
  9252  		"uses disallowed field: LivenessProbe",
  9253  		line(),
  9254  		[]core.Container{{
  9255  			Name:                     "debug",
  9256  			Image:                    "image",
  9257  			ImagePullPolicy:          "IfNotPresent",
  9258  			TerminationMessagePolicy: "File",
  9259  			LivenessProbe: &core.Probe{
  9260  				ProbeHandler: core.ProbeHandler{
  9261  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9262  				},
  9263  				SuccessThreshold: 1,
  9264  			},
  9265  		}},
  9266  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}},
  9267  	}, {
  9268  		"uses disallowed field: ReadinessProbe",
  9269  		line(),
  9270  		[]core.Container{{
  9271  			Name:                     "debug",
  9272  			Image:                    "image",
  9273  			ImagePullPolicy:          "IfNotPresent",
  9274  			TerminationMessagePolicy: "File",
  9275  			ReadinessProbe: &core.Probe{
  9276  				ProbeHandler: core.ProbeHandler{
  9277  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9278  				},
  9279  			},
  9280  		}},
  9281  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}},
  9282  	}, {
  9283  		"Container uses disallowed field: StartupProbe",
  9284  		line(),
  9285  		[]core.Container{{
  9286  			Name:                     "debug",
  9287  			Image:                    "image",
  9288  			ImagePullPolicy:          "IfNotPresent",
  9289  			TerminationMessagePolicy: "File",
  9290  			StartupProbe: &core.Probe{
  9291  				ProbeHandler: core.ProbeHandler{
  9292  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9293  				},
  9294  				SuccessThreshold: 1,
  9295  			},
  9296  		}},
  9297  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  9298  	}, {
  9299  		"Disallowed field with other errors should only return a single Forbidden",
  9300  		line(),
  9301  		[]core.Container{{
  9302  			Name:                     "debug",
  9303  			Image:                    "image",
  9304  			ImagePullPolicy:          "IfNotPresent",
  9305  			TerminationMessagePolicy: "File",
  9306  			StartupProbe: &core.Probe{
  9307  				ProbeHandler: core.ProbeHandler{
  9308  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9309  				},
  9310  				InitialDelaySeconds:           -1,
  9311  				TimeoutSeconds:                -1,
  9312  				PeriodSeconds:                 -1,
  9313  				SuccessThreshold:              -1,
  9314  				FailureThreshold:              -1,
  9315  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  9316  			},
  9317  		}},
  9318  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  9319  	}, {
  9320  		"Not supported RestartPolicy: OnFailure",
  9321  		line(),
  9322  		[]core.Container{{
  9323  			Name:                     "init",
  9324  			Image:                    "image",
  9325  			ImagePullPolicy:          "IfNotPresent",
  9326  			TerminationMessagePolicy: "File",
  9327  			RestartPolicy:            &containerRestartPolicyOnFailure,
  9328  		}},
  9329  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}},
  9330  	}, {
  9331  		"Not supported RestartPolicy: Never",
  9332  		line(),
  9333  		[]core.Container{{
  9334  			Name:                     "init",
  9335  			Image:                    "image",
  9336  			ImagePullPolicy:          "IfNotPresent",
  9337  			TerminationMessagePolicy: "File",
  9338  			RestartPolicy:            &containerRestartPolicyNever,
  9339  		}},
  9340  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}},
  9341  	}, {
  9342  		"Not supported RestartPolicy: invalid",
  9343  		line(),
  9344  		[]core.Container{{
  9345  			Name:                     "init",
  9346  			Image:                    "image",
  9347  			ImagePullPolicy:          "IfNotPresent",
  9348  			TerminationMessagePolicy: "File",
  9349  			RestartPolicy:            &containerRestartPolicyInvalid,
  9350  		}},
  9351  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}},
  9352  	}, {
  9353  		"Not supported RestartPolicy: empty",
  9354  		line(),
  9355  		[]core.Container{{
  9356  			Name:                     "init",
  9357  			Image:                    "image",
  9358  			ImagePullPolicy:          "IfNotPresent",
  9359  			TerminationMessagePolicy: "File",
  9360  			RestartPolicy:            &containerRestartPolicyEmpty,
  9361  		}},
  9362  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}},
  9363  	}, {
  9364  		"invalid startup probe in restartable container, successThreshold != 1",
  9365  		line(),
  9366  		[]core.Container{{
  9367  			Name:                     "restartable-init",
  9368  			Image:                    "image",
  9369  			ImagePullPolicy:          "IfNotPresent",
  9370  			TerminationMessagePolicy: "File",
  9371  			RestartPolicy:            &containerRestartPolicyAlways,
  9372  			StartupProbe: &core.Probe{
  9373  				ProbeHandler: core.ProbeHandler{
  9374  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9375  				},
  9376  				SuccessThreshold: 2,
  9377  			},
  9378  		}},
  9379  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}},
  9380  	}, {
  9381  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  9382  		line(),
  9383  		[]core.Container{{
  9384  			Name:                     "life-123",
  9385  			Image:                    "image",
  9386  			ImagePullPolicy:          "IfNotPresent",
  9387  			TerminationMessagePolicy: "File",
  9388  			RestartPolicy:            &containerRestartPolicyAlways,
  9389  			ReadinessProbe: &core.Probe{
  9390  				ProbeHandler: core.ProbeHandler{
  9391  					TCPSocket: &core.TCPSocketAction{
  9392  						Port: intstr.FromInt32(80),
  9393  					},
  9394  				},
  9395  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  9396  			},
  9397  		}},
  9398  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}},
  9399  	}, {
  9400  		"invalid liveness probe, successThreshold != 1",
  9401  		line(),
  9402  		[]core.Container{{
  9403  			Name:                     "live-123",
  9404  			Image:                    "image",
  9405  			ImagePullPolicy:          "IfNotPresent",
  9406  			TerminationMessagePolicy: "File",
  9407  			RestartPolicy:            &containerRestartPolicyAlways,
  9408  			LivenessProbe: &core.Probe{
  9409  				ProbeHandler: core.ProbeHandler{
  9410  					TCPSocket: &core.TCPSocketAction{
  9411  						Port: intstr.FromInt32(80),
  9412  					},
  9413  				},
  9414  				SuccessThreshold: 2,
  9415  			},
  9416  		}},
  9417  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}},
  9418  	}, {
  9419  		"invalid lifecycle, no exec command.",
  9420  		line(),
  9421  		[]core.Container{{
  9422  			Name:                     "life-123",
  9423  			Image:                    "image",
  9424  			ImagePullPolicy:          "IfNotPresent",
  9425  			TerminationMessagePolicy: "File",
  9426  			RestartPolicy:            &containerRestartPolicyAlways,
  9427  			Lifecycle: &core.Lifecycle{
  9428  				PreStop: &core.LifecycleHandler{
  9429  					Exec: &core.ExecAction{},
  9430  				},
  9431  			},
  9432  		}},
  9433  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}},
  9434  	}, {
  9435  		"invalid lifecycle, no http path.",
  9436  		line(),
  9437  		[]core.Container{{
  9438  			Name:                     "life-123",
  9439  			Image:                    "image",
  9440  			ImagePullPolicy:          "IfNotPresent",
  9441  			TerminationMessagePolicy: "File",
  9442  			RestartPolicy:            &containerRestartPolicyAlways,
  9443  			Lifecycle: &core.Lifecycle{
  9444  				PreStop: &core.LifecycleHandler{
  9445  					HTTPGet: &core.HTTPGetAction{
  9446  						Port:   intstr.FromInt32(80),
  9447  						Scheme: "HTTP",
  9448  					},
  9449  				},
  9450  			},
  9451  		}},
  9452  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}},
  9453  	}, {
  9454  		"invalid lifecycle, no http port.",
  9455  		line(),
  9456  		[]core.Container{{
  9457  			Name:                     "life-123",
  9458  			Image:                    "image",
  9459  			ImagePullPolicy:          "IfNotPresent",
  9460  			TerminationMessagePolicy: "File",
  9461  			RestartPolicy:            &containerRestartPolicyAlways,
  9462  			Lifecycle: &core.Lifecycle{
  9463  				PreStop: &core.LifecycleHandler{
  9464  					HTTPGet: &core.HTTPGetAction{
  9465  						Path:   "/",
  9466  						Scheme: "HTTP",
  9467  					},
  9468  				},
  9469  			},
  9470  		}},
  9471  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}},
  9472  	}, {
  9473  		"invalid lifecycle, no http scheme.",
  9474  		line(),
  9475  		[]core.Container{{
  9476  			Name:                     "life-123",
  9477  			Image:                    "image",
  9478  			ImagePullPolicy:          "IfNotPresent",
  9479  			TerminationMessagePolicy: "File",
  9480  			RestartPolicy:            &containerRestartPolicyAlways,
  9481  			Lifecycle: &core.Lifecycle{
  9482  				PreStop: &core.LifecycleHandler{
  9483  					HTTPGet: &core.HTTPGetAction{
  9484  						Path: "/",
  9485  						Port: intstr.FromInt32(80),
  9486  					},
  9487  				},
  9488  			},
  9489  		}},
  9490  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}},
  9491  	}, {
  9492  		"invalid lifecycle, no tcp socket port.",
  9493  		line(),
  9494  		[]core.Container{{
  9495  			Name:                     "life-123",
  9496  			Image:                    "image",
  9497  			ImagePullPolicy:          "IfNotPresent",
  9498  			TerminationMessagePolicy: "File",
  9499  			RestartPolicy:            &containerRestartPolicyAlways,
  9500  			Lifecycle: &core.Lifecycle{
  9501  				PreStop: &core.LifecycleHandler{
  9502  					TCPSocket: &core.TCPSocketAction{},
  9503  				},
  9504  			},
  9505  		}},
  9506  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  9507  	}, {
  9508  		"invalid lifecycle, zero tcp socket port.",
  9509  		line(),
  9510  		[]core.Container{{
  9511  			Name:                     "life-123",
  9512  			Image:                    "image",
  9513  			ImagePullPolicy:          "IfNotPresent",
  9514  			TerminationMessagePolicy: "File",
  9515  			RestartPolicy:            &containerRestartPolicyAlways,
  9516  			Lifecycle: &core.Lifecycle{
  9517  				PreStop: &core.LifecycleHandler{
  9518  					TCPSocket: &core.TCPSocketAction{
  9519  						Port: intstr.FromInt32(0),
  9520  					},
  9521  				},
  9522  			},
  9523  		}},
  9524  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  9525  	}, {
  9526  		"invalid lifecycle, no action.",
  9527  		line(),
  9528  		[]core.Container{{
  9529  			Name:                     "life-123",
  9530  			Image:                    "image",
  9531  			ImagePullPolicy:          "IfNotPresent",
  9532  			TerminationMessagePolicy: "File",
  9533  			RestartPolicy:            &containerRestartPolicyAlways,
  9534  			Lifecycle: &core.Lifecycle{
  9535  				PreStop: &core.LifecycleHandler{},
  9536  			},
  9537  		}},
  9538  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
  9539  	},
  9540  	}
  9541  
  9542  	for _, tc := range errorCases {
  9543  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  9544  			errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("initContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  9545  			if len(errs) == 0 {
  9546  				t.Fatal("expected error but received none")
  9547  			}
  9548  
  9549  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "Detail")); diff != "" {
  9550  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  9551  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  9552  			}
  9553  		})
  9554  	}
  9555  }
  9556  
  9557  func TestValidateRestartPolicy(t *testing.T) {
  9558  	successCases := []core.RestartPolicy{
  9559  		core.RestartPolicyAlways,
  9560  		core.RestartPolicyOnFailure,
  9561  		core.RestartPolicyNever,
  9562  	}
  9563  	for _, policy := range successCases {
  9564  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9565  			t.Errorf("expected success: %v", errs)
  9566  		}
  9567  	}
  9568  
  9569  	errorCases := []core.RestartPolicy{"", "newpolicy"}
  9570  
  9571  	for k, policy := range errorCases {
  9572  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9573  			t.Errorf("expected failure for %d", k)
  9574  		}
  9575  	}
  9576  }
  9577  
  9578  func TestValidateDNSPolicy(t *testing.T) {
  9579  	successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSClusterFirstWithHostNet, core.DNSNone}
  9580  	for _, policy := range successCases {
  9581  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9582  			t.Errorf("expected success: %v", errs)
  9583  		}
  9584  	}
  9585  
  9586  	errorCases := []core.DNSPolicy{core.DNSPolicy("invalid"), core.DNSPolicy("")}
  9587  	for _, policy := range errorCases {
  9588  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9589  			t.Errorf("expected failure for %v", policy)
  9590  		}
  9591  	}
  9592  }
  9593  
  9594  func TestValidatePodDNSConfig(t *testing.T) {
  9595  	generateTestSearchPathFunc := func(numChars int) string {
  9596  		res := ""
  9597  		for i := 0; i < numChars; i++ {
  9598  			res = res + "a"
  9599  		}
  9600  		return res
  9601  	}
  9602  	testOptionValue := "2"
  9603  	testDNSNone := core.DNSNone
  9604  	testDNSClusterFirst := core.DNSClusterFirst
  9605  
  9606  	testCases := []struct {
  9607  		desc          string
  9608  		dnsConfig     *core.PodDNSConfig
  9609  		dnsPolicy     *core.DNSPolicy
  9610  		opts          PodValidationOptions
  9611  		expectedError bool
  9612  	}{{
  9613  		desc:          "valid: empty DNSConfig",
  9614  		dnsConfig:     &core.PodDNSConfig{},
  9615  		expectedError: false,
  9616  	}, {
  9617  		desc: "valid: 1 option",
  9618  		dnsConfig: &core.PodDNSConfig{
  9619  			Options: []core.PodDNSConfigOption{
  9620  				{Name: "ndots", Value: &testOptionValue},
  9621  			},
  9622  		},
  9623  		expectedError: false,
  9624  	}, {
  9625  		desc: "valid: 1 nameserver",
  9626  		dnsConfig: &core.PodDNSConfig{
  9627  			Nameservers: []string{"127.0.0.1"},
  9628  		},
  9629  		expectedError: false,
  9630  	}, {
  9631  		desc: "valid: DNSNone with 1 nameserver",
  9632  		dnsConfig: &core.PodDNSConfig{
  9633  			Nameservers: []string{"127.0.0.1"},
  9634  		},
  9635  		dnsPolicy:     &testDNSNone,
  9636  		expectedError: false,
  9637  	}, {
  9638  		desc: "valid: 1 search path",
  9639  		dnsConfig: &core.PodDNSConfig{
  9640  			Searches: []string{"custom"},
  9641  		},
  9642  		expectedError: false,
  9643  	}, {
  9644  		desc: "valid: 1 search path with trailing period",
  9645  		dnsConfig: &core.PodDNSConfig{
  9646  			Searches: []string{"custom."},
  9647  		},
  9648  		expectedError: false,
  9649  	}, {
  9650  		desc: "valid: 3 nameservers and 6 search paths(legacy)",
  9651  		dnsConfig: &core.PodDNSConfig{
  9652  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9653  			Searches:    []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."},
  9654  		},
  9655  		expectedError: false,
  9656  	}, {
  9657  		desc: "valid: 3 nameservers and 32 search paths",
  9658  		dnsConfig: &core.PodDNSConfig{
  9659  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9660  			Searches:    []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"},
  9661  		},
  9662  		expectedError: false,
  9663  	}, {
  9664  		desc: "valid: 256 characters in search path list(legacy)",
  9665  		dnsConfig: &core.PodDNSConfig{
  9666  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9667  			Searches: []string{
  9668  				generateTestSearchPathFunc(1),
  9669  				generateTestSearchPathFunc(50),
  9670  				generateTestSearchPathFunc(50),
  9671  				generateTestSearchPathFunc(50),
  9672  				generateTestSearchPathFunc(50),
  9673  				generateTestSearchPathFunc(50),
  9674  			},
  9675  		},
  9676  		expectedError: false,
  9677  	}, {
  9678  		desc: "valid: 2048 characters in search path list",
  9679  		dnsConfig: &core.PodDNSConfig{
  9680  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9681  			Searches: []string{
  9682  				generateTestSearchPathFunc(64),
  9683  				generateTestSearchPathFunc(63),
  9684  				generateTestSearchPathFunc(63),
  9685  				generateTestSearchPathFunc(63),
  9686  				generateTestSearchPathFunc(63),
  9687  				generateTestSearchPathFunc(63),
  9688  				generateTestSearchPathFunc(63),
  9689  				generateTestSearchPathFunc(63),
  9690  				generateTestSearchPathFunc(63),
  9691  				generateTestSearchPathFunc(63),
  9692  				generateTestSearchPathFunc(63),
  9693  				generateTestSearchPathFunc(63),
  9694  				generateTestSearchPathFunc(63),
  9695  				generateTestSearchPathFunc(63),
  9696  				generateTestSearchPathFunc(63),
  9697  				generateTestSearchPathFunc(63),
  9698  				generateTestSearchPathFunc(63),
  9699  				generateTestSearchPathFunc(63),
  9700  				generateTestSearchPathFunc(63),
  9701  				generateTestSearchPathFunc(63),
  9702  				generateTestSearchPathFunc(63),
  9703  				generateTestSearchPathFunc(63),
  9704  				generateTestSearchPathFunc(63),
  9705  				generateTestSearchPathFunc(63),
  9706  				generateTestSearchPathFunc(63),
  9707  				generateTestSearchPathFunc(63),
  9708  				generateTestSearchPathFunc(63),
  9709  				generateTestSearchPathFunc(63),
  9710  				generateTestSearchPathFunc(63),
  9711  				generateTestSearchPathFunc(63),
  9712  				generateTestSearchPathFunc(63),
  9713  				generateTestSearchPathFunc(63),
  9714  			},
  9715  		},
  9716  		expectedError: false,
  9717  	}, {
  9718  		desc: "valid: ipv6 nameserver",
  9719  		dnsConfig: &core.PodDNSConfig{
  9720  			Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
  9721  		},
  9722  		expectedError: false,
  9723  	}, {
  9724  		desc: "invalid: 4 nameservers",
  9725  		dnsConfig: &core.PodDNSConfig{
  9726  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
  9727  		},
  9728  		expectedError: true,
  9729  	}, {
  9730  		desc: "valid: 7 search paths",
  9731  		dnsConfig: &core.PodDNSConfig{
  9732  			Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
  9733  		},
  9734  	}, {
  9735  		desc: "invalid: 33 search paths",
  9736  		dnsConfig: &core.PodDNSConfig{
  9737  			Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local.", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"},
  9738  		},
  9739  		expectedError: true,
  9740  	}, {
  9741  		desc: "valid: 257 characters in search path list",
  9742  		dnsConfig: &core.PodDNSConfig{
  9743  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9744  			Searches: []string{
  9745  				generateTestSearchPathFunc(2),
  9746  				generateTestSearchPathFunc(50),
  9747  				generateTestSearchPathFunc(50),
  9748  				generateTestSearchPathFunc(50),
  9749  				generateTestSearchPathFunc(50),
  9750  				generateTestSearchPathFunc(50),
  9751  			},
  9752  		},
  9753  	}, {
  9754  		desc: "invalid: 2049 characters in search path list",
  9755  		dnsConfig: &core.PodDNSConfig{
  9756  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9757  			Searches: []string{
  9758  				generateTestSearchPathFunc(65),
  9759  				generateTestSearchPathFunc(63),
  9760  				generateTestSearchPathFunc(63),
  9761  				generateTestSearchPathFunc(63),
  9762  				generateTestSearchPathFunc(63),
  9763  				generateTestSearchPathFunc(63),
  9764  				generateTestSearchPathFunc(63),
  9765  				generateTestSearchPathFunc(63),
  9766  				generateTestSearchPathFunc(63),
  9767  				generateTestSearchPathFunc(63),
  9768  				generateTestSearchPathFunc(63),
  9769  				generateTestSearchPathFunc(63),
  9770  				generateTestSearchPathFunc(63),
  9771  				generateTestSearchPathFunc(63),
  9772  				generateTestSearchPathFunc(63),
  9773  				generateTestSearchPathFunc(63),
  9774  				generateTestSearchPathFunc(63),
  9775  				generateTestSearchPathFunc(63),
  9776  				generateTestSearchPathFunc(63),
  9777  				generateTestSearchPathFunc(63),
  9778  				generateTestSearchPathFunc(63),
  9779  				generateTestSearchPathFunc(63),
  9780  				generateTestSearchPathFunc(63),
  9781  				generateTestSearchPathFunc(63),
  9782  				generateTestSearchPathFunc(63),
  9783  				generateTestSearchPathFunc(63),
  9784  				generateTestSearchPathFunc(63),
  9785  				generateTestSearchPathFunc(63),
  9786  				generateTestSearchPathFunc(63),
  9787  				generateTestSearchPathFunc(63),
  9788  				generateTestSearchPathFunc(63),
  9789  				generateTestSearchPathFunc(63),
  9790  			},
  9791  		},
  9792  		expectedError: true,
  9793  	}, {
  9794  		desc: "invalid search path",
  9795  		dnsConfig: &core.PodDNSConfig{
  9796  			Searches: []string{"custom?"},
  9797  		},
  9798  		expectedError: true,
  9799  	}, {
  9800  		desc: "invalid nameserver",
  9801  		dnsConfig: &core.PodDNSConfig{
  9802  			Nameservers: []string{"invalid"},
  9803  		},
  9804  		expectedError: true,
  9805  	}, {
  9806  		desc: "invalid empty option name",
  9807  		dnsConfig: &core.PodDNSConfig{
  9808  			Options: []core.PodDNSConfigOption{
  9809  				{Value: &testOptionValue},
  9810  			},
  9811  		},
  9812  		expectedError: true,
  9813  	}, {
  9814  		desc: "invalid: DNSNone with 0 nameserver",
  9815  		dnsConfig: &core.PodDNSConfig{
  9816  			Searches: []string{"custom"},
  9817  		},
  9818  		dnsPolicy:     &testDNSNone,
  9819  		expectedError: true,
  9820  	},
  9821  	}
  9822  
  9823  	for _, tc := range testCases {
  9824  		if tc.dnsPolicy == nil {
  9825  			tc.dnsPolicy = &testDNSClusterFirst
  9826  		}
  9827  
  9828  		errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"), tc.opts)
  9829  		if len(errs) != 0 && !tc.expectedError {
  9830  			t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
  9831  		} else if len(errs) == 0 && tc.expectedError {
  9832  			t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
  9833  		}
  9834  	}
  9835  }
  9836  
  9837  func TestValidatePodReadinessGates(t *testing.T) {
  9838  	successCases := []struct {
  9839  		desc           string
  9840  		readinessGates []core.PodReadinessGate
  9841  	}{{
  9842  		"no gate",
  9843  		[]core.PodReadinessGate{},
  9844  	}, {
  9845  		"one readiness gate",
  9846  		[]core.PodReadinessGate{{
  9847  			ConditionType: core.PodConditionType("example.com/condition"),
  9848  		}},
  9849  	}, {
  9850  		"two readiness gates",
  9851  		[]core.PodReadinessGate{{
  9852  			ConditionType: core.PodConditionType("example.com/condition1"),
  9853  		}, {
  9854  			ConditionType: core.PodConditionType("example.com/condition2"),
  9855  		}},
  9856  	},
  9857  	}
  9858  	for _, tc := range successCases {
  9859  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
  9860  			t.Errorf("expect tc %q to success: %v", tc.desc, errs)
  9861  		}
  9862  	}
  9863  
  9864  	errorCases := []struct {
  9865  		desc           string
  9866  		readinessGates []core.PodReadinessGate
  9867  	}{{
  9868  		"invalid condition type",
  9869  		[]core.PodReadinessGate{{
  9870  			ConditionType: core.PodConditionType("invalid/condition/type"),
  9871  		}},
  9872  	},
  9873  	}
  9874  	for _, tc := range errorCases {
  9875  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
  9876  			t.Errorf("expected tc %q to fail", tc.desc)
  9877  		}
  9878  	}
  9879  }
  9880  
  9881  func TestValidatePodConditions(t *testing.T) {
  9882  	successCases := []struct {
  9883  		desc          string
  9884  		podConditions []core.PodCondition
  9885  	}{{
  9886  		"no condition",
  9887  		[]core.PodCondition{},
  9888  	}, {
  9889  		"one system condition",
  9890  		[]core.PodCondition{{
  9891  			Type:   core.PodReady,
  9892  			Status: core.ConditionTrue,
  9893  		}},
  9894  	}, {
  9895  		"one system condition and one custom condition",
  9896  		[]core.PodCondition{{
  9897  			Type:   core.PodReady,
  9898  			Status: core.ConditionTrue,
  9899  		}, {
  9900  			Type:   core.PodConditionType("example.com/condition"),
  9901  			Status: core.ConditionFalse,
  9902  		}},
  9903  	}, {
  9904  		"two custom condition",
  9905  		[]core.PodCondition{{
  9906  			Type:   core.PodConditionType("foobar"),
  9907  			Status: core.ConditionTrue,
  9908  		}, {
  9909  			Type:   core.PodConditionType("example.com/condition"),
  9910  			Status: core.ConditionFalse,
  9911  		}},
  9912  	},
  9913  	}
  9914  
  9915  	for _, tc := range successCases {
  9916  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
  9917  			t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
  9918  		}
  9919  	}
  9920  
  9921  	errorCases := []struct {
  9922  		desc          string
  9923  		podConditions []core.PodCondition
  9924  	}{{
  9925  		"one system condition and a invalid custom condition",
  9926  		[]core.PodCondition{{
  9927  			Type:   core.PodReady,
  9928  			Status: core.ConditionStatus("True"),
  9929  		}, {
  9930  			Type:   core.PodConditionType("invalid/custom/condition"),
  9931  			Status: core.ConditionStatus("True"),
  9932  		}},
  9933  	},
  9934  	}
  9935  	for _, tc := range errorCases {
  9936  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
  9937  			t.Errorf("expected tc %q to fail", tc.desc)
  9938  		}
  9939  	}
  9940  }
  9941  
  9942  func TestValidatePodSpec(t *testing.T) {
  9943  	activeDeadlineSeconds := int64(30)
  9944  	activeDeadlineSecondsMax := int64(math.MaxInt32)
  9945  
  9946  	minUserID := int64(0)
  9947  	maxUserID := int64(2147483647)
  9948  	minGroupID := int64(0)
  9949  	maxGroupID := int64(2147483647)
  9950  	goodfsGroupChangePolicy := core.FSGroupChangeAlways
  9951  	badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
  9952  	badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
  9953  
  9954  	successCases := map[string]core.PodSpec{
  9955  		"populate basic fields, leave defaults for most": {
  9956  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9957  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9958  			RestartPolicy: core.RestartPolicyAlways,
  9959  			DNSPolicy:     core.DNSClusterFirst,
  9960  		},
  9961  		"populate all fields": {
  9962  			Volumes: []core.Volume{
  9963  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9964  			},
  9965  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9966  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9967  			RestartPolicy:  core.RestartPolicyAlways,
  9968  			NodeSelector: map[string]string{
  9969  				"key": "value",
  9970  			},
  9971  			NodeName:              "foobar",
  9972  			DNSPolicy:             core.DNSClusterFirst,
  9973  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
  9974  			ServiceAccountName:    "acct",
  9975  		},
  9976  		"populate all fields with larger active deadline": {
  9977  			Volumes: []core.Volume{
  9978  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9979  			},
  9980  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9981  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9982  			RestartPolicy:  core.RestartPolicyAlways,
  9983  			NodeSelector: map[string]string{
  9984  				"key": "value",
  9985  			},
  9986  			NodeName:              "foobar",
  9987  			DNSPolicy:             core.DNSClusterFirst,
  9988  			ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
  9989  			ServiceAccountName:    "acct",
  9990  		},
  9991  		"populate HostNetwork": {
  9992  			Containers: []core.Container{
  9993  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  9994  					Ports: []core.ContainerPort{
  9995  						{HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
  9996  				},
  9997  			},
  9998  			SecurityContext: &core.PodSecurityContext{
  9999  				HostNetwork: true,
 10000  			},
 10001  			RestartPolicy: core.RestartPolicyAlways,
 10002  			DNSPolicy:     core.DNSClusterFirst,
 10003  		},
 10004  		"populate RunAsUser SupplementalGroups FSGroup with minID 0": {
 10005  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10006  			SecurityContext: &core.PodSecurityContext{
 10007  				SupplementalGroups: []int64{minGroupID},
 10008  				RunAsUser:          &minUserID,
 10009  				FSGroup:            &minGroupID,
 10010  			},
 10011  			RestartPolicy: core.RestartPolicyAlways,
 10012  			DNSPolicy:     core.DNSClusterFirst,
 10013  		},
 10014  		"populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
 10015  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10016  			SecurityContext: &core.PodSecurityContext{
 10017  				SupplementalGroups: []int64{maxGroupID},
 10018  				RunAsUser:          &maxUserID,
 10019  				FSGroup:            &maxGroupID,
 10020  			},
 10021  			RestartPolicy: core.RestartPolicyAlways,
 10022  			DNSPolicy:     core.DNSClusterFirst,
 10023  		},
 10024  		"populate HostIPC": {
 10025  			SecurityContext: &core.PodSecurityContext{
 10026  				HostIPC: true,
 10027  			},
 10028  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10029  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10030  			RestartPolicy: core.RestartPolicyAlways,
 10031  			DNSPolicy:     core.DNSClusterFirst,
 10032  		},
 10033  		"populate HostPID": {
 10034  			SecurityContext: &core.PodSecurityContext{
 10035  				HostPID: true,
 10036  			},
 10037  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10038  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10039  			RestartPolicy: core.RestartPolicyAlways,
 10040  			DNSPolicy:     core.DNSClusterFirst,
 10041  		},
 10042  		"populate Affinity": {
 10043  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10044  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10045  			RestartPolicy: core.RestartPolicyAlways,
 10046  			DNSPolicy:     core.DNSClusterFirst,
 10047  		},
 10048  		"populate HostAliases": {
 10049  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
 10050  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10051  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10052  			RestartPolicy: core.RestartPolicyAlways,
 10053  			DNSPolicy:     core.DNSClusterFirst,
 10054  		},
 10055  		"populate HostAliases with `foo.bar` hostnames": {
 10056  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
 10057  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10058  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10059  			RestartPolicy: core.RestartPolicyAlways,
 10060  			DNSPolicy:     core.DNSClusterFirst,
 10061  		},
 10062  		"populate HostAliases with HostNetwork": {
 10063  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
 10064  			Containers:  []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10065  			SecurityContext: &core.PodSecurityContext{
 10066  				HostNetwork: true,
 10067  			},
 10068  			RestartPolicy: core.RestartPolicyAlways,
 10069  			DNSPolicy:     core.DNSClusterFirst,
 10070  		},
 10071  		"populate PriorityClassName": {
 10072  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10073  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10074  			RestartPolicy:     core.RestartPolicyAlways,
 10075  			DNSPolicy:         core.DNSClusterFirst,
 10076  			PriorityClassName: "valid-name",
 10077  		},
 10078  		"populate ShareProcessNamespace": {
 10079  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10080  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10081  			RestartPolicy: core.RestartPolicyAlways,
 10082  			DNSPolicy:     core.DNSClusterFirst,
 10083  			SecurityContext: &core.PodSecurityContext{
 10084  				ShareProcessNamespace: &[]bool{true}[0],
 10085  			},
 10086  		},
 10087  		"populate RuntimeClassName": {
 10088  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10089  			RestartPolicy:    core.RestartPolicyAlways,
 10090  			DNSPolicy:        core.DNSClusterFirst,
 10091  			RuntimeClassName: utilpointer.String("valid-sandbox"),
 10092  		},
 10093  		"populate Overhead": {
 10094  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10095  			RestartPolicy:    core.RestartPolicyAlways,
 10096  			DNSPolicy:        core.DNSClusterFirst,
 10097  			RuntimeClassName: utilpointer.String("valid-sandbox"),
 10098  			Overhead:         core.ResourceList{},
 10099  		},
 10100  		"populate DNSPolicy": {
 10101  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10102  			SecurityContext: &core.PodSecurityContext{
 10103  				FSGroupChangePolicy: &goodfsGroupChangePolicy,
 10104  			},
 10105  			RestartPolicy: core.RestartPolicyAlways,
 10106  			DNSPolicy:     core.DNSClusterFirst,
 10107  		},
 10108  	}
 10109  	for k, v := range successCases {
 10110  		t.Run(k, func(t *testing.T) {
 10111  			opts := PodValidationOptions{
 10112  				ResourceIsPod: true,
 10113  			}
 10114  			if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 {
 10115  				t.Errorf("expected success: %v", errs)
 10116  			}
 10117  		})
 10118  	}
 10119  
 10120  	activeDeadlineSeconds = int64(0)
 10121  	activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1)
 10122  
 10123  	minUserID = int64(-1)
 10124  	maxUserID = int64(2147483648)
 10125  	minGroupID = int64(-1)
 10126  	maxGroupID = int64(2147483648)
 10127  
 10128  	failureCases := map[string]core.PodSpec{
 10129  		"bad volume": {
 10130  			Volumes:       []core.Volume{{}},
 10131  			RestartPolicy: core.RestartPolicyAlways,
 10132  			DNSPolicy:     core.DNSClusterFirst,
 10133  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10134  		},
 10135  		"no containers": {
 10136  			RestartPolicy: core.RestartPolicyAlways,
 10137  			DNSPolicy:     core.DNSClusterFirst,
 10138  		},
 10139  		"bad container": {
 10140  			Containers:    []core.Container{{}},
 10141  			RestartPolicy: core.RestartPolicyAlways,
 10142  			DNSPolicy:     core.DNSClusterFirst,
 10143  		},
 10144  		"bad init container": {
 10145  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10146  			InitContainers: []core.Container{{}},
 10147  			RestartPolicy:  core.RestartPolicyAlways,
 10148  			DNSPolicy:      core.DNSClusterFirst,
 10149  		},
 10150  		"bad DNS policy": {
 10151  			DNSPolicy:     core.DNSPolicy("invalid"),
 10152  			RestartPolicy: core.RestartPolicyAlways,
 10153  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10154  		},
 10155  		"bad service account name": {
 10156  			Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10157  			RestartPolicy:      core.RestartPolicyAlways,
 10158  			DNSPolicy:          core.DNSClusterFirst,
 10159  			ServiceAccountName: "invalidName",
 10160  		},
 10161  		"bad restart policy": {
 10162  			RestartPolicy: "UnknowPolicy",
 10163  			DNSPolicy:     core.DNSClusterFirst,
 10164  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10165  		},
 10166  		"with hostNetwork hostPort unspecified": {
 10167  			Containers: []core.Container{
 10168  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
 10169  					{HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}},
 10170  				},
 10171  			},
 10172  			SecurityContext: &core.PodSecurityContext{
 10173  				HostNetwork: true,
 10174  			},
 10175  			RestartPolicy: core.RestartPolicyAlways,
 10176  			DNSPolicy:     core.DNSClusterFirst,
 10177  		},
 10178  		"with hostNetwork hostPort not equal to containerPort": {
 10179  			Containers: []core.Container{
 10180  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
 10181  					{HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}},
 10182  				},
 10183  			},
 10184  			SecurityContext: &core.PodSecurityContext{
 10185  				HostNetwork: true,
 10186  			},
 10187  			RestartPolicy: core.RestartPolicyAlways,
 10188  			DNSPolicy:     core.DNSClusterFirst,
 10189  		},
 10190  		"with hostAliases with invalid IP": {
 10191  			SecurityContext: &core.PodSecurityContext{
 10192  				HostNetwork: false,
 10193  			},
 10194  			HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}},
 10195  		},
 10196  		"with hostAliases with invalid hostname": {
 10197  			SecurityContext: &core.PodSecurityContext{
 10198  				HostNetwork: false,
 10199  			},
 10200  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}},
 10201  		},
 10202  		"bad supplementalGroups large than math.MaxInt32": {
 10203  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10204  			SecurityContext: &core.PodSecurityContext{
 10205  				SupplementalGroups: []int64{maxGroupID, 1234},
 10206  			},
 10207  			RestartPolicy: core.RestartPolicyAlways,
 10208  			DNSPolicy:     core.DNSClusterFirst,
 10209  		},
 10210  		"bad supplementalGroups less than 0": {
 10211  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10212  			SecurityContext: &core.PodSecurityContext{
 10213  				SupplementalGroups: []int64{minGroupID, 1234},
 10214  			},
 10215  			RestartPolicy: core.RestartPolicyAlways,
 10216  			DNSPolicy:     core.DNSClusterFirst,
 10217  		},
 10218  		"bad runAsUser large than math.MaxInt32": {
 10219  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10220  			SecurityContext: &core.PodSecurityContext{
 10221  				RunAsUser: &maxUserID,
 10222  			},
 10223  			RestartPolicy: core.RestartPolicyAlways,
 10224  			DNSPolicy:     core.DNSClusterFirst,
 10225  		},
 10226  		"bad runAsUser less than 0": {
 10227  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10228  			SecurityContext: &core.PodSecurityContext{
 10229  				RunAsUser: &minUserID,
 10230  			},
 10231  			RestartPolicy: core.RestartPolicyAlways,
 10232  			DNSPolicy:     core.DNSClusterFirst,
 10233  		},
 10234  		"bad fsGroup large than math.MaxInt32": {
 10235  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10236  			SecurityContext: &core.PodSecurityContext{
 10237  				FSGroup: &maxGroupID,
 10238  			},
 10239  			RestartPolicy: core.RestartPolicyAlways,
 10240  			DNSPolicy:     core.DNSClusterFirst,
 10241  		},
 10242  		"bad fsGroup less than 0": {
 10243  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10244  			SecurityContext: &core.PodSecurityContext{
 10245  				FSGroup: &minGroupID,
 10246  			},
 10247  			RestartPolicy: core.RestartPolicyAlways,
 10248  			DNSPolicy:     core.DNSClusterFirst,
 10249  		},
 10250  		"bad-active-deadline-seconds": {
 10251  			Volumes: []core.Volume{
 10252  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10253  			},
 10254  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10255  			RestartPolicy: core.RestartPolicyAlways,
 10256  			NodeSelector: map[string]string{
 10257  				"key": "value",
 10258  			},
 10259  			NodeName:              "foobar",
 10260  			DNSPolicy:             core.DNSClusterFirst,
 10261  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
 10262  		},
 10263  		"active-deadline-seconds-too-large": {
 10264  			Volumes: []core.Volume{
 10265  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10266  			},
 10267  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10268  			RestartPolicy: core.RestartPolicyAlways,
 10269  			NodeSelector: map[string]string{
 10270  				"key": "value",
 10271  			},
 10272  			NodeName:              "foobar",
 10273  			DNSPolicy:             core.DNSClusterFirst,
 10274  			ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge,
 10275  		},
 10276  		"bad nodeName": {
 10277  			NodeName:      "node name",
 10278  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10279  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10280  			RestartPolicy: core.RestartPolicyAlways,
 10281  			DNSPolicy:     core.DNSClusterFirst,
 10282  		},
 10283  		"bad PriorityClassName": {
 10284  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10285  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10286  			RestartPolicy:     core.RestartPolicyAlways,
 10287  			DNSPolicy:         core.DNSClusterFirst,
 10288  			PriorityClassName: "InvalidName",
 10289  		},
 10290  		"ShareProcessNamespace and HostPID both set": {
 10291  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10292  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10293  			RestartPolicy: core.RestartPolicyAlways,
 10294  			DNSPolicy:     core.DNSClusterFirst,
 10295  			SecurityContext: &core.PodSecurityContext{
 10296  				HostPID:               true,
 10297  				ShareProcessNamespace: &[]bool{true}[0],
 10298  			},
 10299  		},
 10300  		"bad RuntimeClassName": {
 10301  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10302  			RestartPolicy:    core.RestartPolicyAlways,
 10303  			DNSPolicy:        core.DNSClusterFirst,
 10304  			RuntimeClassName: utilpointer.String("invalid/sandbox"),
 10305  		},
 10306  		"bad empty fsGroupchangepolicy": {
 10307  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10308  			SecurityContext: &core.PodSecurityContext{
 10309  				FSGroupChangePolicy: &badfsGroupChangePolicy2,
 10310  			},
 10311  			RestartPolicy: core.RestartPolicyAlways,
 10312  			DNSPolicy:     core.DNSClusterFirst,
 10313  		},
 10314  		"bad invalid fsgroupchangepolicy": {
 10315  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10316  			SecurityContext: &core.PodSecurityContext{
 10317  				FSGroupChangePolicy: &badfsGroupChangePolicy1,
 10318  			},
 10319  			RestartPolicy: core.RestartPolicyAlways,
 10320  			DNSPolicy:     core.DNSClusterFirst,
 10321  		},
 10322  		"disallowed resources resize policy for init containers": {
 10323  			InitContainers: []core.Container{{
 10324  				Name:  "initctr",
 10325  				Image: "initimage",
 10326  				ResizePolicy: []core.ContainerResizePolicy{
 10327  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
 10328  				},
 10329  				ImagePullPolicy:          "IfNotPresent",
 10330  				TerminationMessagePolicy: "File",
 10331  			}},
 10332  			Containers: []core.Container{{
 10333  				Name:  "ctr",
 10334  				Image: "image",
 10335  				ResizePolicy: []core.ContainerResizePolicy{
 10336  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
 10337  				},
 10338  				ImagePullPolicy:          "IfNotPresent",
 10339  				TerminationMessagePolicy: "File",
 10340  			}},
 10341  			RestartPolicy: core.RestartPolicyAlways,
 10342  			DNSPolicy:     core.DNSClusterFirst,
 10343  		},
 10344  	}
 10345  	for k, v := range failureCases {
 10346  		opts := PodValidationOptions{
 10347  			ResourceIsPod: true,
 10348  		}
 10349  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 {
 10350  			t.Errorf("expected failure for %q", k)
 10351  		}
 10352  	}
 10353  }
 10354  
 10355  func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
 10356  	var out core.PodSpec
 10357  	out.Containers = in.Containers
 10358  	out.RestartPolicy = in.RestartPolicy
 10359  	out.DNSPolicy = in.DNSPolicy
 10360  	out.Tolerations = tolerations
 10361  	return out
 10362  }
 10363  
 10364  func TestValidatePod(t *testing.T) {
 10365  	validPodSpec := func(affinity *core.Affinity) core.PodSpec {
 10366  		spec := core.PodSpec{
 10367  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10368  			RestartPolicy: core.RestartPolicyAlways,
 10369  			DNSPolicy:     core.DNSClusterFirst,
 10370  		}
 10371  		if affinity != nil {
 10372  			spec.Affinity = affinity
 10373  		}
 10374  		return spec
 10375  	}
 10376  	validPVCSpec := core.PersistentVolumeClaimSpec{
 10377  		AccessModes: []core.PersistentVolumeAccessMode{
 10378  			core.ReadWriteOnce,
 10379  		},
 10380  		Resources: core.VolumeResourceRequirements{
 10381  			Requests: core.ResourceList{
 10382  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 10383  			},
 10384  		},
 10385  	}
 10386  	validPVCTemplate := core.PersistentVolumeClaimTemplate{
 10387  		Spec: validPVCSpec,
 10388  	}
 10389  	longPodName := strings.Repeat("a", 200)
 10390  	longVolName := strings.Repeat("b", 60)
 10391  
 10392  	successCases := map[string]core.Pod{
 10393  		"basic fields": {
 10394  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10395  			Spec: core.PodSpec{
 10396  				Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10397  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10398  				RestartPolicy: core.RestartPolicyAlways,
 10399  				DNSPolicy:     core.DNSClusterFirst,
 10400  			},
 10401  		},
 10402  		"just about everything": {
 10403  			ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
 10404  			Spec: core.PodSpec{
 10405  				Volumes: []core.Volume{
 10406  					{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10407  				},
 10408  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10409  				RestartPolicy: core.RestartPolicyAlways,
 10410  				DNSPolicy:     core.DNSClusterFirst,
 10411  				NodeSelector: map[string]string{
 10412  					"key": "value",
 10413  				},
 10414  				NodeName: "foobar",
 10415  			},
 10416  		},
 10417  		"serialized node affinity requirements": {
 10418  			ObjectMeta: metav1.ObjectMeta{
 10419  				Name:      "123",
 10420  				Namespace: "ns",
 10421  			},
 10422  			Spec: validPodSpec(
 10423  				// TODO: Uncomment and move this block and move inside NodeAffinity once
 10424  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10425  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
 10426  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
 10427  				//				{
 10428  				//					MatchExpressions: []core.NodeSelectorRequirement{
 10429  				//						{
 10430  				//							Key: "key1",
 10431  				//							Operator: core.NodeSelectorOpExists
 10432  				//						},
 10433  				//					},
 10434  				//				},
 10435  				//			},
 10436  				//		},
 10437  				&core.Affinity{
 10438  					NodeAffinity: &core.NodeAffinity{
 10439  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10440  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10441  								MatchExpressions: []core.NodeSelectorRequirement{{
 10442  									Key:      "key2",
 10443  									Operator: core.NodeSelectorOpIn,
 10444  									Values:   []string{"value1", "value2"},
 10445  								}},
 10446  								MatchFields: []core.NodeSelectorRequirement{{
 10447  									Key:      "metadata.name",
 10448  									Operator: core.NodeSelectorOpIn,
 10449  									Values:   []string{"host1"},
 10450  								}},
 10451  							}},
 10452  						},
 10453  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 10454  							Weight: 10,
 10455  							Preference: core.NodeSelectorTerm{
 10456  								MatchExpressions: []core.NodeSelectorRequirement{{
 10457  									Key:      "foo",
 10458  									Operator: core.NodeSelectorOpIn,
 10459  									Values:   []string{"bar"},
 10460  								}},
 10461  							},
 10462  						}},
 10463  					},
 10464  				},
 10465  			),
 10466  		},
 10467  		"serialized node affinity requirements, II": {
 10468  			ObjectMeta: metav1.ObjectMeta{
 10469  				Name:      "123",
 10470  				Namespace: "ns",
 10471  			},
 10472  			Spec: validPodSpec(
 10473  				// TODO: Uncomment and move this block and move inside NodeAffinity once
 10474  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10475  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
 10476  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
 10477  				//				{
 10478  				//					MatchExpressions: []core.NodeSelectorRequirement{
 10479  				//						{
 10480  				//							Key: "key1",
 10481  				//							Operator: core.NodeSelectorOpExists
 10482  				//						},
 10483  				//					},
 10484  				//				},
 10485  				//			},
 10486  				//		},
 10487  				&core.Affinity{
 10488  					NodeAffinity: &core.NodeAffinity{
 10489  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10490  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10491  								MatchExpressions: []core.NodeSelectorRequirement{},
 10492  							}},
 10493  						},
 10494  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 10495  							Weight: 10,
 10496  							Preference: core.NodeSelectorTerm{
 10497  								MatchExpressions: []core.NodeSelectorRequirement{},
 10498  							},
 10499  						}},
 10500  					},
 10501  				},
 10502  			),
 10503  		},
 10504  		"serialized pod affinity in affinity requirements in annotations": {
 10505  			ObjectMeta: metav1.ObjectMeta{
 10506  				Name:      "123",
 10507  				Namespace: "ns",
 10508  				// TODO: Uncomment and move this block into Annotations map once
 10509  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10510  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
 10511  				//			"labelSelector": {
 10512  				//				"matchExpressions": [{
 10513  				//					"key": "key2",
 10514  				//					"operator": "In",
 10515  				//					"values": ["value1", "value2"]
 10516  				//				}]
 10517  				//			},
 10518  				//			"namespaces":["ns"],
 10519  				//			"topologyKey": "zone"
 10520  				//		}]
 10521  			},
 10522  			Spec: validPodSpec(&core.Affinity{
 10523  				PodAffinity: &core.PodAffinity{
 10524  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10525  						LabelSelector: &metav1.LabelSelector{
 10526  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10527  								Key:      "key2",
 10528  								Operator: metav1.LabelSelectorOpIn,
 10529  								Values:   []string{"value1", "value2"},
 10530  							}},
 10531  						},
 10532  						TopologyKey: "zone",
 10533  						Namespaces:  []string{"ns"},
 10534  						NamespaceSelector: &metav1.LabelSelector{
 10535  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10536  								Key:      "key",
 10537  								Operator: metav1.LabelSelectorOpIn,
 10538  								Values:   []string{"value1", "value2"},
 10539  							}},
 10540  						},
 10541  					}},
 10542  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10543  						Weight: 10,
 10544  						PodAffinityTerm: core.PodAffinityTerm{
 10545  							LabelSelector: &metav1.LabelSelector{
 10546  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10547  									Key:      "key2",
 10548  									Operator: metav1.LabelSelectorOpNotIn,
 10549  									Values:   []string{"value1", "value2"},
 10550  								}},
 10551  							},
 10552  							Namespaces:  []string{"ns"},
 10553  							TopologyKey: "region",
 10554  						},
 10555  					}},
 10556  				},
 10557  			}),
 10558  		},
 10559  		"serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
 10560  			ObjectMeta: metav1.ObjectMeta{
 10561  				Name:      "123",
 10562  				Namespace: "ns",
 10563  				// TODO: Uncomment and move this block into Annotations map once
 10564  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10565  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
 10566  				//			"labelSelector": {
 10567  				//				"matchExpressions": [{
 10568  				//					"key": "key2",
 10569  				//					"operator": "In",
 10570  				//					"values": ["value1", "value2"]
 10571  				//				}]
 10572  				//			},
 10573  				//			"namespaces":["ns"],
 10574  				//			"topologyKey": "zone"
 10575  				//		}]
 10576  			},
 10577  			Spec: validPodSpec(&core.Affinity{
 10578  				PodAntiAffinity: &core.PodAntiAffinity{
 10579  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10580  						LabelSelector: &metav1.LabelSelector{
 10581  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10582  								Key:      "key2",
 10583  								Operator: metav1.LabelSelectorOpExists,
 10584  							}},
 10585  						},
 10586  						TopologyKey: "zone",
 10587  						Namespaces:  []string{"ns"},
 10588  					}},
 10589  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10590  						Weight: 10,
 10591  						PodAffinityTerm: core.PodAffinityTerm{
 10592  							LabelSelector: &metav1.LabelSelector{
 10593  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10594  									Key:      "key2",
 10595  									Operator: metav1.LabelSelectorOpDoesNotExist,
 10596  								}},
 10597  							},
 10598  							Namespaces:  []string{"ns"},
 10599  							TopologyKey: "region",
 10600  						},
 10601  					}},
 10602  				},
 10603  			}),
 10604  		},
 10605  		"populate forgiveness tolerations with exists operator in annotations.": {
 10606  			ObjectMeta: metav1.ObjectMeta{
 10607  				Name:      "123",
 10608  				Namespace: "ns",
 10609  			},
 10610  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10611  		},
 10612  		"populate forgiveness tolerations with equal operator in annotations.": {
 10613  			ObjectMeta: metav1.ObjectMeta{
 10614  				Name:      "123",
 10615  				Namespace: "ns",
 10616  			},
 10617  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10618  		},
 10619  		"populate tolerations equal operator in annotations.": {
 10620  			ObjectMeta: metav1.ObjectMeta{
 10621  				Name:      "123",
 10622  				Namespace: "ns",
 10623  			},
 10624  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 10625  		},
 10626  		"populate tolerations exists operator in annotations.": {
 10627  			ObjectMeta: metav1.ObjectMeta{
 10628  				Name:      "123",
 10629  				Namespace: "ns",
 10630  			},
 10631  			Spec: validPodSpec(nil),
 10632  		},
 10633  		"empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
 10634  			ObjectMeta: metav1.ObjectMeta{
 10635  				Name:      "123",
 10636  				Namespace: "ns",
 10637  			},
 10638  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
 10639  		},
 10640  		"empty operator is OK for toleration, defaults to Equal.": {
 10641  			ObjectMeta: metav1.ObjectMeta{
 10642  				Name:      "123",
 10643  				Namespace: "ns",
 10644  			},
 10645  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
 10646  		},
 10647  		"empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
 10648  			ObjectMeta: metav1.ObjectMeta{
 10649  				Name:      "123",
 10650  				Namespace: "ns",
 10651  			},
 10652  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
 10653  		},
 10654  		"negative tolerationSeconds is OK for toleration.": {
 10655  			ObjectMeta: metav1.ObjectMeta{
 10656  				Name:      "pod-forgiveness-invalid",
 10657  				Namespace: "ns",
 10658  			},
 10659  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
 10660  		},
 10661  		"runtime default seccomp profile": {
 10662  			ObjectMeta: metav1.ObjectMeta{
 10663  				Name:      "123",
 10664  				Namespace: "ns",
 10665  				Annotations: map[string]string{
 10666  					core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault,
 10667  				},
 10668  			},
 10669  			Spec: validPodSpec(nil),
 10670  		},
 10671  		"docker default seccomp profile": {
 10672  			ObjectMeta: metav1.ObjectMeta{
 10673  				Name:      "123",
 10674  				Namespace: "ns",
 10675  				Annotations: map[string]string{
 10676  					core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault,
 10677  				},
 10678  			},
 10679  			Spec: validPodSpec(nil),
 10680  		},
 10681  		"unconfined seccomp profile": {
 10682  			ObjectMeta: metav1.ObjectMeta{
 10683  				Name:      "123",
 10684  				Namespace: "ns",
 10685  				Annotations: map[string]string{
 10686  					core.SeccompPodAnnotationKey: "unconfined",
 10687  				},
 10688  			},
 10689  			Spec: validPodSpec(nil),
 10690  		},
 10691  		"localhost seccomp profile": {
 10692  			ObjectMeta: metav1.ObjectMeta{
 10693  				Name:      "123",
 10694  				Namespace: "ns",
 10695  				Annotations: map[string]string{
 10696  					core.SeccompPodAnnotationKey: "localhost/foo",
 10697  				},
 10698  			},
 10699  			Spec: validPodSpec(nil),
 10700  		},
 10701  		"localhost seccomp profile for a container": {
 10702  			ObjectMeta: metav1.ObjectMeta{
 10703  				Name:      "123",
 10704  				Namespace: "ns",
 10705  				Annotations: map[string]string{
 10706  					core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo",
 10707  				},
 10708  			},
 10709  			Spec: validPodSpec(nil),
 10710  		},
 10711  		"runtime default seccomp profile for a pod": {
 10712  			ObjectMeta: metav1.ObjectMeta{
 10713  				Name:      "123",
 10714  				Namespace: "ns",
 10715  			},
 10716  			Spec: core.PodSpec{
 10717  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10718  				RestartPolicy: core.RestartPolicyAlways,
 10719  				DNSPolicy:     core.DNSDefault,
 10720  				SecurityContext: &core.PodSecurityContext{
 10721  					SeccompProfile: &core.SeccompProfile{
 10722  						Type: core.SeccompProfileTypeRuntimeDefault,
 10723  					},
 10724  				},
 10725  			},
 10726  		},
 10727  		"runtime default seccomp profile for a container": {
 10728  			ObjectMeta: metav1.ObjectMeta{
 10729  				Name:      "123",
 10730  				Namespace: "ns",
 10731  			},
 10732  			Spec: core.PodSpec{
 10733  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10734  					SecurityContext: &core.SecurityContext{
 10735  						SeccompProfile: &core.SeccompProfile{
 10736  							Type: core.SeccompProfileTypeRuntimeDefault,
 10737  						},
 10738  					},
 10739  				}},
 10740  				RestartPolicy: core.RestartPolicyAlways,
 10741  				DNSPolicy:     core.DNSDefault,
 10742  			},
 10743  		},
 10744  		"unconfined seccomp profile for a pod": {
 10745  			ObjectMeta: metav1.ObjectMeta{
 10746  				Name:      "123",
 10747  				Namespace: "ns",
 10748  			},
 10749  			Spec: core.PodSpec{
 10750  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10751  				RestartPolicy: core.RestartPolicyAlways,
 10752  				DNSPolicy:     core.DNSDefault,
 10753  				SecurityContext: &core.PodSecurityContext{
 10754  					SeccompProfile: &core.SeccompProfile{
 10755  						Type: core.SeccompProfileTypeUnconfined,
 10756  					},
 10757  				},
 10758  			},
 10759  		},
 10760  		"unconfined seccomp profile for a container": {
 10761  			ObjectMeta: metav1.ObjectMeta{
 10762  				Name:      "123",
 10763  				Namespace: "ns",
 10764  			},
 10765  			Spec: core.PodSpec{
 10766  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10767  					SecurityContext: &core.SecurityContext{
 10768  						SeccompProfile: &core.SeccompProfile{
 10769  							Type: core.SeccompProfileTypeUnconfined,
 10770  						},
 10771  					},
 10772  				}},
 10773  				RestartPolicy: core.RestartPolicyAlways,
 10774  				DNSPolicy:     core.DNSDefault,
 10775  			},
 10776  		},
 10777  		"localhost seccomp profile for a pod": {
 10778  			ObjectMeta: metav1.ObjectMeta{
 10779  				Name:      "123",
 10780  				Namespace: "ns",
 10781  			},
 10782  			Spec: core.PodSpec{
 10783  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10784  				RestartPolicy: core.RestartPolicyAlways,
 10785  				DNSPolicy:     core.DNSDefault,
 10786  				SecurityContext: &core.PodSecurityContext{
 10787  					SeccompProfile: &core.SeccompProfile{
 10788  						Type:             core.SeccompProfileTypeLocalhost,
 10789  						LocalhostProfile: utilpointer.String("filename.json"),
 10790  					},
 10791  				},
 10792  			},
 10793  		},
 10794  		"localhost seccomp profile for a container, II": {
 10795  			ObjectMeta: metav1.ObjectMeta{
 10796  				Name:      "123",
 10797  				Namespace: "ns",
 10798  			},
 10799  			Spec: core.PodSpec{
 10800  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10801  					SecurityContext: &core.SecurityContext{
 10802  						SeccompProfile: &core.SeccompProfile{
 10803  							Type:             core.SeccompProfileTypeLocalhost,
 10804  							LocalhostProfile: utilpointer.String("filename.json"),
 10805  						},
 10806  					},
 10807  				}},
 10808  				RestartPolicy: core.RestartPolicyAlways,
 10809  				DNSPolicy:     core.DNSDefault,
 10810  			},
 10811  		},
 10812  		"default AppArmor annotation for a container": {
 10813  			ObjectMeta: metav1.ObjectMeta{
 10814  				Name:      "123",
 10815  				Namespace: "ns",
 10816  				Annotations: map[string]string{
 10817  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 10818  				},
 10819  			},
 10820  			Spec: validPodSpec(nil),
 10821  		},
 10822  		"default AppArmor annotation for an init container": {
 10823  			ObjectMeta: metav1.ObjectMeta{
 10824  				Name:      "123",
 10825  				Namespace: "ns",
 10826  				Annotations: map[string]string{
 10827  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 10828  				},
 10829  			},
 10830  			Spec: core.PodSpec{
 10831  				InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10832  				Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10833  				RestartPolicy:  core.RestartPolicyAlways,
 10834  				DNSPolicy:      core.DNSClusterFirst,
 10835  			},
 10836  		},
 10837  		"localhost AppArmor annotation for a container": {
 10838  			ObjectMeta: metav1.ObjectMeta{
 10839  				Name:      "123",
 10840  				Namespace: "ns",
 10841  				Annotations: map[string]string{
 10842  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo",
 10843  				},
 10844  			},
 10845  			Spec: validPodSpec(nil),
 10846  		},
 10847  		"runtime default AppArmor profile for a pod": {
 10848  			ObjectMeta: metav1.ObjectMeta{
 10849  				Name:      "123",
 10850  				Namespace: "ns",
 10851  			},
 10852  			Spec: core.PodSpec{
 10853  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10854  				RestartPolicy: core.RestartPolicyAlways,
 10855  				DNSPolicy:     core.DNSDefault,
 10856  				SecurityContext: &core.PodSecurityContext{
 10857  					AppArmorProfile: &core.AppArmorProfile{
 10858  						Type: core.AppArmorProfileTypeRuntimeDefault,
 10859  					},
 10860  				},
 10861  			},
 10862  		},
 10863  		"runtime default AppArmor profile for a container": {
 10864  			ObjectMeta: metav1.ObjectMeta{
 10865  				Name:      "123",
 10866  				Namespace: "ns",
 10867  			},
 10868  			Spec: core.PodSpec{
 10869  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10870  					SecurityContext: &core.SecurityContext{
 10871  						AppArmorProfile: &core.AppArmorProfile{
 10872  							Type: core.AppArmorProfileTypeRuntimeDefault,
 10873  						},
 10874  					},
 10875  				}},
 10876  				RestartPolicy: core.RestartPolicyAlways,
 10877  				DNSPolicy:     core.DNSDefault,
 10878  			},
 10879  		},
 10880  		"unconfined AppArmor profile for a pod": {
 10881  			ObjectMeta: metav1.ObjectMeta{
 10882  				Name:      "123",
 10883  				Namespace: "ns",
 10884  			},
 10885  			Spec: core.PodSpec{
 10886  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10887  				RestartPolicy: core.RestartPolicyAlways,
 10888  				DNSPolicy:     core.DNSDefault,
 10889  				SecurityContext: &core.PodSecurityContext{
 10890  					AppArmorProfile: &core.AppArmorProfile{
 10891  						Type: core.AppArmorProfileTypeUnconfined,
 10892  					},
 10893  				},
 10894  			},
 10895  		},
 10896  		"unconfined AppArmor profile for a container": {
 10897  			ObjectMeta: metav1.ObjectMeta{
 10898  				Name:      "123",
 10899  				Namespace: "ns",
 10900  			},
 10901  			Spec: core.PodSpec{
 10902  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10903  					SecurityContext: &core.SecurityContext{
 10904  						AppArmorProfile: &core.AppArmorProfile{
 10905  							Type: core.AppArmorProfileTypeUnconfined,
 10906  						},
 10907  					},
 10908  				}},
 10909  				RestartPolicy: core.RestartPolicyAlways,
 10910  				DNSPolicy:     core.DNSDefault,
 10911  			},
 10912  		},
 10913  		"localhost AppArmor profile for a pod": {
 10914  			ObjectMeta: metav1.ObjectMeta{
 10915  				Name:      "123",
 10916  				Namespace: "ns",
 10917  			},
 10918  			Spec: core.PodSpec{
 10919  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10920  				RestartPolicy: core.RestartPolicyAlways,
 10921  				DNSPolicy:     core.DNSDefault,
 10922  				SecurityContext: &core.PodSecurityContext{
 10923  					AppArmorProfile: &core.AppArmorProfile{
 10924  						Type:             core.AppArmorProfileTypeLocalhost,
 10925  						LocalhostProfile: ptr.To("example-org/application-foo"),
 10926  					},
 10927  				},
 10928  			},
 10929  		},
 10930  		"localhost AppArmor profile for a container field": {
 10931  			ObjectMeta: metav1.ObjectMeta{
 10932  				Name:      "123",
 10933  				Namespace: "ns",
 10934  			},
 10935  			Spec: core.PodSpec{
 10936  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10937  					SecurityContext: &core.SecurityContext{
 10938  						AppArmorProfile: &core.AppArmorProfile{
 10939  							Type:             core.AppArmorProfileTypeLocalhost,
 10940  							LocalhostProfile: ptr.To("example-org/application-foo"),
 10941  						},
 10942  					},
 10943  				}},
 10944  				RestartPolicy: core.RestartPolicyAlways,
 10945  				DNSPolicy:     core.DNSDefault,
 10946  			},
 10947  		},
 10948  		"matching AppArmor fields and annotations": {
 10949  			ObjectMeta: metav1.ObjectMeta{
 10950  				Name:      "123",
 10951  				Namespace: "ns",
 10952  				Annotations: map[string]string{
 10953  					core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 10954  				},
 10955  			},
 10956  			Spec: core.PodSpec{
 10957  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10958  					SecurityContext: &core.SecurityContext{
 10959  						AppArmorProfile: &core.AppArmorProfile{
 10960  							Type:             core.AppArmorProfileTypeLocalhost,
 10961  							LocalhostProfile: ptr.To("foo"),
 10962  						},
 10963  					},
 10964  				}},
 10965  				RestartPolicy: core.RestartPolicyAlways,
 10966  				DNSPolicy:     core.DNSDefault,
 10967  			},
 10968  		},
 10969  		"matching AppArmor pod field and annotations": {
 10970  			ObjectMeta: metav1.ObjectMeta{
 10971  				Name:      "123",
 10972  				Namespace: "ns",
 10973  				Annotations: map[string]string{
 10974  					core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 10975  				},
 10976  			},
 10977  			Spec: core.PodSpec{
 10978  				SecurityContext: &core.PodSecurityContext{
 10979  					AppArmorProfile: &core.AppArmorProfile{
 10980  						Type:             core.AppArmorProfileTypeLocalhost,
 10981  						LocalhostProfile: ptr.To("foo"),
 10982  					},
 10983  				},
 10984  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10985  				RestartPolicy: core.RestartPolicyAlways,
 10986  				DNSPolicy:     core.DNSDefault,
 10987  			},
 10988  		},
 10989  		"syntactically valid sysctls": {
 10990  			ObjectMeta: metav1.ObjectMeta{
 10991  				Name:      "123",
 10992  				Namespace: "ns",
 10993  			},
 10994  			Spec: core.PodSpec{
 10995  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10996  				RestartPolicy: core.RestartPolicyAlways,
 10997  				DNSPolicy:     core.DNSClusterFirst,
 10998  				SecurityContext: &core.PodSecurityContext{
 10999  					Sysctls: []core.Sysctl{{
 11000  						Name:  "kernel.shmmni",
 11001  						Value: "32768",
 11002  					}, {
 11003  						Name:  "kernel.shmmax",
 11004  						Value: "1000000000",
 11005  					}, {
 11006  						Name:  "knet.ipv4.route.min_pmtu",
 11007  						Value: "1000",
 11008  					}},
 11009  				},
 11010  			},
 11011  		},
 11012  		"valid extended resources for init container": {
 11013  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11014  			Spec: core.PodSpec{
 11015  				InitContainers: []core.Container{{
 11016  					Name:            "valid-extended",
 11017  					Image:           "image",
 11018  					ImagePullPolicy: "IfNotPresent",
 11019  					Resources: core.ResourceRequirements{
 11020  						Requests: core.ResourceList{
 11021  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11022  						},
 11023  						Limits: core.ResourceList{
 11024  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11025  						},
 11026  					},
 11027  					TerminationMessagePolicy: "File",
 11028  				}},
 11029  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11030  				RestartPolicy: core.RestartPolicyAlways,
 11031  				DNSPolicy:     core.DNSClusterFirst,
 11032  			},
 11033  		},
 11034  		"valid extended resources for regular container": {
 11035  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11036  			Spec: core.PodSpec{
 11037  				InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11038  				Containers: []core.Container{{
 11039  					Name:            "valid-extended",
 11040  					Image:           "image",
 11041  					ImagePullPolicy: "IfNotPresent",
 11042  					Resources: core.ResourceRequirements{
 11043  						Requests: core.ResourceList{
 11044  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11045  						},
 11046  						Limits: core.ResourceList{
 11047  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11048  						},
 11049  					},
 11050  					TerminationMessagePolicy: "File",
 11051  				}},
 11052  				RestartPolicy: core.RestartPolicyAlways,
 11053  				DNSPolicy:     core.DNSClusterFirst,
 11054  			},
 11055  		},
 11056  		"valid serviceaccount token projected volume with serviceaccount name specified": {
 11057  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11058  			Spec: core.PodSpec{
 11059  				ServiceAccountName: "some-service-account",
 11060  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11061  				RestartPolicy:      core.RestartPolicyAlways,
 11062  				DNSPolicy:          core.DNSClusterFirst,
 11063  				Volumes: []core.Volume{{
 11064  					Name: "projected-volume",
 11065  					VolumeSource: core.VolumeSource{
 11066  						Projected: &core.ProjectedVolumeSource{
 11067  							Sources: []core.VolumeProjection{{
 11068  								ServiceAccountToken: &core.ServiceAccountTokenProjection{
 11069  									Audience:          "foo-audience",
 11070  									ExpirationSeconds: 6000,
 11071  									Path:              "foo-path",
 11072  								},
 11073  							}},
 11074  						},
 11075  					},
 11076  				}},
 11077  			},
 11078  		},
 11079  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by name": {
 11080  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11081  			Spec: core.PodSpec{
 11082  				ServiceAccountName: "some-service-account",
 11083  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11084  				RestartPolicy:      core.RestartPolicyAlways,
 11085  				DNSPolicy:          core.DNSClusterFirst,
 11086  				Volumes: []core.Volume{
 11087  					{
 11088  						Name: "projected-volume",
 11089  						VolumeSource: core.VolumeSource{
 11090  							Projected: &core.ProjectedVolumeSource{
 11091  								Sources: []core.VolumeProjection{
 11092  									{
 11093  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 11094  											Path: "foo-path",
 11095  											Name: utilpointer.String("foo"),
 11096  										},
 11097  									},
 11098  								},
 11099  							},
 11100  						},
 11101  					},
 11102  				},
 11103  			},
 11104  		},
 11105  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by signer name": {
 11106  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11107  			Spec: core.PodSpec{
 11108  				ServiceAccountName: "some-service-account",
 11109  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11110  				RestartPolicy:      core.RestartPolicyAlways,
 11111  				DNSPolicy:          core.DNSClusterFirst,
 11112  				Volumes: []core.Volume{
 11113  					{
 11114  						Name: "projected-volume",
 11115  						VolumeSource: core.VolumeSource{
 11116  							Projected: &core.ProjectedVolumeSource{
 11117  								Sources: []core.VolumeProjection{
 11118  									{
 11119  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 11120  											Path:       "foo-path",
 11121  											SignerName: utilpointer.String("example.com/foo"),
 11122  											LabelSelector: &metav1.LabelSelector{
 11123  												MatchLabels: map[string]string{
 11124  													"version": "live",
 11125  												},
 11126  											},
 11127  										},
 11128  									},
 11129  								},
 11130  							},
 11131  						},
 11132  					},
 11133  				},
 11134  			},
 11135  		},
 11136  		"ephemeral volume + PVC, no conflict between them": {
 11137  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11138  			Spec: core.PodSpec{
 11139  				Volumes: []core.Volume{
 11140  					{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 11141  					{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 11142  				},
 11143  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11144  				RestartPolicy: core.RestartPolicyAlways,
 11145  				DNSPolicy:     core.DNSClusterFirst,
 11146  			},
 11147  		},
 11148  		"negative pod-deletion-cost": {
 11149  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "-100"}},
 11150  			Spec: core.PodSpec{
 11151  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11152  				RestartPolicy: core.RestartPolicyAlways,
 11153  				DNSPolicy:     core.DNSClusterFirst,
 11154  			},
 11155  		},
 11156  		"positive pod-deletion-cost": {
 11157  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "100"}},
 11158  			Spec: core.PodSpec{
 11159  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11160  				RestartPolicy: core.RestartPolicyAlways,
 11161  				DNSPolicy:     core.DNSClusterFirst,
 11162  			},
 11163  		},
 11164  		"MatchLabelKeys/MismatchLabelKeys in required PodAffinity": {
 11165  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11166  			Spec: core.PodSpec{
 11167  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11168  				RestartPolicy: core.RestartPolicyAlways,
 11169  				DNSPolicy:     core.DNSClusterFirst,
 11170  				Affinity: &core.Affinity{
 11171  					PodAffinity: &core.PodAffinity{
 11172  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11173  							{
 11174  								LabelSelector: &metav1.LabelSelector{
 11175  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11176  										{
 11177  											Key:      "key",
 11178  											Operator: metav1.LabelSelectorOpNotIn,
 11179  											Values:   []string{"value1", "value2"},
 11180  										},
 11181  										{
 11182  											Key:      "key2",
 11183  											Operator: metav1.LabelSelectorOpIn,
 11184  											Values:   []string{"value1"},
 11185  										},
 11186  										{
 11187  											Key:      "key3",
 11188  											Operator: metav1.LabelSelectorOpNotIn,
 11189  											Values:   []string{"value1"},
 11190  										},
 11191  									},
 11192  								},
 11193  								TopologyKey:       "k8s.io/zone",
 11194  								MatchLabelKeys:    []string{"key2"},
 11195  								MismatchLabelKeys: []string{"key3"},
 11196  							},
 11197  						},
 11198  					},
 11199  				},
 11200  			},
 11201  		},
 11202  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": {
 11203  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11204  			Spec: core.PodSpec{
 11205  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11206  				RestartPolicy: core.RestartPolicyAlways,
 11207  				DNSPolicy:     core.DNSClusterFirst,
 11208  				Affinity: &core.Affinity{
 11209  					PodAffinity: &core.PodAffinity{
 11210  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11211  							{
 11212  								Weight: 10,
 11213  								PodAffinityTerm: core.PodAffinityTerm{
 11214  									LabelSelector: &metav1.LabelSelector{
 11215  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11216  											{
 11217  												Key:      "key",
 11218  												Operator: metav1.LabelSelectorOpNotIn,
 11219  												Values:   []string{"value1", "value2"},
 11220  											},
 11221  											{
 11222  												Key:      "key2",
 11223  												Operator: metav1.LabelSelectorOpIn,
 11224  												Values:   []string{"value1"},
 11225  											},
 11226  											{
 11227  												Key:      "key3",
 11228  												Operator: metav1.LabelSelectorOpNotIn,
 11229  												Values:   []string{"value1"},
 11230  											},
 11231  										},
 11232  									},
 11233  									TopologyKey:       "k8s.io/zone",
 11234  									MatchLabelKeys:    []string{"key2"},
 11235  									MismatchLabelKeys: []string{"key3"},
 11236  								},
 11237  							},
 11238  						},
 11239  					},
 11240  				},
 11241  			},
 11242  		},
 11243  		"MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": {
 11244  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11245  			Spec: core.PodSpec{
 11246  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11247  				RestartPolicy: core.RestartPolicyAlways,
 11248  				DNSPolicy:     core.DNSClusterFirst,
 11249  				Affinity: &core.Affinity{
 11250  					PodAntiAffinity: &core.PodAntiAffinity{
 11251  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11252  							{
 11253  								LabelSelector: &metav1.LabelSelector{
 11254  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11255  										{
 11256  											Key:      "key",
 11257  											Operator: metav1.LabelSelectorOpNotIn,
 11258  											Values:   []string{"value1", "value2"},
 11259  										},
 11260  										{
 11261  											Key:      "key2",
 11262  											Operator: metav1.LabelSelectorOpIn,
 11263  											Values:   []string{"value1"},
 11264  										},
 11265  										{
 11266  											Key:      "key3",
 11267  											Operator: metav1.LabelSelectorOpNotIn,
 11268  											Values:   []string{"value1"},
 11269  										},
 11270  									},
 11271  								},
 11272  								TopologyKey:       "k8s.io/zone",
 11273  								MatchLabelKeys:    []string{"key2"},
 11274  								MismatchLabelKeys: []string{"key3"},
 11275  							},
 11276  						},
 11277  					},
 11278  				},
 11279  			},
 11280  		},
 11281  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": {
 11282  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11283  			Spec: core.PodSpec{
 11284  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11285  				RestartPolicy: core.RestartPolicyAlways,
 11286  				DNSPolicy:     core.DNSClusterFirst,
 11287  				Affinity: &core.Affinity{
 11288  					PodAntiAffinity: &core.PodAntiAffinity{
 11289  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11290  							{
 11291  								Weight: 10,
 11292  								PodAffinityTerm: core.PodAffinityTerm{
 11293  									LabelSelector: &metav1.LabelSelector{
 11294  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11295  											{
 11296  												Key:      "key",
 11297  												Operator: metav1.LabelSelectorOpNotIn,
 11298  												Values:   []string{"value1", "value2"},
 11299  											},
 11300  											{
 11301  												Key:      "key2",
 11302  												Operator: metav1.LabelSelectorOpIn,
 11303  												Values:   []string{"value1"},
 11304  											},
 11305  											{
 11306  												Key:      "key3",
 11307  												Operator: metav1.LabelSelectorOpNotIn,
 11308  												Values:   []string{"value1"},
 11309  											},
 11310  										},
 11311  									},
 11312  									TopologyKey:       "k8s.io/zone",
 11313  									MatchLabelKeys:    []string{"key2"},
 11314  									MismatchLabelKeys: []string{"key3"},
 11315  								},
 11316  							},
 11317  						},
 11318  					},
 11319  				},
 11320  			},
 11321  		},
 11322  		"LabelSelector can have the same key as MismatchLabelKeys": {
 11323  			// Note: On the contrary, in case of matchLabelKeys, keys in matchLabelKeys are not allowed to be specified in labelSelector by users.
 11324  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11325  			Spec: core.PodSpec{
 11326  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11327  				RestartPolicy: core.RestartPolicyAlways,
 11328  				DNSPolicy:     core.DNSClusterFirst,
 11329  				Affinity: &core.Affinity{
 11330  					PodAffinity: &core.PodAffinity{
 11331  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11332  							{
 11333  								LabelSelector: &metav1.LabelSelector{
 11334  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11335  										{
 11336  											Key:      "key",
 11337  											Operator: metav1.LabelSelectorOpNotIn,
 11338  											Values:   []string{"value1", "value2"},
 11339  										},
 11340  										{
 11341  											// This is the same key as in MismatchLabelKeys
 11342  											// but it's allowed.
 11343  											Key:      "key2",
 11344  											Operator: metav1.LabelSelectorOpIn,
 11345  											Values:   []string{"value1"},
 11346  										},
 11347  										{
 11348  											Key:      "key2",
 11349  											Operator: metav1.LabelSelectorOpNotIn,
 11350  											Values:   []string{"value1"},
 11351  										},
 11352  									},
 11353  								},
 11354  								TopologyKey:       "k8s.io/zone",
 11355  								MismatchLabelKeys: []string{"key2"},
 11356  							},
 11357  						},
 11358  					},
 11359  				},
 11360  			},
 11361  		},
 11362  	}
 11363  
 11364  	for k, v := range successCases {
 11365  		t.Run(k, func(t *testing.T) {
 11366  			if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
 11367  				t.Errorf("expected success: %v", errs)
 11368  			}
 11369  		})
 11370  	}
 11371  
 11372  	errorCases := map[string]struct {
 11373  		spec          core.Pod
 11374  		expectedError string
 11375  	}{
 11376  		"bad name": {
 11377  			expectedError: "metadata.name",
 11378  			spec: core.Pod{
 11379  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"},
 11380  				Spec: core.PodSpec{
 11381  					RestartPolicy: core.RestartPolicyAlways,
 11382  					DNSPolicy:     core.DNSClusterFirst,
 11383  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11384  				},
 11385  			},
 11386  		},
 11387  		"image whitespace": {
 11388  			expectedError: "spec.containers[0].image",
 11389  			spec: core.Pod{
 11390  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11391  				Spec: core.PodSpec{
 11392  					RestartPolicy: core.RestartPolicyAlways,
 11393  					DNSPolicy:     core.DNSClusterFirst,
 11394  					Containers:    []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11395  				},
 11396  			},
 11397  		},
 11398  		"image leading and trailing whitespace": {
 11399  			expectedError: "spec.containers[0].image",
 11400  			spec: core.Pod{
 11401  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11402  				Spec: core.PodSpec{
 11403  					RestartPolicy: core.RestartPolicyAlways,
 11404  					DNSPolicy:     core.DNSClusterFirst,
 11405  					Containers:    []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11406  				},
 11407  			},
 11408  		},
 11409  		"bad namespace": {
 11410  			expectedError: "metadata.namespace",
 11411  			spec: core.Pod{
 11412  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""},
 11413  				Spec: core.PodSpec{
 11414  					RestartPolicy: core.RestartPolicyAlways,
 11415  					DNSPolicy:     core.DNSClusterFirst,
 11416  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11417  				},
 11418  			},
 11419  		},
 11420  		"bad spec": {
 11421  			expectedError: "spec.containers[0].name",
 11422  			spec: core.Pod{
 11423  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11424  				Spec: core.PodSpec{
 11425  					Containers: []core.Container{{}},
 11426  				},
 11427  			},
 11428  		},
 11429  		"bad label": {
 11430  			expectedError: "NoUppercaseOrSpecialCharsLike=Equals",
 11431  			spec: core.Pod{
 11432  				ObjectMeta: metav1.ObjectMeta{
 11433  					Name:      "abc",
 11434  					Namespace: "ns",
 11435  					Labels: map[string]string{
 11436  						"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 11437  					},
 11438  				},
 11439  				Spec: core.PodSpec{
 11440  					RestartPolicy: core.RestartPolicyAlways,
 11441  					DNSPolicy:     core.DNSClusterFirst,
 11442  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11443  				},
 11444  			},
 11445  		},
 11446  		"invalid node selector requirement in node affinity, operator can't be null": {
 11447  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator",
 11448  			spec: core.Pod{
 11449  				ObjectMeta: metav1.ObjectMeta{
 11450  					Name:      "123",
 11451  					Namespace: "ns",
 11452  				},
 11453  				Spec: validPodSpec(&core.Affinity{
 11454  					NodeAffinity: &core.NodeAffinity{
 11455  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11456  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11457  								MatchExpressions: []core.NodeSelectorRequirement{{
 11458  									Key: "key1",
 11459  								}},
 11460  							}},
 11461  						},
 11462  					},
 11463  				}),
 11464  			},
 11465  		},
 11466  		"invalid node selector requirement in node affinity, key is invalid": {
 11467  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key",
 11468  			spec: core.Pod{
 11469  				ObjectMeta: metav1.ObjectMeta{
 11470  					Name:      "123",
 11471  					Namespace: "ns",
 11472  				},
 11473  				Spec: validPodSpec(&core.Affinity{
 11474  					NodeAffinity: &core.NodeAffinity{
 11475  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11476  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11477  								MatchExpressions: []core.NodeSelectorRequirement{{
 11478  									Key:      "invalid key ___@#",
 11479  									Operator: core.NodeSelectorOpExists,
 11480  								}},
 11481  							}},
 11482  						},
 11483  					},
 11484  				}),
 11485  			},
 11486  		},
 11487  		"invalid node field selector requirement in node affinity, more values for field selector": {
 11488  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values",
 11489  			spec: core.Pod{
 11490  				ObjectMeta: metav1.ObjectMeta{
 11491  					Name:      "123",
 11492  					Namespace: "ns",
 11493  				},
 11494  				Spec: validPodSpec(&core.Affinity{
 11495  					NodeAffinity: &core.NodeAffinity{
 11496  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11497  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11498  								MatchFields: []core.NodeSelectorRequirement{{
 11499  									Key:      "metadata.name",
 11500  									Operator: core.NodeSelectorOpIn,
 11501  									Values:   []string{"host1", "host2"},
 11502  								}},
 11503  							}},
 11504  						},
 11505  					},
 11506  				}),
 11507  			},
 11508  		},
 11509  		"invalid node field selector requirement in node affinity, invalid operator": {
 11510  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator",
 11511  			spec: core.Pod{
 11512  				ObjectMeta: metav1.ObjectMeta{
 11513  					Name:      "123",
 11514  					Namespace: "ns",
 11515  				},
 11516  				Spec: validPodSpec(&core.Affinity{
 11517  					NodeAffinity: &core.NodeAffinity{
 11518  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11519  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11520  								MatchFields: []core.NodeSelectorRequirement{{
 11521  									Key:      "metadata.name",
 11522  									Operator: core.NodeSelectorOpExists,
 11523  								}},
 11524  							}},
 11525  						},
 11526  					},
 11527  				}),
 11528  			},
 11529  		},
 11530  		"invalid node field selector requirement in node affinity, invalid key": {
 11531  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key",
 11532  			spec: core.Pod{
 11533  				ObjectMeta: metav1.ObjectMeta{
 11534  					Name:      "123",
 11535  					Namespace: "ns",
 11536  				},
 11537  				Spec: validPodSpec(&core.Affinity{
 11538  					NodeAffinity: &core.NodeAffinity{
 11539  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11540  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11541  								MatchFields: []core.NodeSelectorRequirement{{
 11542  									Key:      "metadata.namespace",
 11543  									Operator: core.NodeSelectorOpIn,
 11544  									Values:   []string{"ns1"},
 11545  								}},
 11546  							}},
 11547  						},
 11548  					},
 11549  				}),
 11550  			},
 11551  		},
 11552  		"invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": {
 11553  			expectedError: "must be in the range 1-100",
 11554  			spec: core.Pod{
 11555  				ObjectMeta: metav1.ObjectMeta{
 11556  					Name:      "123",
 11557  					Namespace: "ns",
 11558  				},
 11559  				Spec: validPodSpec(&core.Affinity{
 11560  					NodeAffinity: &core.NodeAffinity{
 11561  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 11562  							Weight: 199,
 11563  							Preference: core.NodeSelectorTerm{
 11564  								MatchExpressions: []core.NodeSelectorRequirement{{
 11565  									Key:      "foo",
 11566  									Operator: core.NodeSelectorOpIn,
 11567  									Values:   []string{"bar"},
 11568  								}},
 11569  							},
 11570  						}},
 11571  					},
 11572  				}),
 11573  			},
 11574  		},
 11575  		"invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": {
 11576  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms",
 11577  			spec: core.Pod{
 11578  				ObjectMeta: metav1.ObjectMeta{
 11579  					Name:      "123",
 11580  					Namespace: "ns",
 11581  				},
 11582  				Spec: validPodSpec(&core.Affinity{
 11583  					NodeAffinity: &core.NodeAffinity{
 11584  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11585  							NodeSelectorTerms: []core.NodeSelectorTerm{},
 11586  						},
 11587  					},
 11588  				}),
 11589  			},
 11590  		},
 11591  		"invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
 11592  			expectedError: "must be in the range 1-100",
 11593  			spec: core.Pod{
 11594  				ObjectMeta: metav1.ObjectMeta{
 11595  					Name:      "123",
 11596  					Namespace: "ns",
 11597  				},
 11598  				Spec: validPodSpec(&core.Affinity{
 11599  					PodAffinity: &core.PodAffinity{
 11600  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11601  							Weight: 109,
 11602  							PodAffinityTerm: core.PodAffinityTerm{
 11603  								LabelSelector: &metav1.LabelSelector{
 11604  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11605  										Key:      "key2",
 11606  										Operator: metav1.LabelSelectorOpNotIn,
 11607  										Values:   []string{"value1", "value2"},
 11608  									}},
 11609  								},
 11610  								Namespaces:  []string{"ns"},
 11611  								TopologyKey: "region",
 11612  							},
 11613  						}},
 11614  					},
 11615  				}),
 11616  			},
 11617  		},
 11618  		"invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
 11619  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values",
 11620  			spec: core.Pod{
 11621  				ObjectMeta: metav1.ObjectMeta{
 11622  					Name:      "123",
 11623  					Namespace: "ns",
 11624  				},
 11625  				Spec: validPodSpec(&core.Affinity{
 11626  					PodAntiAffinity: &core.PodAntiAffinity{
 11627  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11628  							Weight: 10,
 11629  							PodAffinityTerm: core.PodAffinityTerm{
 11630  								LabelSelector: &metav1.LabelSelector{
 11631  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11632  										Key:      "key2",
 11633  										Operator: metav1.LabelSelectorOpExists,
 11634  										Values:   []string{"value1", "value2"},
 11635  									}},
 11636  								},
 11637  								Namespaces:  []string{"ns"},
 11638  								TopologyKey: "region",
 11639  							},
 11640  						}},
 11641  					},
 11642  				}),
 11643  			},
 11644  		},
 11645  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, In operator must include Values": {
 11646  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 11647  			spec: core.Pod{
 11648  				ObjectMeta: metav1.ObjectMeta{
 11649  					Name:      "123",
 11650  					Namespace: "ns",
 11651  				},
 11652  				Spec: validPodSpec(&core.Affinity{
 11653  					PodAntiAffinity: &core.PodAntiAffinity{
 11654  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11655  							Weight: 10,
 11656  							PodAffinityTerm: core.PodAffinityTerm{
 11657  								NamespaceSelector: &metav1.LabelSelector{
 11658  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11659  										Key:      "key2",
 11660  										Operator: metav1.LabelSelectorOpIn,
 11661  									}},
 11662  								},
 11663  								Namespaces:  []string{"ns"},
 11664  								TopologyKey: "region",
 11665  							},
 11666  						}},
 11667  					},
 11668  				}),
 11669  			},
 11670  		},
 11671  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, Exists operator can not have values": {
 11672  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 11673  			spec: core.Pod{
 11674  				ObjectMeta: metav1.ObjectMeta{
 11675  					Name:      "123",
 11676  					Namespace: "ns",
 11677  				},
 11678  				Spec: validPodSpec(&core.Affinity{
 11679  					PodAntiAffinity: &core.PodAntiAffinity{
 11680  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11681  							Weight: 10,
 11682  							PodAffinityTerm: core.PodAffinityTerm{
 11683  								NamespaceSelector: &metav1.LabelSelector{
 11684  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11685  										Key:      "key2",
 11686  										Operator: metav1.LabelSelectorOpExists,
 11687  										Values:   []string{"value1", "value2"},
 11688  									}},
 11689  								},
 11690  								Namespaces:  []string{"ns"},
 11691  								TopologyKey: "region",
 11692  							},
 11693  						}},
 11694  					},
 11695  				}),
 11696  			},
 11697  		},
 11698  		"invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, namespace should be valid": {
 11699  			expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace",
 11700  			spec: core.Pod{
 11701  				ObjectMeta: metav1.ObjectMeta{
 11702  					Name:      "123",
 11703  					Namespace: "ns",
 11704  				},
 11705  				Spec: validPodSpec(&core.Affinity{
 11706  					PodAffinity: &core.PodAffinity{
 11707  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11708  							Weight: 10,
 11709  							PodAffinityTerm: core.PodAffinityTerm{
 11710  								LabelSelector: &metav1.LabelSelector{
 11711  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11712  										Key:      "key2",
 11713  										Operator: metav1.LabelSelectorOpExists,
 11714  									}},
 11715  								},
 11716  								Namespaces:  []string{"INVALID_NAMESPACE"},
 11717  								TopologyKey: "region",
 11718  							},
 11719  						}},
 11720  					},
 11721  				}),
 11722  			},
 11723  		},
 11724  		"invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": {
 11725  			expectedError: "can not be empty",
 11726  			spec: core.Pod{
 11727  				ObjectMeta: metav1.ObjectMeta{
 11728  					Name:      "123",
 11729  					Namespace: "ns",
 11730  				},
 11731  				Spec: validPodSpec(&core.Affinity{
 11732  					PodAffinity: &core.PodAffinity{
 11733  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11734  							LabelSelector: &metav1.LabelSelector{
 11735  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11736  									Key:      "key2",
 11737  									Operator: metav1.LabelSelectorOpIn,
 11738  									Values:   []string{"value1", "value2"},
 11739  								}},
 11740  							},
 11741  							Namespaces: []string{"ns"},
 11742  						}},
 11743  					},
 11744  				}),
 11745  			},
 11746  		},
 11747  		"invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
 11748  			expectedError: "can not be empty",
 11749  			spec: core.Pod{
 11750  				ObjectMeta: metav1.ObjectMeta{
 11751  					Name:      "123",
 11752  					Namespace: "ns",
 11753  				},
 11754  				Spec: validPodSpec(&core.Affinity{
 11755  					PodAntiAffinity: &core.PodAntiAffinity{
 11756  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11757  							LabelSelector: &metav1.LabelSelector{
 11758  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11759  									Key:      "key2",
 11760  									Operator: metav1.LabelSelectorOpIn,
 11761  									Values:   []string{"value1", "value2"},
 11762  								}},
 11763  							},
 11764  							Namespaces: []string{"ns"},
 11765  						}},
 11766  					},
 11767  				}),
 11768  			},
 11769  		},
 11770  		"invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": {
 11771  			expectedError: "can not be empty",
 11772  			spec: core.Pod{
 11773  				ObjectMeta: metav1.ObjectMeta{
 11774  					Name:      "123",
 11775  					Namespace: "ns",
 11776  				},
 11777  				Spec: validPodSpec(&core.Affinity{
 11778  					PodAffinity: &core.PodAffinity{
 11779  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11780  							Weight: 10,
 11781  							PodAffinityTerm: core.PodAffinityTerm{
 11782  								LabelSelector: &metav1.LabelSelector{
 11783  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11784  										Key:      "key2",
 11785  										Operator: metav1.LabelSelectorOpNotIn,
 11786  										Values:   []string{"value1", "value2"},
 11787  									}},
 11788  								},
 11789  								Namespaces: []string{"ns"},
 11790  							},
 11791  						}},
 11792  					},
 11793  				}),
 11794  			},
 11795  		},
 11796  		"invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": {
 11797  			expectedError: "can not be empty",
 11798  			spec: core.Pod{
 11799  				ObjectMeta: metav1.ObjectMeta{
 11800  					Name:      "123",
 11801  					Namespace: "ns",
 11802  				},
 11803  				Spec: validPodSpec(&core.Affinity{
 11804  					PodAntiAffinity: &core.PodAntiAffinity{
 11805  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11806  							Weight: 10,
 11807  							PodAffinityTerm: core.PodAffinityTerm{
 11808  								LabelSelector: &metav1.LabelSelector{
 11809  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11810  										Key:      "key2",
 11811  										Operator: metav1.LabelSelectorOpNotIn,
 11812  										Values:   []string{"value1", "value2"},
 11813  									}},
 11814  								},
 11815  								Namespaces: []string{"ns"},
 11816  							},
 11817  						}},
 11818  					},
 11819  				}),
 11820  			},
 11821  		},
 11822  		"invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11823  			expectedError: "prefix part must be non-empty",
 11824  			spec: core.Pod{
 11825  				ObjectMeta: metav1.ObjectMeta{
 11826  					Name:      "123",
 11827  					Namespace: "ns",
 11828  				},
 11829  				Spec: validPodSpec(&core.Affinity{
 11830  					PodAffinity: &core.PodAffinity{
 11831  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11832  							{
 11833  								Weight: 10,
 11834  								PodAffinityTerm: core.PodAffinityTerm{
 11835  									LabelSelector: &metav1.LabelSelector{
 11836  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11837  											{
 11838  												Key:      "key",
 11839  												Operator: metav1.LabelSelectorOpNotIn,
 11840  												Values:   []string{"value1", "value2"},
 11841  											},
 11842  										},
 11843  									},
 11844  									TopologyKey:    "k8s.io/zone",
 11845  									MatchLabelKeys: []string{"/simple"},
 11846  								},
 11847  							},
 11848  						},
 11849  					},
 11850  				}),
 11851  			},
 11852  		},
 11853  		"invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11854  			expectedError: "prefix part must be non-empty",
 11855  			spec: core.Pod{
 11856  				ObjectMeta: metav1.ObjectMeta{
 11857  					Name:      "123",
 11858  					Namespace: "ns",
 11859  				},
 11860  				Spec: validPodSpec(&core.Affinity{
 11861  					PodAffinity: &core.PodAffinity{
 11862  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11863  							{
 11864  								LabelSelector: &metav1.LabelSelector{
 11865  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11866  										{
 11867  											Key:      "key",
 11868  											Operator: metav1.LabelSelectorOpNotIn,
 11869  											Values:   []string{"value1", "value2"},
 11870  										},
 11871  									},
 11872  								},
 11873  								TopologyKey:    "k8s.io/zone",
 11874  								MatchLabelKeys: []string{"/simple"},
 11875  							},
 11876  						},
 11877  					},
 11878  				}),
 11879  			},
 11880  		},
 11881  		"invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 11882  			expectedError: "prefix part must be non-empty",
 11883  			spec: core.Pod{
 11884  				ObjectMeta: metav1.ObjectMeta{
 11885  					Name:      "123",
 11886  					Namespace: "ns",
 11887  				},
 11888  				Spec: validPodSpec(&core.Affinity{
 11889  					PodAntiAffinity: &core.PodAntiAffinity{
 11890  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11891  							{
 11892  								Weight: 10,
 11893  								PodAffinityTerm: core.PodAffinityTerm{
 11894  									LabelSelector: &metav1.LabelSelector{
 11895  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11896  											{
 11897  												Key:      "key",
 11898  												Operator: metav1.LabelSelectorOpNotIn,
 11899  												Values:   []string{"value1", "value2"},
 11900  											},
 11901  										},
 11902  									},
 11903  									TopologyKey:    "k8s.io/zone",
 11904  									MatchLabelKeys: []string{"/simple"},
 11905  								},
 11906  							},
 11907  						},
 11908  					},
 11909  				}),
 11910  			},
 11911  		},
 11912  		"invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 11913  			expectedError: "prefix part must be non-empty",
 11914  			spec: core.Pod{
 11915  				ObjectMeta: metav1.ObjectMeta{
 11916  					Name:      "123",
 11917  					Namespace: "ns",
 11918  				},
 11919  				Spec: validPodSpec(&core.Affinity{
 11920  					PodAntiAffinity: &core.PodAntiAffinity{
 11921  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11922  							{
 11923  								LabelSelector: &metav1.LabelSelector{
 11924  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11925  										{
 11926  											Key:      "key",
 11927  											Operator: metav1.LabelSelectorOpNotIn,
 11928  											Values:   []string{"value1", "value2"},
 11929  										},
 11930  									},
 11931  								},
 11932  								TopologyKey:    "k8s.io/zone",
 11933  								MatchLabelKeys: []string{"/simple"},
 11934  							},
 11935  						},
 11936  					},
 11937  				}),
 11938  			},
 11939  		},
 11940  		"invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 11941  			expectedError: "prefix part must be non-empty",
 11942  			spec: core.Pod{
 11943  				ObjectMeta: metav1.ObjectMeta{
 11944  					Name:      "123",
 11945  					Namespace: "ns",
 11946  				},
 11947  				Spec: validPodSpec(&core.Affinity{
 11948  					PodAffinity: &core.PodAffinity{
 11949  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11950  							{
 11951  								Weight: 10,
 11952  								PodAffinityTerm: core.PodAffinityTerm{
 11953  									LabelSelector: &metav1.LabelSelector{
 11954  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11955  											{
 11956  												Key:      "key",
 11957  												Operator: metav1.LabelSelectorOpNotIn,
 11958  												Values:   []string{"value1", "value2"},
 11959  											},
 11960  										},
 11961  									},
 11962  									TopologyKey:       "k8s.io/zone",
 11963  									MismatchLabelKeys: []string{"/simple"},
 11964  								},
 11965  							},
 11966  						},
 11967  					},
 11968  				}),
 11969  			},
 11970  		},
 11971  		"invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 11972  			expectedError: "prefix part must be non-empty",
 11973  			spec: core.Pod{
 11974  				ObjectMeta: metav1.ObjectMeta{
 11975  					Name:      "123",
 11976  					Namespace: "ns",
 11977  				},
 11978  				Spec: validPodSpec(&core.Affinity{
 11979  					PodAffinity: &core.PodAffinity{
 11980  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11981  							{
 11982  								LabelSelector: &metav1.LabelSelector{
 11983  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11984  										{
 11985  											Key:      "key",
 11986  											Operator: metav1.LabelSelectorOpNotIn,
 11987  											Values:   []string{"value1", "value2"},
 11988  										},
 11989  									},
 11990  								},
 11991  								TopologyKey:       "k8s.io/zone",
 11992  								MismatchLabelKeys: []string{"/simple"},
 11993  							},
 11994  						},
 11995  					},
 11996  				}),
 11997  			},
 11998  		},
 11999  		"invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 12000  			expectedError: "prefix part must be non-empty",
 12001  			spec: core.Pod{
 12002  				ObjectMeta: metav1.ObjectMeta{
 12003  					Name:      "123",
 12004  					Namespace: "ns",
 12005  				},
 12006  				Spec: validPodSpec(&core.Affinity{
 12007  					PodAntiAffinity: &core.PodAntiAffinity{
 12008  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12009  							{
 12010  								Weight: 10,
 12011  								PodAffinityTerm: core.PodAffinityTerm{
 12012  									LabelSelector: &metav1.LabelSelector{
 12013  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12014  											{
 12015  												Key:      "key",
 12016  												Operator: metav1.LabelSelectorOpNotIn,
 12017  												Values:   []string{"value1", "value2"},
 12018  											},
 12019  										},
 12020  									},
 12021  									TopologyKey:       "k8s.io/zone",
 12022  									MismatchLabelKeys: []string{"/simple"},
 12023  								},
 12024  							},
 12025  						},
 12026  					},
 12027  				}),
 12028  			},
 12029  		},
 12030  		"invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 12031  			expectedError: "prefix part must be non-empty",
 12032  			spec: core.Pod{
 12033  				ObjectMeta: metav1.ObjectMeta{
 12034  					Name:      "123",
 12035  					Namespace: "ns",
 12036  				},
 12037  				Spec: validPodSpec(&core.Affinity{
 12038  					PodAntiAffinity: &core.PodAntiAffinity{
 12039  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12040  							{
 12041  								LabelSelector: &metav1.LabelSelector{
 12042  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12043  										{
 12044  											Key:      "key",
 12045  											Operator: metav1.LabelSelectorOpNotIn,
 12046  											Values:   []string{"value1", "value2"},
 12047  										},
 12048  									},
 12049  								},
 12050  								TopologyKey:       "k8s.io/zone",
 12051  								MismatchLabelKeys: []string{"/simple"},
 12052  							},
 12053  						},
 12054  					},
 12055  				}),
 12056  			},
 12057  		},
 12058  		"invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": {
 12059  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12060  			spec: core.Pod{
 12061  				ObjectMeta: metav1.ObjectMeta{
 12062  					Name:      "123",
 12063  					Namespace: "ns",
 12064  					Labels:    map[string]string{"key": "value1"},
 12065  				},
 12066  				Spec: validPodSpec(&core.Affinity{
 12067  					PodAffinity: &core.PodAffinity{
 12068  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12069  							{
 12070  								Weight: 10,
 12071  								PodAffinityTerm: core.PodAffinityTerm{
 12072  									LabelSelector: &metav1.LabelSelector{
 12073  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12074  											// This one should be created from MatchLabelKeys.
 12075  											{
 12076  												Key:      "key",
 12077  												Operator: metav1.LabelSelectorOpIn,
 12078  												Values:   []string{"value1"},
 12079  											},
 12080  											{
 12081  												Key:      "key",
 12082  												Operator: metav1.LabelSelectorOpNotIn,
 12083  												Values:   []string{"value2"},
 12084  											},
 12085  										},
 12086  									},
 12087  									TopologyKey:    "k8s.io/zone",
 12088  									MatchLabelKeys: []string{"key"},
 12089  								},
 12090  							},
 12091  						},
 12092  					},
 12093  				}),
 12094  			},
 12095  		},
 12096  		"invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": {
 12097  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12098  			spec: core.Pod{
 12099  				ObjectMeta: metav1.ObjectMeta{
 12100  					Name:      "123",
 12101  					Namespace: "ns",
 12102  					Labels:    map[string]string{"key": "value1"},
 12103  				},
 12104  				Spec: validPodSpec(&core.Affinity{
 12105  					PodAffinity: &core.PodAffinity{
 12106  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12107  							{
 12108  								LabelSelector: &metav1.LabelSelector{
 12109  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12110  										// This one should be created from MatchLabelKeys.
 12111  										{
 12112  											Key:      "key",
 12113  											Operator: metav1.LabelSelectorOpIn,
 12114  											Values:   []string{"value1"},
 12115  										},
 12116  										{
 12117  											Key:      "key",
 12118  											Operator: metav1.LabelSelectorOpNotIn,
 12119  											Values:   []string{"value2"},
 12120  										},
 12121  									},
 12122  								},
 12123  								TopologyKey:    "k8s.io/zone",
 12124  								MatchLabelKeys: []string{"key"},
 12125  							},
 12126  						},
 12127  					},
 12128  				}),
 12129  			},
 12130  		},
 12131  		"invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 12132  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12133  			spec: core.Pod{
 12134  				ObjectMeta: metav1.ObjectMeta{
 12135  					Name:      "123",
 12136  					Namespace: "ns",
 12137  					Labels:    map[string]string{"key": "value1"},
 12138  				},
 12139  				Spec: validPodSpec(&core.Affinity{
 12140  					PodAntiAffinity: &core.PodAntiAffinity{
 12141  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12142  							{
 12143  								Weight: 10,
 12144  								PodAffinityTerm: core.PodAffinityTerm{
 12145  									LabelSelector: &metav1.LabelSelector{
 12146  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12147  											// This one should be created from MatchLabelKeys.
 12148  											{
 12149  												Key:      "key",
 12150  												Operator: metav1.LabelSelectorOpIn,
 12151  												Values:   []string{"value1"},
 12152  											},
 12153  											{
 12154  												Key:      "key",
 12155  												Operator: metav1.LabelSelectorOpNotIn,
 12156  												Values:   []string{"value2"},
 12157  											},
 12158  										},
 12159  									},
 12160  									TopologyKey:    "k8s.io/zone",
 12161  									MatchLabelKeys: []string{"key"},
 12162  								},
 12163  							},
 12164  						},
 12165  					},
 12166  				}),
 12167  			},
 12168  		},
 12169  		"invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 12170  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12171  			spec: core.Pod{
 12172  				ObjectMeta: metav1.ObjectMeta{
 12173  					Name:      "123",
 12174  					Namespace: "ns",
 12175  					Labels:    map[string]string{"key": "value1"},
 12176  				},
 12177  				Spec: validPodSpec(&core.Affinity{
 12178  					PodAntiAffinity: &core.PodAntiAffinity{
 12179  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12180  							{
 12181  								LabelSelector: &metav1.LabelSelector{
 12182  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12183  										// This one should be created from MatchLabelKeys.
 12184  										{
 12185  											Key:      "key",
 12186  											Operator: metav1.LabelSelectorOpIn,
 12187  											Values:   []string{"value1"},
 12188  										},
 12189  										{
 12190  											Key:      "key",
 12191  											Operator: metav1.LabelSelectorOpNotIn,
 12192  											Values:   []string{"value2"},
 12193  										},
 12194  									},
 12195  								},
 12196  								TopologyKey:    "k8s.io/zone",
 12197  								MatchLabelKeys: []string{"key"},
 12198  							},
 12199  						},
 12200  					},
 12201  				}),
 12202  			},
 12203  		},
 12204  		"invalid soft pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12205  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12206  			spec: core.Pod{
 12207  				ObjectMeta: metav1.ObjectMeta{
 12208  					Name:      "123",
 12209  					Namespace: "ns",
 12210  				},
 12211  				Spec: validPodSpec(&core.Affinity{
 12212  					PodAffinity: &core.PodAffinity{
 12213  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12214  							{
 12215  								Weight: 10,
 12216  								PodAffinityTerm: core.PodAffinityTerm{
 12217  									LabelSelector: &metav1.LabelSelector{
 12218  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12219  											{
 12220  												Key:      "key",
 12221  												Operator: metav1.LabelSelectorOpNotIn,
 12222  												Values:   []string{"value1", "value2"},
 12223  											},
 12224  										},
 12225  									},
 12226  									TopologyKey:       "k8s.io/zone",
 12227  									MatchLabelKeys:    []string{"samekey"},
 12228  									MismatchLabelKeys: []string{"samekey"},
 12229  								},
 12230  							},
 12231  						},
 12232  					},
 12233  				}),
 12234  			},
 12235  		},
 12236  		"invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12237  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12238  			spec: core.Pod{
 12239  				ObjectMeta: metav1.ObjectMeta{
 12240  					Name:      "123",
 12241  					Namespace: "ns",
 12242  				},
 12243  				Spec: validPodSpec(&core.Affinity{
 12244  					PodAffinity: &core.PodAffinity{
 12245  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12246  							{
 12247  								LabelSelector: &metav1.LabelSelector{
 12248  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12249  										{
 12250  											Key:      "key",
 12251  											Operator: metav1.LabelSelectorOpNotIn,
 12252  											Values:   []string{"value1", "value2"},
 12253  										},
 12254  									},
 12255  								},
 12256  								TopologyKey:       "k8s.io/zone",
 12257  								MatchLabelKeys:    []string{"samekey"},
 12258  								MismatchLabelKeys: []string{"samekey"},
 12259  							},
 12260  						},
 12261  					},
 12262  				}),
 12263  			},
 12264  		},
 12265  		"invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12266  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12267  			spec: core.Pod{
 12268  				ObjectMeta: metav1.ObjectMeta{
 12269  					Name:      "123",
 12270  					Namespace: "ns",
 12271  				},
 12272  				Spec: validPodSpec(&core.Affinity{
 12273  					PodAntiAffinity: &core.PodAntiAffinity{
 12274  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12275  							{
 12276  								Weight: 10,
 12277  								PodAffinityTerm: core.PodAffinityTerm{
 12278  									LabelSelector: &metav1.LabelSelector{
 12279  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12280  											{
 12281  												Key:      "key",
 12282  												Operator: metav1.LabelSelectorOpNotIn,
 12283  												Values:   []string{"value1", "value2"},
 12284  											},
 12285  										},
 12286  									},
 12287  									TopologyKey:       "k8s.io/zone",
 12288  									MatchLabelKeys:    []string{"samekey"},
 12289  									MismatchLabelKeys: []string{"samekey"},
 12290  								},
 12291  							},
 12292  						},
 12293  					},
 12294  				}),
 12295  			},
 12296  		},
 12297  		"invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12298  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12299  			spec: core.Pod{
 12300  				ObjectMeta: metav1.ObjectMeta{
 12301  					Name:      "123",
 12302  					Namespace: "ns",
 12303  				},
 12304  				Spec: validPodSpec(&core.Affinity{
 12305  					PodAntiAffinity: &core.PodAntiAffinity{
 12306  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12307  							{
 12308  								LabelSelector: &metav1.LabelSelector{
 12309  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12310  										{
 12311  											Key:      "key",
 12312  											Operator: metav1.LabelSelectorOpNotIn,
 12313  											Values:   []string{"value1", "value2"},
 12314  										},
 12315  									},
 12316  								},
 12317  								TopologyKey:       "k8s.io/zone",
 12318  								MatchLabelKeys:    []string{"samekey"},
 12319  								MismatchLabelKeys: []string{"samekey"},
 12320  							},
 12321  						},
 12322  					},
 12323  				}),
 12324  			},
 12325  		},
 12326  		"invalid toleration key": {
 12327  			expectedError: "spec.tolerations[0].key",
 12328  			spec: core.Pod{
 12329  				ObjectMeta: metav1.ObjectMeta{
 12330  					Name:      "123",
 12331  					Namespace: "ns",
 12332  				},
 12333  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 12334  			},
 12335  		},
 12336  		"invalid toleration operator": {
 12337  			expectedError: "spec.tolerations[0].operator",
 12338  			spec: core.Pod{
 12339  				ObjectMeta: metav1.ObjectMeta{
 12340  					Name:      "123",
 12341  					Namespace: "ns",
 12342  				},
 12343  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}),
 12344  			},
 12345  		},
 12346  		"value must be empty when `operator` is 'Exists'": {
 12347  			expectedError: "spec.tolerations[0].operator",
 12348  			spec: core.Pod{
 12349  				ObjectMeta: metav1.ObjectMeta{
 12350  					Name:      "123",
 12351  					Namespace: "ns",
 12352  				},
 12353  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}),
 12354  			},
 12355  		},
 12356  
 12357  		"operator must be 'Exists' when `key` is empty": {
 12358  			expectedError: "spec.tolerations[0].operator",
 12359  			spec: core.Pod{
 12360  				ObjectMeta: metav1.ObjectMeta{
 12361  					Name:      "123",
 12362  					Namespace: "ns",
 12363  				},
 12364  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 12365  			},
 12366  		},
 12367  		"effect must be 'NoExecute' when `TolerationSeconds` is set": {
 12368  			expectedError: "spec.tolerations[0].effect",
 12369  			spec: core.Pod{
 12370  				ObjectMeta: metav1.ObjectMeta{
 12371  					Name:      "pod-forgiveness-invalid",
 12372  					Namespace: "ns",
 12373  				},
 12374  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}),
 12375  			},
 12376  		},
 12377  		"must be a valid pod seccomp profile": {
 12378  			expectedError: "must be a valid seccomp profile",
 12379  			spec: core.Pod{
 12380  				ObjectMeta: metav1.ObjectMeta{
 12381  					Name:      "123",
 12382  					Namespace: "ns",
 12383  					Annotations: map[string]string{
 12384  						core.SeccompPodAnnotationKey: "foo",
 12385  					},
 12386  				},
 12387  				Spec: validPodSpec(nil),
 12388  			},
 12389  		},
 12390  		"must be a valid container seccomp profile": {
 12391  			expectedError: "must be a valid seccomp profile",
 12392  			spec: core.Pod{
 12393  				ObjectMeta: metav1.ObjectMeta{
 12394  					Name:      "123",
 12395  					Namespace: "ns",
 12396  					Annotations: map[string]string{
 12397  						core.SeccompContainerAnnotationKeyPrefix + "foo": "foo",
 12398  					},
 12399  				},
 12400  				Spec: validPodSpec(nil),
 12401  			},
 12402  		},
 12403  		"must be a non-empty container name in seccomp annotation": {
 12404  			expectedError: "name part must be non-empty",
 12405  			spec: core.Pod{
 12406  				ObjectMeta: metav1.ObjectMeta{
 12407  					Name:      "123",
 12408  					Namespace: "ns",
 12409  					Annotations: map[string]string{
 12410  						core.SeccompContainerAnnotationKeyPrefix: "foo",
 12411  					},
 12412  				},
 12413  				Spec: validPodSpec(nil),
 12414  			},
 12415  		},
 12416  		"must be a non-empty container profile in seccomp annotation": {
 12417  			expectedError: "must be a valid seccomp profile",
 12418  			spec: core.Pod{
 12419  				ObjectMeta: metav1.ObjectMeta{
 12420  					Name:      "123",
 12421  					Namespace: "ns",
 12422  					Annotations: map[string]string{
 12423  						core.SeccompContainerAnnotationKeyPrefix + "foo": "",
 12424  					},
 12425  				},
 12426  				Spec: validPodSpec(nil),
 12427  			},
 12428  		},
 12429  		"must match seccomp profile type and pod annotation": {
 12430  			expectedError: "seccomp type in annotation and field must match",
 12431  			spec: core.Pod{
 12432  				ObjectMeta: metav1.ObjectMeta{
 12433  					Name:      "123",
 12434  					Namespace: "ns",
 12435  					Annotations: map[string]string{
 12436  						core.SeccompPodAnnotationKey: "unconfined",
 12437  					},
 12438  				},
 12439  				Spec: core.PodSpec{
 12440  					SecurityContext: &core.PodSecurityContext{
 12441  						SeccompProfile: &core.SeccompProfile{
 12442  							Type: core.SeccompProfileTypeRuntimeDefault,
 12443  						},
 12444  					},
 12445  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12446  					RestartPolicy: core.RestartPolicyAlways,
 12447  					DNSPolicy:     core.DNSClusterFirst,
 12448  				},
 12449  			},
 12450  		},
 12451  		"must match seccomp profile type and container annotation": {
 12452  			expectedError: "seccomp type in annotation and field must match",
 12453  			spec: core.Pod{
 12454  				ObjectMeta: metav1.ObjectMeta{
 12455  					Name:      "123",
 12456  					Namespace: "ns",
 12457  					Annotations: map[string]string{
 12458  						core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined",
 12459  					},
 12460  				},
 12461  				Spec: core.PodSpec{
 12462  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12463  						SecurityContext: &core.SecurityContext{
 12464  							SeccompProfile: &core.SeccompProfile{
 12465  								Type: core.SeccompProfileTypeRuntimeDefault,
 12466  							},
 12467  						}}},
 12468  					RestartPolicy: core.RestartPolicyAlways,
 12469  					DNSPolicy:     core.DNSClusterFirst,
 12470  				},
 12471  			},
 12472  		},
 12473  		"must be a relative path in a node-local seccomp profile annotation": {
 12474  			expectedError: "must be a relative path",
 12475  			spec: core.Pod{
 12476  				ObjectMeta: metav1.ObjectMeta{
 12477  					Name:      "123",
 12478  					Namespace: "ns",
 12479  					Annotations: map[string]string{
 12480  						core.SeccompPodAnnotationKey: "localhost//foo",
 12481  					},
 12482  				},
 12483  				Spec: validPodSpec(nil),
 12484  			},
 12485  		},
 12486  		"must not start with '../'": {
 12487  			expectedError: "must not contain '..'",
 12488  			spec: core.Pod{
 12489  				ObjectMeta: metav1.ObjectMeta{
 12490  					Name:      "123",
 12491  					Namespace: "ns",
 12492  					Annotations: map[string]string{
 12493  						core.SeccompPodAnnotationKey: "localhost/../foo",
 12494  					},
 12495  				},
 12496  				Spec: validPodSpec(nil),
 12497  			},
 12498  		},
 12499  		"AppArmor profile must apply to a container": {
 12500  			expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]",
 12501  			spec: core.Pod{
 12502  				ObjectMeta: metav1.ObjectMeta{
 12503  					Name:      "123",
 12504  					Namespace: "ns",
 12505  					Annotations: map[string]string{
 12506  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr":      v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12507  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12508  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12509  					},
 12510  				},
 12511  				Spec: core.PodSpec{
 12512  					InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12513  					Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12514  					RestartPolicy:  core.RestartPolicyAlways,
 12515  					DNSPolicy:      core.DNSClusterFirst,
 12516  				},
 12517  			},
 12518  		},
 12519  		"AppArmor profile format must be valid": {
 12520  			expectedError: "invalid AppArmor profile name",
 12521  			spec: core.Pod{
 12522  				ObjectMeta: metav1.ObjectMeta{
 12523  					Name:      "123",
 12524  					Namespace: "ns",
 12525  					Annotations: map[string]string{
 12526  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
 12527  					},
 12528  				},
 12529  				Spec: validPodSpec(nil),
 12530  			},
 12531  		},
 12532  		"only default AppArmor profile may start with runtime/": {
 12533  			expectedError: "invalid AppArmor profile name",
 12534  			spec: core.Pod{
 12535  				ObjectMeta: metav1.ObjectMeta{
 12536  					Name:      "123",
 12537  					Namespace: "ns",
 12538  					Annotations: map[string]string{
 12539  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
 12540  					},
 12541  				},
 12542  				Spec: validPodSpec(nil),
 12543  			},
 12544  		},
 12545  		"unsupported pod AppArmor profile type": {
 12546  			expectedError: `Unsupported value: "test"`,
 12547  			spec: core.Pod{
 12548  				ObjectMeta: metav1.ObjectMeta{
 12549  					Name:      "123",
 12550  					Namespace: "ns",
 12551  				},
 12552  				Spec: core.PodSpec{
 12553  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12554  					RestartPolicy: core.RestartPolicyAlways,
 12555  					DNSPolicy:     core.DNSDefault,
 12556  					SecurityContext: &core.PodSecurityContext{
 12557  						AppArmorProfile: &core.AppArmorProfile{
 12558  							Type: "test",
 12559  						},
 12560  					},
 12561  				},
 12562  			},
 12563  		},
 12564  		"unsupported container AppArmor profile type": {
 12565  			expectedError: `Unsupported value: "test"`,
 12566  			spec: core.Pod{
 12567  				ObjectMeta: metav1.ObjectMeta{
 12568  					Name:      "123",
 12569  					Namespace: "ns",
 12570  				},
 12571  				Spec: core.PodSpec{
 12572  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12573  						SecurityContext: &core.SecurityContext{
 12574  							AppArmorProfile: &core.AppArmorProfile{
 12575  								Type: "test",
 12576  							},
 12577  						},
 12578  					}},
 12579  					RestartPolicy: core.RestartPolicyAlways,
 12580  					DNSPolicy:     core.DNSDefault,
 12581  				},
 12582  			},
 12583  		},
 12584  		"missing pod AppArmor profile type": {
 12585  			expectedError: "Required value: type is required when appArmorProfile is set",
 12586  			spec: core.Pod{
 12587  				ObjectMeta: metav1.ObjectMeta{
 12588  					Name:      "123",
 12589  					Namespace: "ns",
 12590  				},
 12591  				Spec: core.PodSpec{
 12592  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12593  					RestartPolicy: core.RestartPolicyAlways,
 12594  					DNSPolicy:     core.DNSDefault,
 12595  					SecurityContext: &core.PodSecurityContext{
 12596  						AppArmorProfile: &core.AppArmorProfile{
 12597  							Type: "",
 12598  						},
 12599  					},
 12600  				},
 12601  			},
 12602  		},
 12603  		"missing AppArmor localhost profile": {
 12604  			expectedError: "Required value: must be set when AppArmor type is Localhost",
 12605  			spec: core.Pod{
 12606  				ObjectMeta: metav1.ObjectMeta{
 12607  					Name:      "123",
 12608  					Namespace: "ns",
 12609  				},
 12610  				Spec: core.PodSpec{
 12611  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12612  					RestartPolicy: core.RestartPolicyAlways,
 12613  					DNSPolicy:     core.DNSDefault,
 12614  					SecurityContext: &core.PodSecurityContext{
 12615  						AppArmorProfile: &core.AppArmorProfile{
 12616  							Type: core.AppArmorProfileTypeLocalhost,
 12617  						},
 12618  					},
 12619  				},
 12620  			},
 12621  		},
 12622  		"empty AppArmor localhost profile": {
 12623  			expectedError: "Required value: must be set when AppArmor type is Localhost",
 12624  			spec: core.Pod{
 12625  				ObjectMeta: metav1.ObjectMeta{
 12626  					Name:      "123",
 12627  					Namespace: "ns",
 12628  				},
 12629  				Spec: core.PodSpec{
 12630  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12631  					RestartPolicy: core.RestartPolicyAlways,
 12632  					DNSPolicy:     core.DNSDefault,
 12633  					SecurityContext: &core.PodSecurityContext{
 12634  						AppArmorProfile: &core.AppArmorProfile{
 12635  							Type:             core.AppArmorProfileTypeLocalhost,
 12636  							LocalhostProfile: ptr.To(""),
 12637  						},
 12638  					},
 12639  				},
 12640  			},
 12641  		},
 12642  		"invalid AppArmor localhost profile type": {
 12643  			expectedError: `Invalid value: "foo-bar"`,
 12644  			spec: core.Pod{
 12645  				ObjectMeta: metav1.ObjectMeta{
 12646  					Name:      "123",
 12647  					Namespace: "ns",
 12648  				},
 12649  				Spec: core.PodSpec{
 12650  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12651  					RestartPolicy: core.RestartPolicyAlways,
 12652  					DNSPolicy:     core.DNSDefault,
 12653  					SecurityContext: &core.PodSecurityContext{
 12654  						AppArmorProfile: &core.AppArmorProfile{
 12655  							Type:             core.AppArmorProfileTypeRuntimeDefault,
 12656  							LocalhostProfile: ptr.To("foo-bar"),
 12657  						},
 12658  					},
 12659  				},
 12660  			},
 12661  		},
 12662  		"invalid AppArmor localhost profile": {
 12663  			expectedError: `Invalid value: "foo-bar "`,
 12664  			spec: core.Pod{
 12665  				ObjectMeta: metav1.ObjectMeta{
 12666  					Name:      "123",
 12667  					Namespace: "ns",
 12668  				},
 12669  				Spec: core.PodSpec{
 12670  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12671  					RestartPolicy: core.RestartPolicyAlways,
 12672  					DNSPolicy:     core.DNSDefault,
 12673  					SecurityContext: &core.PodSecurityContext{
 12674  						AppArmorProfile: &core.AppArmorProfile{
 12675  							Type:             core.AppArmorProfileTypeLocalhost,
 12676  							LocalhostProfile: ptr.To("foo-bar "),
 12677  						},
 12678  					},
 12679  				},
 12680  			},
 12681  		},
 12682  		"too long AppArmor localhost profile": {
 12683  			expectedError: "Too long: may not be longer than 4095",
 12684  			spec: core.Pod{
 12685  				ObjectMeta: metav1.ObjectMeta{
 12686  					Name:      "123",
 12687  					Namespace: "ns",
 12688  				},
 12689  				Spec: core.PodSpec{
 12690  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12691  					RestartPolicy: core.RestartPolicyAlways,
 12692  					DNSPolicy:     core.DNSDefault,
 12693  					SecurityContext: &core.PodSecurityContext{
 12694  						AppArmorProfile: &core.AppArmorProfile{
 12695  							Type:             core.AppArmorProfileTypeLocalhost,
 12696  							LocalhostProfile: ptr.To(strings.Repeat("a", 4096)),
 12697  						},
 12698  					},
 12699  				},
 12700  			},
 12701  		},
 12702  		"mismatched AppArmor field and annotation types": {
 12703  			expectedError: "Forbidden: apparmor type in annotation and field must match",
 12704  			spec: core.Pod{
 12705  				ObjectMeta: metav1.ObjectMeta{
 12706  					Name:      "123",
 12707  					Namespace: "ns",
 12708  					Annotations: map[string]string{
 12709  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
 12710  					},
 12711  				},
 12712  				Spec: core.PodSpec{
 12713  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12714  						SecurityContext: &core.SecurityContext{
 12715  							AppArmorProfile: &core.AppArmorProfile{
 12716  								Type: core.AppArmorProfileTypeUnconfined,
 12717  							},
 12718  						},
 12719  					}},
 12720  					RestartPolicy: core.RestartPolicyAlways,
 12721  					DNSPolicy:     core.DNSDefault,
 12722  				},
 12723  			},
 12724  		},
 12725  		"mismatched AppArmor pod field and annotation types": {
 12726  			expectedError: "Forbidden: apparmor type in annotation and field must match",
 12727  			spec: core.Pod{
 12728  				ObjectMeta: metav1.ObjectMeta{
 12729  					Name:      "123",
 12730  					Namespace: "ns",
 12731  					Annotations: map[string]string{
 12732  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
 12733  					},
 12734  				},
 12735  				Spec: core.PodSpec{
 12736  					SecurityContext: &core.PodSecurityContext{
 12737  						AppArmorProfile: &core.AppArmorProfile{
 12738  							Type: core.AppArmorProfileTypeUnconfined,
 12739  						},
 12740  					},
 12741  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12742  					RestartPolicy: core.RestartPolicyAlways,
 12743  					DNSPolicy:     core.DNSDefault,
 12744  				},
 12745  			},
 12746  		},
 12747  		"mismatched AppArmor localhost profiles": {
 12748  			expectedError: "Forbidden: apparmor profile in annotation and field must match",
 12749  			spec: core.Pod{
 12750  				ObjectMeta: metav1.ObjectMeta{
 12751  					Name:      "123",
 12752  					Namespace: "ns",
 12753  					Annotations: map[string]string{
 12754  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 12755  					},
 12756  				},
 12757  				Spec: core.PodSpec{
 12758  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12759  						SecurityContext: &core.SecurityContext{
 12760  							AppArmorProfile: &core.AppArmorProfile{
 12761  								Type:             core.AppArmorProfileTypeLocalhost,
 12762  								LocalhostProfile: ptr.To("bar"),
 12763  							},
 12764  						},
 12765  					}},
 12766  					RestartPolicy: core.RestartPolicyAlways,
 12767  					DNSPolicy:     core.DNSDefault,
 12768  				},
 12769  			},
 12770  		},
 12771  		"invalid extended resource name in container request": {
 12772  			expectedError: "must be a standard resource for containers",
 12773  			spec: core.Pod{
 12774  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12775  				Spec: core.PodSpec{
 12776  					Containers: []core.Container{{
 12777  						Name:            "invalid",
 12778  						Image:           "image",
 12779  						ImagePullPolicy: "IfNotPresent",
 12780  						Resources: core.ResourceRequirements{
 12781  							Requests: core.ResourceList{
 12782  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 12783  							},
 12784  							Limits: core.ResourceList{
 12785  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 12786  							},
 12787  						},
 12788  					}},
 12789  					RestartPolicy: core.RestartPolicyAlways,
 12790  					DNSPolicy:     core.DNSClusterFirst,
 12791  				},
 12792  			},
 12793  		},
 12794  		"invalid extended resource requirement: request must be == limit": {
 12795  			expectedError: "must be equal to example.com/a",
 12796  			spec: core.Pod{
 12797  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12798  				Spec: core.PodSpec{
 12799  					Containers: []core.Container{{
 12800  						Name:            "invalid",
 12801  						Image:           "image",
 12802  						ImagePullPolicy: "IfNotPresent",
 12803  						Resources: core.ResourceRequirements{
 12804  							Requests: core.ResourceList{
 12805  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 12806  							},
 12807  							Limits: core.ResourceList{
 12808  								core.ResourceName("example.com/a"): resource.MustParse("1"),
 12809  							},
 12810  						},
 12811  					}},
 12812  					RestartPolicy: core.RestartPolicyAlways,
 12813  					DNSPolicy:     core.DNSClusterFirst,
 12814  				},
 12815  			},
 12816  		},
 12817  		"invalid extended resource requirement without limit": {
 12818  			expectedError: "Limit must be set",
 12819  			spec: core.Pod{
 12820  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12821  				Spec: core.PodSpec{
 12822  					Containers: []core.Container{{
 12823  						Name:            "invalid",
 12824  						Image:           "image",
 12825  						ImagePullPolicy: "IfNotPresent",
 12826  						Resources: core.ResourceRequirements{
 12827  							Requests: core.ResourceList{
 12828  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 12829  							},
 12830  						},
 12831  					}},
 12832  					RestartPolicy: core.RestartPolicyAlways,
 12833  					DNSPolicy:     core.DNSClusterFirst,
 12834  				},
 12835  			},
 12836  		},
 12837  		"invalid fractional extended resource in container request": {
 12838  			expectedError: "must be an integer",
 12839  			spec: core.Pod{
 12840  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12841  				Spec: core.PodSpec{
 12842  					Containers: []core.Container{{
 12843  						Name:            "invalid",
 12844  						Image:           "image",
 12845  						ImagePullPolicy: "IfNotPresent",
 12846  						Resources: core.ResourceRequirements{
 12847  							Requests: core.ResourceList{
 12848  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 12849  							},
 12850  						},
 12851  					}},
 12852  					RestartPolicy: core.RestartPolicyAlways,
 12853  					DNSPolicy:     core.DNSClusterFirst,
 12854  				},
 12855  			},
 12856  		},
 12857  		"invalid fractional extended resource in init container request": {
 12858  			expectedError: "must be an integer",
 12859  			spec: core.Pod{
 12860  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12861  				Spec: core.PodSpec{
 12862  					InitContainers: []core.Container{{
 12863  						Name:            "invalid",
 12864  						Image:           "image",
 12865  						ImagePullPolicy: "IfNotPresent",
 12866  						Resources: core.ResourceRequirements{
 12867  							Requests: core.ResourceList{
 12868  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 12869  							},
 12870  						},
 12871  					}},
 12872  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12873  					RestartPolicy: core.RestartPolicyAlways,
 12874  					DNSPolicy:     core.DNSClusterFirst,
 12875  				},
 12876  			},
 12877  		},
 12878  		"invalid fractional extended resource in container limit": {
 12879  			expectedError: "must be an integer",
 12880  			spec: core.Pod{
 12881  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12882  				Spec: core.PodSpec{
 12883  					Containers: []core.Container{{
 12884  						Name:            "invalid",
 12885  						Image:           "image",
 12886  						ImagePullPolicy: "IfNotPresent",
 12887  						Resources: core.ResourceRequirements{
 12888  							Requests: core.ResourceList{
 12889  								core.ResourceName("example.com/a"): resource.MustParse("5"),
 12890  							},
 12891  							Limits: core.ResourceList{
 12892  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12893  							},
 12894  						},
 12895  					}},
 12896  					RestartPolicy: core.RestartPolicyAlways,
 12897  					DNSPolicy:     core.DNSClusterFirst,
 12898  				},
 12899  			},
 12900  		},
 12901  		"invalid fractional extended resource in init container limit": {
 12902  			expectedError: "must be an integer",
 12903  			spec: core.Pod{
 12904  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12905  				Spec: core.PodSpec{
 12906  					InitContainers: []core.Container{{
 12907  						Name:            "invalid",
 12908  						Image:           "image",
 12909  						ImagePullPolicy: "IfNotPresent",
 12910  						Resources: core.ResourceRequirements{
 12911  							Requests: core.ResourceList{
 12912  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12913  							},
 12914  							Limits: core.ResourceList{
 12915  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12916  							},
 12917  						},
 12918  					}},
 12919  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12920  					RestartPolicy: core.RestartPolicyAlways,
 12921  					DNSPolicy:     core.DNSClusterFirst,
 12922  				},
 12923  			},
 12924  		},
 12925  		"mirror-pod present without nodeName": {
 12926  			expectedError: "mirror",
 12927  			spec: core.Pod{
 12928  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}},
 12929  				Spec: core.PodSpec{
 12930  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12931  					RestartPolicy: core.RestartPolicyAlways,
 12932  					DNSPolicy:     core.DNSClusterFirst,
 12933  				},
 12934  			},
 12935  		},
 12936  		"mirror-pod populated without nodeName": {
 12937  			expectedError: "mirror",
 12938  			spec: core.Pod{
 12939  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}},
 12940  				Spec: core.PodSpec{
 12941  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12942  					RestartPolicy: core.RestartPolicyAlways,
 12943  					DNSPolicy:     core.DNSClusterFirst,
 12944  				},
 12945  			},
 12946  		},
 12947  		"serviceaccount token projected volume with no serviceaccount name specified": {
 12948  			expectedError: "must not be specified when serviceAccountName is not set",
 12949  			spec: core.Pod{
 12950  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12951  				Spec: core.PodSpec{
 12952  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12953  					RestartPolicy: core.RestartPolicyAlways,
 12954  					DNSPolicy:     core.DNSClusterFirst,
 12955  					Volumes: []core.Volume{{
 12956  						Name: "projected-volume",
 12957  						VolumeSource: core.VolumeSource{
 12958  							Projected: &core.ProjectedVolumeSource{
 12959  								Sources: []core.VolumeProjection{{
 12960  									ServiceAccountToken: &core.ServiceAccountTokenProjection{
 12961  										Audience:          "foo-audience",
 12962  										ExpirationSeconds: 6000,
 12963  										Path:              "foo-path",
 12964  									},
 12965  								}},
 12966  							},
 12967  						},
 12968  					}},
 12969  				},
 12970  			},
 12971  		},
 12972  		"ClusterTrustBundlePEM projected volume using both byName and bySigner": {
 12973  			expectedError: "only one of name and signerName may be used",
 12974  			spec: core.Pod{
 12975  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 12976  				Spec: core.PodSpec{
 12977  					ServiceAccountName: "some-service-account",
 12978  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12979  					RestartPolicy:      core.RestartPolicyAlways,
 12980  					DNSPolicy:          core.DNSClusterFirst,
 12981  					Volumes: []core.Volume{
 12982  						{
 12983  							Name: "projected-volume",
 12984  							VolumeSource: core.VolumeSource{
 12985  								Projected: &core.ProjectedVolumeSource{
 12986  									Sources: []core.VolumeProjection{
 12987  										{
 12988  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 12989  												Path:       "foo-path",
 12990  												SignerName: utilpointer.String("example.com/foo"),
 12991  												LabelSelector: &metav1.LabelSelector{
 12992  													MatchLabels: map[string]string{
 12993  														"version": "live",
 12994  													},
 12995  												},
 12996  												Name: utilpointer.String("foo"),
 12997  											},
 12998  										},
 12999  									},
 13000  								},
 13001  							},
 13002  						},
 13003  					},
 13004  				},
 13005  			},
 13006  		},
 13007  		"ClusterTrustBundlePEM projected volume byName with no name": {
 13008  			expectedError: "must be a valid object name",
 13009  			spec: core.Pod{
 13010  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13011  				Spec: core.PodSpec{
 13012  					ServiceAccountName: "some-service-account",
 13013  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13014  					RestartPolicy:      core.RestartPolicyAlways,
 13015  					DNSPolicy:          core.DNSClusterFirst,
 13016  					Volumes: []core.Volume{
 13017  						{
 13018  							Name: "projected-volume",
 13019  							VolumeSource: core.VolumeSource{
 13020  								Projected: &core.ProjectedVolumeSource{
 13021  									Sources: []core.VolumeProjection{
 13022  										{
 13023  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13024  												Path: "foo-path",
 13025  												Name: utilpointer.String(""),
 13026  											},
 13027  										},
 13028  									},
 13029  								},
 13030  							},
 13031  						},
 13032  					},
 13033  				},
 13034  			},
 13035  		},
 13036  		"ClusterTrustBundlePEM projected volume bySigner with no signer name": {
 13037  			expectedError: "must be a valid signer name",
 13038  			spec: core.Pod{
 13039  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13040  				Spec: core.PodSpec{
 13041  					ServiceAccountName: "some-service-account",
 13042  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13043  					RestartPolicy:      core.RestartPolicyAlways,
 13044  					DNSPolicy:          core.DNSClusterFirst,
 13045  					Volumes: []core.Volume{
 13046  						{
 13047  							Name: "projected-volume",
 13048  							VolumeSource: core.VolumeSource{
 13049  								Projected: &core.ProjectedVolumeSource{
 13050  									Sources: []core.VolumeProjection{
 13051  										{
 13052  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13053  												Path:       "foo-path",
 13054  												SignerName: utilpointer.String(""),
 13055  												LabelSelector: &metav1.LabelSelector{
 13056  													MatchLabels: map[string]string{
 13057  														"foo": "bar",
 13058  													},
 13059  												},
 13060  											},
 13061  										},
 13062  									},
 13063  								},
 13064  							},
 13065  						},
 13066  					},
 13067  				},
 13068  			},
 13069  		},
 13070  		"ClusterTrustBundlePEM projected volume bySigner with invalid signer name": {
 13071  			expectedError: "must be a fully qualified domain and path of the form",
 13072  			spec: core.Pod{
 13073  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13074  				Spec: core.PodSpec{
 13075  					ServiceAccountName: "some-service-account",
 13076  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13077  					RestartPolicy:      core.RestartPolicyAlways,
 13078  					DNSPolicy:          core.DNSClusterFirst,
 13079  					Volumes: []core.Volume{
 13080  						{
 13081  							Name: "projected-volume",
 13082  							VolumeSource: core.VolumeSource{
 13083  								Projected: &core.ProjectedVolumeSource{
 13084  									Sources: []core.VolumeProjection{
 13085  										{
 13086  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13087  												Path:       "foo-path",
 13088  												SignerName: utilpointer.String("example.com/foo/invalid"),
 13089  											},
 13090  										},
 13091  									},
 13092  								},
 13093  							},
 13094  						},
 13095  					},
 13096  				},
 13097  			},
 13098  		},
 13099  		"final PVC name for ephemeral volume must be valid": {
 13100  			expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
 13101  			spec: core.Pod{
 13102  				ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
 13103  				Spec: core.PodSpec{
 13104  					Volumes: []core.Volume{
 13105  						{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 13106  						{Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 13107  					},
 13108  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13109  					RestartPolicy: core.RestartPolicyAlways,
 13110  					DNSPolicy:     core.DNSClusterFirst,
 13111  				},
 13112  			},
 13113  		},
 13114  		"PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
 13115  			expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
 13116  			spec: core.Pod{
 13117  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 13118  				Spec: core.PodSpec{
 13119  					Volumes: []core.Volume{
 13120  						{Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
 13121  						{Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 13122  					},
 13123  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13124  					RestartPolicy: core.RestartPolicyAlways,
 13125  					DNSPolicy:     core.DNSClusterFirst,
 13126  				},
 13127  			},
 13128  		},
 13129  		"invalid pod-deletion-cost": {
 13130  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"text\": must be a 32bit integer",
 13131  			spec: core.Pod{
 13132  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "text"}},
 13133  				Spec: core.PodSpec{
 13134  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13135  					RestartPolicy: core.RestartPolicyAlways,
 13136  					DNSPolicy:     core.DNSClusterFirst,
 13137  				},
 13138  			},
 13139  		},
 13140  		"invalid leading zeros pod-deletion-cost": {
 13141  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"008\": must be a 32bit integer",
 13142  			spec: core.Pod{
 13143  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "008"}},
 13144  				Spec: core.PodSpec{
 13145  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13146  					RestartPolicy: core.RestartPolicyAlways,
 13147  					DNSPolicy:     core.DNSClusterFirst,
 13148  				},
 13149  			},
 13150  		},
 13151  		"invalid leading plus sign pod-deletion-cost": {
 13152  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"+10\": must be a 32bit integer",
 13153  			spec: core.Pod{
 13154  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "+10"}},
 13155  				Spec: core.PodSpec{
 13156  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13157  					RestartPolicy: core.RestartPolicyAlways,
 13158  					DNSPolicy:     core.DNSClusterFirst,
 13159  				},
 13160  			},
 13161  		},
 13162  	}
 13163  	for k, v := range errorCases {
 13164  		t.Run(k, func(t *testing.T) {
 13165  			if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
 13166  				t.Errorf("expected failure")
 13167  			} else if v.expectedError == "" {
 13168  				t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
 13169  			} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
 13170  				t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
 13171  			}
 13172  		})
 13173  	}
 13174  }
 13175  
 13176  func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
 13177  	applyEssentials := func(pod *core.Pod) {
 13178  		pod.Spec.Containers = []core.Container{
 13179  			{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
 13180  		}
 13181  		pod.Spec.RestartPolicy = core.RestartPolicyAlways
 13182  		pod.Spec.DNSPolicy = core.DNSClusterFirst
 13183  	}
 13184  	fldPath := field.NewPath("spec")
 13185  
 13186  	tests := []struct {
 13187  		name            string
 13188  		pod             *core.Pod
 13189  		wantFieldErrors field.ErrorList
 13190  	}{{
 13191  		name: "create a Pod with nodeName and schedulingGates, feature enabled",
 13192  		pod: &core.Pod{
 13193  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 13194  			Spec: core.PodSpec{
 13195  				NodeName: "node",
 13196  				SchedulingGates: []core.PodSchedulingGate{
 13197  					{Name: "foo"},
 13198  				},
 13199  			},
 13200  		},
 13201  		wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
 13202  	}, {
 13203  		name: "create a Pod with schedulingGates, feature enabled",
 13204  		pod: &core.Pod{
 13205  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 13206  			Spec: core.PodSpec{
 13207  				SchedulingGates: []core.PodSchedulingGate{
 13208  					{Name: "foo"},
 13209  				},
 13210  			},
 13211  		},
 13212  		wantFieldErrors: nil,
 13213  	},
 13214  	}
 13215  
 13216  	for _, tt := range tests {
 13217  		t.Run(tt.name, func(t *testing.T) {
 13218  			applyEssentials(tt.pod)
 13219  			errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
 13220  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 13221  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 13222  			}
 13223  		})
 13224  	}
 13225  }
 13226  
 13227  func TestValidatePodUpdate(t *testing.T) {
 13228  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
 13229  	var (
 13230  		activeDeadlineSecondsZero     = int64(0)
 13231  		activeDeadlineSecondsNegative = int64(-30)
 13232  		activeDeadlineSecondsPositive = int64(30)
 13233  		activeDeadlineSecondsLarger   = int64(31)
 13234  		validfsGroupChangePolicy      = core.FSGroupChangeOnRootMismatch
 13235  
 13236  		now    = metav1.Now()
 13237  		grace  = int64(30)
 13238  		grace2 = int64(31)
 13239  	)
 13240  
 13241  	tests := []struct {
 13242  		new  core.Pod
 13243  		old  core.Pod
 13244  		err  string
 13245  		test string
 13246  	}{
 13247  		{new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, {
 13248  			new: core.Pod{
 13249  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13250  			},
 13251  			old: core.Pod{
 13252  				ObjectMeta: metav1.ObjectMeta{Name: "bar"},
 13253  			},
 13254  			err:  "metadata.name",
 13255  			test: "ids",
 13256  		}, {
 13257  			new: core.Pod{
 13258  				ObjectMeta: metav1.ObjectMeta{
 13259  					Name: "foo",
 13260  					Labels: map[string]string{
 13261  						"foo": "bar",
 13262  					},
 13263  				},
 13264  			},
 13265  			old: core.Pod{
 13266  				ObjectMeta: metav1.ObjectMeta{
 13267  					Name: "foo",
 13268  					Labels: map[string]string{
 13269  						"bar": "foo",
 13270  					},
 13271  				},
 13272  			},
 13273  			err:  "",
 13274  			test: "labels",
 13275  		}, {
 13276  			new: core.Pod{
 13277  				ObjectMeta: metav1.ObjectMeta{
 13278  					Name: "foo",
 13279  					Annotations: map[string]string{
 13280  						"foo": "bar",
 13281  					},
 13282  				},
 13283  			},
 13284  			old: core.Pod{
 13285  				ObjectMeta: metav1.ObjectMeta{
 13286  					Name: "foo",
 13287  					Annotations: map[string]string{
 13288  						"bar": "foo",
 13289  					},
 13290  				},
 13291  			},
 13292  			err:  "",
 13293  			test: "annotations",
 13294  		}, {
 13295  			new: core.Pod{
 13296  				ObjectMeta: metav1.ObjectMeta{
 13297  					Name: "foo",
 13298  				},
 13299  				Spec: core.PodSpec{
 13300  					Containers: []core.Container{{
 13301  						Image: "foo:V1",
 13302  					}},
 13303  				},
 13304  			},
 13305  			old: core.Pod{
 13306  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13307  				Spec: core.PodSpec{
 13308  					Containers: []core.Container{{
 13309  						Image: "foo:V2",
 13310  					}, {
 13311  						Image: "bar:V2",
 13312  					}},
 13313  				},
 13314  			},
 13315  			err:  "may not add or remove containers",
 13316  			test: "less containers",
 13317  		}, {
 13318  			new: core.Pod{
 13319  				ObjectMeta: metav1.ObjectMeta{
 13320  					Name: "foo",
 13321  				},
 13322  				Spec: core.PodSpec{
 13323  					Containers: []core.Container{{
 13324  						Image: "foo:V1",
 13325  					}, {
 13326  						Image: "bar:V2",
 13327  					}},
 13328  				},
 13329  			},
 13330  			old: core.Pod{
 13331  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13332  				Spec: core.PodSpec{
 13333  					Containers: []core.Container{{
 13334  						Image: "foo:V2",
 13335  					}},
 13336  				},
 13337  			},
 13338  			err:  "may not add or remove containers",
 13339  			test: "more containers",
 13340  		}, {
 13341  			new: core.Pod{
 13342  				ObjectMeta: metav1.ObjectMeta{
 13343  					Name: "foo",
 13344  				},
 13345  				Spec: core.PodSpec{
 13346  					InitContainers: []core.Container{{
 13347  						Image: "foo:V1",
 13348  					}},
 13349  				},
 13350  			},
 13351  			old: core.Pod{
 13352  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13353  				Spec: core.PodSpec{
 13354  					InitContainers: []core.Container{{
 13355  						Image: "foo:V2",
 13356  					}, {
 13357  						Image: "bar:V2",
 13358  					}},
 13359  				},
 13360  			},
 13361  			err:  "may not add or remove containers",
 13362  			test: "more init containers",
 13363  		}, {
 13364  			new: core.Pod{
 13365  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13366  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13367  			},
 13368  			old: core.Pod{
 13369  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 13370  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13371  			},
 13372  			err:  "metadata.deletionTimestamp",
 13373  			test: "deletion timestamp removed",
 13374  		}, {
 13375  			new: core.Pod{
 13376  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 13377  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13378  			},
 13379  			old: core.Pod{
 13380  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13381  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13382  			},
 13383  			err:  "metadata.deletionTimestamp",
 13384  			test: "deletion timestamp added",
 13385  		}, {
 13386  			new: core.Pod{
 13387  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace},
 13388  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13389  			},
 13390  			old: core.Pod{
 13391  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2},
 13392  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13393  			},
 13394  			err:  "metadata.deletionGracePeriodSeconds",
 13395  			test: "deletion grace period seconds changed",
 13396  		}, {
 13397  			new: core.Pod{
 13398  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13399  				Spec: core.PodSpec{
 13400  					Containers: []core.Container{{
 13401  						Name:                     "container",
 13402  						Image:                    "foo:V1",
 13403  						TerminationMessagePolicy: "File",
 13404  						ImagePullPolicy:          "Always",
 13405  					}},
 13406  				},
 13407  			},
 13408  			old: core.Pod{
 13409  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13410  				Spec: core.PodSpec{
 13411  					Containers: []core.Container{{
 13412  						Name:                     "container",
 13413  						Image:                    "foo:V2",
 13414  						TerminationMessagePolicy: "File",
 13415  						ImagePullPolicy:          "Always",
 13416  					}},
 13417  				},
 13418  			},
 13419  			err:  "",
 13420  			test: "image change",
 13421  		}, {
 13422  			new: core.Pod{
 13423  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13424  				Spec: core.PodSpec{
 13425  					InitContainers: []core.Container{{
 13426  						Name:                     "container",
 13427  						Image:                    "foo:V1",
 13428  						TerminationMessagePolicy: "File",
 13429  						ImagePullPolicy:          "Always",
 13430  					}},
 13431  				},
 13432  			},
 13433  			old: core.Pod{
 13434  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13435  				Spec: core.PodSpec{
 13436  					InitContainers: []core.Container{{
 13437  						Name:                     "container",
 13438  						Image:                    "foo:V2",
 13439  						TerminationMessagePolicy: "File",
 13440  						ImagePullPolicy:          "Always",
 13441  					}},
 13442  				},
 13443  			},
 13444  			err:  "",
 13445  			test: "init container image change",
 13446  		}, {
 13447  			new: core.Pod{
 13448  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13449  				Spec: core.PodSpec{
 13450  					Containers: []core.Container{{
 13451  						Name:                     "container",
 13452  						TerminationMessagePolicy: "File",
 13453  						ImagePullPolicy:          "Always",
 13454  					}},
 13455  				},
 13456  			},
 13457  			old: core.Pod{
 13458  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13459  				Spec: core.PodSpec{
 13460  					Containers: []core.Container{{
 13461  						Name:                     "container",
 13462  						Image:                    "foo:V2",
 13463  						TerminationMessagePolicy: "File",
 13464  						ImagePullPolicy:          "Always",
 13465  					}},
 13466  				},
 13467  			},
 13468  			err:  "spec.containers[0].image",
 13469  			test: "image change to empty",
 13470  		}, {
 13471  			new: core.Pod{
 13472  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13473  				Spec: core.PodSpec{
 13474  					InitContainers: []core.Container{{
 13475  						Name:                     "container",
 13476  						TerminationMessagePolicy: "File",
 13477  						ImagePullPolicy:          "Always",
 13478  					}},
 13479  				},
 13480  			},
 13481  			old: core.Pod{
 13482  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13483  				Spec: core.PodSpec{
 13484  					InitContainers: []core.Container{{
 13485  						Name:                     "container",
 13486  						Image:                    "foo:V2",
 13487  						TerminationMessagePolicy: "File",
 13488  						ImagePullPolicy:          "Always",
 13489  					}},
 13490  				},
 13491  			},
 13492  			err:  "spec.initContainers[0].image",
 13493  			test: "init container image change to empty",
 13494  		}, {
 13495  			new: core.Pod{
 13496  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13497  				Spec: core.PodSpec{
 13498  					EphemeralContainers: []core.EphemeralContainer{{
 13499  						EphemeralContainerCommon: core.EphemeralContainerCommon{
 13500  							Name:  "ephemeral",
 13501  							Image: "busybox",
 13502  						},
 13503  					}},
 13504  				},
 13505  			},
 13506  			old: core.Pod{
 13507  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13508  				Spec:       core.PodSpec{},
 13509  			},
 13510  			err:  "Forbidden: pod updates may not change fields other than",
 13511  			test: "ephemeralContainer changes are not allowed via normal pod update",
 13512  		}, {
 13513  			new: core.Pod{
 13514  				Spec: core.PodSpec{},
 13515  			},
 13516  			old: core.Pod{
 13517  				Spec: core.PodSpec{},
 13518  			},
 13519  			err:  "",
 13520  			test: "activeDeadlineSeconds no change, nil",
 13521  		}, {
 13522  			new: core.Pod{
 13523  				Spec: core.PodSpec{
 13524  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13525  				},
 13526  			},
 13527  			old: core.Pod{
 13528  				Spec: core.PodSpec{
 13529  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13530  				},
 13531  			},
 13532  			err:  "",
 13533  			test: "activeDeadlineSeconds no change, set",
 13534  		}, {
 13535  			new: core.Pod{
 13536  				Spec: core.PodSpec{
 13537  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13538  				},
 13539  			},
 13540  			old:  core.Pod{},
 13541  			err:  "",
 13542  			test: "activeDeadlineSeconds change to positive from nil",
 13543  		}, {
 13544  			new: core.Pod{
 13545  				Spec: core.PodSpec{
 13546  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13547  				},
 13548  			},
 13549  			old: core.Pod{
 13550  				Spec: core.PodSpec{
 13551  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 13552  				},
 13553  			},
 13554  			err:  "",
 13555  			test: "activeDeadlineSeconds change to smaller positive",
 13556  		}, {
 13557  			new: core.Pod{
 13558  				Spec: core.PodSpec{
 13559  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 13560  				},
 13561  			},
 13562  			old: core.Pod{
 13563  				Spec: core.PodSpec{
 13564  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13565  				},
 13566  			},
 13567  			err:  "spec.activeDeadlineSeconds",
 13568  			test: "activeDeadlineSeconds change to larger positive",
 13569  		},
 13570  
 13571  		{
 13572  			new: core.Pod{
 13573  				Spec: core.PodSpec{
 13574  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 13575  				},
 13576  			},
 13577  			old:  core.Pod{},
 13578  			err:  "spec.activeDeadlineSeconds",
 13579  			test: "activeDeadlineSeconds change to negative from nil",
 13580  		}, {
 13581  			new: core.Pod{
 13582  				Spec: core.PodSpec{
 13583  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 13584  				},
 13585  			},
 13586  			old: core.Pod{
 13587  				Spec: core.PodSpec{
 13588  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13589  				},
 13590  			},
 13591  			err:  "spec.activeDeadlineSeconds",
 13592  			test: "activeDeadlineSeconds change to negative from positive",
 13593  		}, {
 13594  			new: core.Pod{
 13595  				Spec: core.PodSpec{
 13596  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 13597  				},
 13598  			},
 13599  			old: core.Pod{
 13600  				Spec: core.PodSpec{
 13601  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13602  				},
 13603  			},
 13604  			err:  "spec.activeDeadlineSeconds",
 13605  			test: "activeDeadlineSeconds change to zero from positive",
 13606  		}, {
 13607  			new: core.Pod{
 13608  				Spec: core.PodSpec{
 13609  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 13610  				},
 13611  			},
 13612  			old:  core.Pod{},
 13613  			err:  "spec.activeDeadlineSeconds",
 13614  			test: "activeDeadlineSeconds change to zero from nil",
 13615  		}, {
 13616  			new: core.Pod{},
 13617  			old: core.Pod{
 13618  				Spec: core.PodSpec{
 13619  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13620  				},
 13621  			},
 13622  			err:  "spec.activeDeadlineSeconds",
 13623  			test: "activeDeadlineSeconds change to nil from positive",
 13624  		}, {
 13625  			new: core.Pod{
 13626  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13627  				Spec: core.PodSpec{
 13628  					Containers: []core.Container{{
 13629  						Name:                     "container",
 13630  						TerminationMessagePolicy: "File",
 13631  						ImagePullPolicy:          "Always",
 13632  						Image:                    "foo:V2",
 13633  						Resources: core.ResourceRequirements{
 13634  							Limits: getResources("200m", "0", "1Gi"),
 13635  						},
 13636  					}},
 13637  				},
 13638  			},
 13639  			old: core.Pod{
 13640  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13641  				Spec: core.PodSpec{
 13642  					Containers: []core.Container{{
 13643  						Name:                     "container",
 13644  						TerminationMessagePolicy: "File",
 13645  						ImagePullPolicy:          "Always",
 13646  						Image:                    "foo:V2",
 13647  						Resources: core.ResourceRequirements{
 13648  							Limits: getResources("100m", "0", "1Gi"),
 13649  						},
 13650  					}},
 13651  				},
 13652  			},
 13653  			err:  "",
 13654  			test: "cpu limit change",
 13655  		}, {
 13656  			new: core.Pod{
 13657  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13658  				Spec: core.PodSpec{
 13659  					Containers: []core.Container{{
 13660  						Name:                     "container",
 13661  						TerminationMessagePolicy: "File",
 13662  						ImagePullPolicy:          "Always",
 13663  						Image:                    "foo:V1",
 13664  						Resources: core.ResourceRequirements{
 13665  							Limits: getResourceLimits("100m", "100Mi"),
 13666  						},
 13667  					}},
 13668  				},
 13669  			},
 13670  			old: core.Pod{
 13671  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13672  				Spec: core.PodSpec{
 13673  					Containers: []core.Container{{
 13674  						Name:                     "container",
 13675  						TerminationMessagePolicy: "File",
 13676  						ImagePullPolicy:          "Always",
 13677  						Image:                    "foo:V2",
 13678  						Resources: core.ResourceRequirements{
 13679  							Limits: getResourceLimits("100m", "200Mi"),
 13680  						},
 13681  					}},
 13682  				},
 13683  			},
 13684  			err:  "",
 13685  			test: "memory limit change",
 13686  		}, {
 13687  			new: core.Pod{
 13688  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13689  				Spec: core.PodSpec{
 13690  					Containers: []core.Container{{
 13691  						Name:                     "container",
 13692  						TerminationMessagePolicy: "File",
 13693  						ImagePullPolicy:          "Always",
 13694  						Image:                    "foo:V1",
 13695  						Resources: core.ResourceRequirements{
 13696  							Limits: getResources("100m", "100Mi", "1Gi"),
 13697  						},
 13698  					}},
 13699  				},
 13700  			},
 13701  			old: core.Pod{
 13702  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13703  				Spec: core.PodSpec{
 13704  					Containers: []core.Container{{
 13705  						Name:                     "container",
 13706  						TerminationMessagePolicy: "File",
 13707  						ImagePullPolicy:          "Always",
 13708  						Image:                    "foo:V2",
 13709  						Resources: core.ResourceRequirements{
 13710  							Limits: getResources("100m", "100Mi", "2Gi"),
 13711  						},
 13712  					}},
 13713  				},
 13714  			},
 13715  			err:  "Forbidden: pod updates may not change fields other than",
 13716  			test: "storage limit change",
 13717  		}, {
 13718  			new: core.Pod{
 13719  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13720  				Spec: core.PodSpec{
 13721  					Containers: []core.Container{{
 13722  						Name:                     "container",
 13723  						TerminationMessagePolicy: "File",
 13724  						ImagePullPolicy:          "Always",
 13725  						Image:                    "foo:V1",
 13726  						Resources: core.ResourceRequirements{
 13727  							Requests: getResourceLimits("100m", "0"),
 13728  						},
 13729  					}},
 13730  				},
 13731  			},
 13732  			old: core.Pod{
 13733  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13734  				Spec: core.PodSpec{
 13735  					Containers: []core.Container{{
 13736  						Name:                     "container",
 13737  						TerminationMessagePolicy: "File",
 13738  						ImagePullPolicy:          "Always",
 13739  						Image:                    "foo:V2",
 13740  						Resources: core.ResourceRequirements{
 13741  							Requests: getResourceLimits("200m", "0"),
 13742  						},
 13743  					}},
 13744  				},
 13745  			},
 13746  			err:  "",
 13747  			test: "cpu request change",
 13748  		}, {
 13749  			new: core.Pod{
 13750  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13751  				Spec: core.PodSpec{
 13752  					Containers: []core.Container{{
 13753  						Name:                     "container",
 13754  						TerminationMessagePolicy: "File",
 13755  						ImagePullPolicy:          "Always",
 13756  						Image:                    "foo:V1",
 13757  						Resources: core.ResourceRequirements{
 13758  							Requests: getResourceLimits("0", "200Mi"),
 13759  						},
 13760  					}},
 13761  				},
 13762  			},
 13763  			old: core.Pod{
 13764  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13765  				Spec: core.PodSpec{
 13766  					Containers: []core.Container{{
 13767  						Name:                     "container",
 13768  						TerminationMessagePolicy: "File",
 13769  						ImagePullPolicy:          "Always",
 13770  						Image:                    "foo:V2",
 13771  						Resources: core.ResourceRequirements{
 13772  							Requests: getResourceLimits("0", "100Mi"),
 13773  						},
 13774  					}},
 13775  				},
 13776  			},
 13777  			err:  "",
 13778  			test: "memory request change",
 13779  		}, {
 13780  			new: core.Pod{
 13781  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13782  				Spec: core.PodSpec{
 13783  					Containers: []core.Container{{
 13784  						Name:                     "container",
 13785  						TerminationMessagePolicy: "File",
 13786  						ImagePullPolicy:          "Always",
 13787  						Image:                    "foo:V1",
 13788  						Resources: core.ResourceRequirements{
 13789  							Requests: getResources("100m", "0", "2Gi"),
 13790  						},
 13791  					}},
 13792  				},
 13793  			},
 13794  			old: core.Pod{
 13795  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13796  				Spec: core.PodSpec{
 13797  					Containers: []core.Container{{
 13798  						Name:                     "container",
 13799  						TerminationMessagePolicy: "File",
 13800  						ImagePullPolicy:          "Always",
 13801  						Image:                    "foo:V2",
 13802  						Resources: core.ResourceRequirements{
 13803  							Requests: getResources("100m", "0", "1Gi"),
 13804  						},
 13805  					}},
 13806  				},
 13807  			},
 13808  			err:  "Forbidden: pod updates may not change fields other than",
 13809  			test: "storage request change",
 13810  		}, {
 13811  			new: core.Pod{
 13812  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13813  				Spec: core.PodSpec{
 13814  					Containers: []core.Container{{
 13815  						Name:                     "container",
 13816  						TerminationMessagePolicy: "File",
 13817  						ImagePullPolicy:          "Always",
 13818  						Image:                    "foo:V1",
 13819  						Resources: core.ResourceRequirements{
 13820  							Limits:   getResources("200m", "400Mi", "1Gi"),
 13821  							Requests: getResources("200m", "400Mi", "1Gi"),
 13822  						},
 13823  					}},
 13824  				},
 13825  			},
 13826  			old: core.Pod{
 13827  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13828  				Spec: core.PodSpec{
 13829  					Containers: []core.Container{{
 13830  						Name:                     "container",
 13831  						TerminationMessagePolicy: "File",
 13832  						ImagePullPolicy:          "Always",
 13833  						Image:                    "foo:V1",
 13834  						Resources: core.ResourceRequirements{
 13835  							Limits:   getResources("100m", "100Mi", "1Gi"),
 13836  							Requests: getResources("100m", "100Mi", "1Gi"),
 13837  						},
 13838  					}},
 13839  				},
 13840  			},
 13841  			err:  "",
 13842  			test: "Pod QoS unchanged, guaranteed -> guaranteed",
 13843  		}, {
 13844  			new: core.Pod{
 13845  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13846  				Spec: core.PodSpec{
 13847  					Containers: []core.Container{{
 13848  						Name:                     "container",
 13849  						TerminationMessagePolicy: "File",
 13850  						ImagePullPolicy:          "Always",
 13851  						Image:                    "foo:V1",
 13852  						Resources: core.ResourceRequirements{
 13853  							Limits:   getResources("200m", "200Mi", "2Gi"),
 13854  							Requests: getResources("100m", "100Mi", "1Gi"),
 13855  						},
 13856  					}},
 13857  				},
 13858  			},
 13859  			old: core.Pod{
 13860  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13861  				Spec: core.PodSpec{
 13862  					Containers: []core.Container{{
 13863  						Name:                     "container",
 13864  						TerminationMessagePolicy: "File",
 13865  						ImagePullPolicy:          "Always",
 13866  						Image:                    "foo:V1",
 13867  						Resources: core.ResourceRequirements{
 13868  							Limits:   getResources("400m", "400Mi", "2Gi"),
 13869  							Requests: getResources("200m", "200Mi", "1Gi"),
 13870  						},
 13871  					}},
 13872  				},
 13873  			},
 13874  			err:  "",
 13875  			test: "Pod QoS unchanged, burstable -> burstable",
 13876  		}, {
 13877  			new: core.Pod{
 13878  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13879  				Spec: core.PodSpec{
 13880  					Containers: []core.Container{{
 13881  						Name:                     "container",
 13882  						TerminationMessagePolicy: "File",
 13883  						ImagePullPolicy:          "Always",
 13884  						Image:                    "foo:V2",
 13885  						Resources: core.ResourceRequirements{
 13886  							Limits:   getResourceLimits("200m", "200Mi"),
 13887  							Requests: getResourceLimits("100m", "100Mi"),
 13888  						},
 13889  					}},
 13890  				},
 13891  			},
 13892  			old: core.Pod{
 13893  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13894  				Spec: core.PodSpec{
 13895  					Containers: []core.Container{{
 13896  						Name:                     "container",
 13897  						TerminationMessagePolicy: "File",
 13898  						ImagePullPolicy:          "Always",
 13899  						Image:                    "foo:V2",
 13900  						Resources: core.ResourceRequirements{
 13901  							Requests: getResourceLimits("100m", "100Mi"),
 13902  						},
 13903  					}},
 13904  				},
 13905  			},
 13906  			err:  "",
 13907  			test: "Pod QoS unchanged, burstable -> burstable, add limits",
 13908  		}, {
 13909  			new: core.Pod{
 13910  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13911  				Spec: core.PodSpec{
 13912  					Containers: []core.Container{{
 13913  						Name:                     "container",
 13914  						TerminationMessagePolicy: "File",
 13915  						ImagePullPolicy:          "Always",
 13916  						Image:                    "foo:V2",
 13917  						Resources: core.ResourceRequirements{
 13918  							Requests: getResourceLimits("100m", "100Mi"),
 13919  						},
 13920  					}},
 13921  				},
 13922  			},
 13923  			old: core.Pod{
 13924  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13925  				Spec: core.PodSpec{
 13926  					Containers: []core.Container{{
 13927  						Name:                     "container",
 13928  						TerminationMessagePolicy: "File",
 13929  						ImagePullPolicy:          "Always",
 13930  						Image:                    "foo:V2",
 13931  						Resources: core.ResourceRequirements{
 13932  							Limits:   getResourceLimits("200m", "200Mi"),
 13933  							Requests: getResourceLimits("100m", "100Mi"),
 13934  						},
 13935  					}},
 13936  				},
 13937  			},
 13938  			err:  "",
 13939  			test: "Pod QoS unchanged, burstable -> burstable, remove limits",
 13940  		}, {
 13941  			new: core.Pod{
 13942  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13943  				Spec: core.PodSpec{
 13944  					Containers: []core.Container{{
 13945  						Name:                     "container",
 13946  						TerminationMessagePolicy: "File",
 13947  						ImagePullPolicy:          "Always",
 13948  						Image:                    "foo:V2",
 13949  						Resources: core.ResourceRequirements{
 13950  							Limits:   getResources("400m", "", "1Gi"),
 13951  							Requests: getResources("300m", "", "1Gi"),
 13952  						},
 13953  					}},
 13954  				},
 13955  			},
 13956  			old: core.Pod{
 13957  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13958  				Spec: core.PodSpec{
 13959  					Containers: []core.Container{{
 13960  						Name:                     "container",
 13961  						TerminationMessagePolicy: "File",
 13962  						ImagePullPolicy:          "Always",
 13963  						Image:                    "foo:V2",
 13964  						Resources: core.ResourceRequirements{
 13965  							Limits: getResources("200m", "500Mi", "1Gi"),
 13966  						},
 13967  					}},
 13968  				},
 13969  			},
 13970  			err:  "",
 13971  			test: "Pod QoS unchanged, burstable -> burstable, add requests",
 13972  		}, {
 13973  			new: core.Pod{
 13974  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13975  				Spec: core.PodSpec{
 13976  					Containers: []core.Container{{
 13977  						Name:                     "container",
 13978  						TerminationMessagePolicy: "File",
 13979  						ImagePullPolicy:          "Always",
 13980  						Image:                    "foo:V2",
 13981  						Resources: core.ResourceRequirements{
 13982  							Limits: getResources("400m", "500Mi", "2Gi"),
 13983  						},
 13984  					}},
 13985  				},
 13986  			},
 13987  			old: core.Pod{
 13988  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13989  				Spec: core.PodSpec{
 13990  					Containers: []core.Container{{
 13991  						Name:                     "container",
 13992  						TerminationMessagePolicy: "File",
 13993  						ImagePullPolicy:          "Always",
 13994  						Image:                    "foo:V2",
 13995  						Resources: core.ResourceRequirements{
 13996  							Limits:   getResources("200m", "300Mi", "2Gi"),
 13997  							Requests: getResourceLimits("100m", "200Mi"),
 13998  						},
 13999  					}},
 14000  				},
 14001  			},
 14002  			err:  "",
 14003  			test: "Pod QoS unchanged, burstable -> burstable, remove requests",
 14004  		}, {
 14005  			new: core.Pod{
 14006  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14007  				Spec: core.PodSpec{
 14008  					Containers: []core.Container{{
 14009  						Name:                     "container",
 14010  						TerminationMessagePolicy: "File",
 14011  						ImagePullPolicy:          "Always",
 14012  						Image:                    "foo:V2",
 14013  						Resources: core.ResourceRequirements{
 14014  							Limits:   getResourceLimits("200m", "200Mi"),
 14015  							Requests: getResourceLimits("100m", "100Mi"),
 14016  						},
 14017  					}},
 14018  				},
 14019  			},
 14020  			old: core.Pod{
 14021  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14022  				Spec: core.PodSpec{
 14023  					Containers: []core.Container{{
 14024  						Name:                     "container",
 14025  						TerminationMessagePolicy: "File",
 14026  						ImagePullPolicy:          "Always",
 14027  						Image:                    "foo:V2",
 14028  						Resources: core.ResourceRequirements{
 14029  							Limits:   getResourceLimits("100m", "100Mi"),
 14030  							Requests: getResourceLimits("100m", "100Mi"),
 14031  						},
 14032  					}},
 14033  				},
 14034  			},
 14035  			err:  "Pod QoS is immutable",
 14036  			test: "Pod QoS change, guaranteed -> burstable",
 14037  		}, {
 14038  			new: core.Pod{
 14039  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14040  				Spec: core.PodSpec{
 14041  					Containers: []core.Container{{
 14042  						Name:                     "container",
 14043  						TerminationMessagePolicy: "File",
 14044  						ImagePullPolicy:          "Always",
 14045  						Image:                    "foo:V2",
 14046  						Resources: core.ResourceRequirements{
 14047  							Limits:   getResourceLimits("100m", "100Mi"),
 14048  							Requests: getResourceLimits("100m", "100Mi"),
 14049  						},
 14050  					}},
 14051  				},
 14052  			},
 14053  			old: core.Pod{
 14054  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14055  				Spec: core.PodSpec{
 14056  					Containers: []core.Container{{
 14057  						Name:                     "container",
 14058  						TerminationMessagePolicy: "File",
 14059  						ImagePullPolicy:          "Always",
 14060  						Image:                    "foo:V2",
 14061  						Resources: core.ResourceRequirements{
 14062  							Requests: getResourceLimits("100m", "100Mi"),
 14063  						},
 14064  					}},
 14065  				},
 14066  			},
 14067  			err:  "Pod QoS is immutable",
 14068  			test: "Pod QoS change, burstable -> guaranteed",
 14069  		}, {
 14070  			new: core.Pod{
 14071  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14072  				Spec: core.PodSpec{
 14073  					Containers: []core.Container{{
 14074  						Name:                     "container",
 14075  						TerminationMessagePolicy: "File",
 14076  						ImagePullPolicy:          "Always",
 14077  						Image:                    "foo:V2",
 14078  						Resources: core.ResourceRequirements{
 14079  							Limits:   getResourceLimits("200m", "200Mi"),
 14080  							Requests: getResourceLimits("100m", "100Mi"),
 14081  						},
 14082  					}},
 14083  				},
 14084  			},
 14085  			old: core.Pod{
 14086  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14087  				Spec: core.PodSpec{
 14088  					Containers: []core.Container{{
 14089  						Name:                     "container",
 14090  						TerminationMessagePolicy: "File",
 14091  						ImagePullPolicy:          "Always",
 14092  						Image:                    "foo:V2",
 14093  					}},
 14094  				},
 14095  			},
 14096  			err:  "Pod QoS is immutable",
 14097  			test: "Pod QoS change, besteffort -> burstable",
 14098  		}, {
 14099  			new: core.Pod{
 14100  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14101  				Spec: core.PodSpec{
 14102  					Containers: []core.Container{{
 14103  						Name:                     "container",
 14104  						TerminationMessagePolicy: "File",
 14105  						ImagePullPolicy:          "Always",
 14106  						Image:                    "foo:V2",
 14107  					}},
 14108  				},
 14109  			},
 14110  			old: core.Pod{
 14111  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14112  				Spec: core.PodSpec{
 14113  					Containers: []core.Container{{
 14114  						Name:                     "container",
 14115  						TerminationMessagePolicy: "File",
 14116  						ImagePullPolicy:          "Always",
 14117  						Image:                    "foo:V2",
 14118  						Resources: core.ResourceRequirements{
 14119  							Limits:   getResourceLimits("200m", "200Mi"),
 14120  							Requests: getResourceLimits("100m", "100Mi"),
 14121  						},
 14122  					}},
 14123  				},
 14124  			},
 14125  			err:  "Pod QoS is immutable",
 14126  			test: "Pod QoS change, burstable -> besteffort",
 14127  		}, {
 14128  			new: core.Pod{
 14129  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14130  				Spec: core.PodSpec{
 14131  					Containers: []core.Container{{
 14132  						Image: "foo:V1",
 14133  					}},
 14134  					SecurityContext: &core.PodSecurityContext{
 14135  						FSGroupChangePolicy: &validfsGroupChangePolicy,
 14136  					},
 14137  				},
 14138  			},
 14139  			old: core.Pod{
 14140  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14141  				Spec: core.PodSpec{
 14142  					Containers: []core.Container{{
 14143  						Image: "foo:V2",
 14144  					}},
 14145  					SecurityContext: &core.PodSecurityContext{
 14146  						FSGroupChangePolicy: nil,
 14147  					},
 14148  				},
 14149  			},
 14150  			err:  "spec: Forbidden: pod updates may not change fields",
 14151  			test: "fsGroupChangePolicy change",
 14152  		}, {
 14153  			new: core.Pod{
 14154  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14155  				Spec: core.PodSpec{
 14156  					Containers: []core.Container{{
 14157  						Image: "foo:V1",
 14158  						Ports: []core.ContainerPort{
 14159  							{HostPort: 8080, ContainerPort: 80},
 14160  						},
 14161  					}},
 14162  				},
 14163  			},
 14164  			old: core.Pod{
 14165  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14166  				Spec: core.PodSpec{
 14167  					Containers: []core.Container{{
 14168  						Image: "foo:V2",
 14169  						Ports: []core.ContainerPort{
 14170  							{HostPort: 8000, ContainerPort: 80},
 14171  						},
 14172  					}},
 14173  				},
 14174  			},
 14175  			err:  "spec: Forbidden: pod updates may not change fields",
 14176  			test: "port change",
 14177  		}, {
 14178  			new: core.Pod{
 14179  				ObjectMeta: metav1.ObjectMeta{
 14180  					Name: "foo",
 14181  					Labels: map[string]string{
 14182  						"foo": "bar",
 14183  					},
 14184  				},
 14185  			},
 14186  			old: core.Pod{
 14187  				ObjectMeta: metav1.ObjectMeta{
 14188  					Name: "foo",
 14189  					Labels: map[string]string{
 14190  						"Bar": "foo",
 14191  					},
 14192  				},
 14193  			},
 14194  			err:  "",
 14195  			test: "bad label change",
 14196  		}, {
 14197  			new: core.Pod{
 14198  				ObjectMeta: metav1.ObjectMeta{
 14199  					Name: "foo",
 14200  				},
 14201  				Spec: core.PodSpec{
 14202  					NodeName:    "node1",
 14203  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 14204  				},
 14205  			},
 14206  			old: core.Pod{
 14207  				ObjectMeta: metav1.ObjectMeta{
 14208  					Name: "foo",
 14209  				},
 14210  				Spec: core.PodSpec{
 14211  					NodeName:    "node1",
 14212  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14213  				},
 14214  			},
 14215  			err:  "spec.tolerations: Forbidden",
 14216  			test: "existing toleration value modified in pod spec updates",
 14217  		}, {
 14218  			new: core.Pod{
 14219  				ObjectMeta: metav1.ObjectMeta{
 14220  					Name: "foo",
 14221  				},
 14222  				Spec: core.PodSpec{
 14223  					NodeName:    "node1",
 14224  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}},
 14225  				},
 14226  			},
 14227  			old: core.Pod{
 14228  				ObjectMeta: metav1.ObjectMeta{
 14229  					Name: "foo",
 14230  				},
 14231  				Spec: core.PodSpec{
 14232  					NodeName:    "node1",
 14233  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14234  				},
 14235  			},
 14236  			err:  "spec.tolerations: Forbidden",
 14237  			test: "existing toleration value modified in pod spec updates with modified tolerationSeconds",
 14238  		}, {
 14239  			new: core.Pod{
 14240  				ObjectMeta: metav1.ObjectMeta{
 14241  					Name: "foo",
 14242  				},
 14243  				Spec: core.PodSpec{
 14244  					NodeName:    "node1",
 14245  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14246  				},
 14247  			},
 14248  			old: core.Pod{
 14249  				ObjectMeta: metav1.ObjectMeta{
 14250  					Name: "foo",
 14251  				},
 14252  				Spec: core.PodSpec{
 14253  					NodeName:    "node1",
 14254  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}},
 14255  				}},
 14256  			err:  "",
 14257  			test: "modified tolerationSeconds in existing toleration value in pod spec updates",
 14258  		}, {
 14259  			new: core.Pod{
 14260  				ObjectMeta: metav1.ObjectMeta{
 14261  					Name: "foo",
 14262  				},
 14263  				Spec: core.PodSpec{
 14264  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 14265  				},
 14266  			},
 14267  			old: core.Pod{
 14268  				ObjectMeta: metav1.ObjectMeta{
 14269  					Name: "foo",
 14270  				},
 14271  				Spec: core.PodSpec{
 14272  					NodeName:    "",
 14273  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14274  				},
 14275  			},
 14276  			err:  "spec.tolerations: Forbidden",
 14277  			test: "toleration modified in updates to an unscheduled pod",
 14278  		}, {
 14279  			new: core.Pod{
 14280  				ObjectMeta: metav1.ObjectMeta{
 14281  					Name: "foo",
 14282  				},
 14283  				Spec: core.PodSpec{
 14284  					NodeName:    "node1",
 14285  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14286  				},
 14287  			},
 14288  			old: core.Pod{
 14289  				ObjectMeta: metav1.ObjectMeta{
 14290  					Name: "foo",
 14291  				},
 14292  				Spec: core.PodSpec{
 14293  					NodeName:    "node1",
 14294  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14295  				},
 14296  			},
 14297  			err:  "",
 14298  			test: "tolerations unmodified in updates to a scheduled pod",
 14299  		}, {
 14300  			new: core.Pod{
 14301  				ObjectMeta: metav1.ObjectMeta{
 14302  					Name: "foo",
 14303  				},
 14304  				Spec: core.PodSpec{
 14305  					NodeName: "node1",
 14306  					Tolerations: []core.Toleration{
 14307  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 14308  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]},
 14309  					},
 14310  				}},
 14311  			old: core.Pod{
 14312  				ObjectMeta: metav1.ObjectMeta{
 14313  					Name: "foo",
 14314  				},
 14315  				Spec: core.PodSpec{
 14316  					NodeName:    "node1",
 14317  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14318  				},
 14319  			},
 14320  			err:  "",
 14321  			test: "added valid new toleration to existing tolerations in pod spec updates",
 14322  		}, {
 14323  			new: core.Pod{
 14324  				ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{
 14325  					NodeName: "node1",
 14326  					Tolerations: []core.Toleration{
 14327  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 14328  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]},
 14329  					},
 14330  				}},
 14331  			old: core.Pod{
 14332  				ObjectMeta: metav1.ObjectMeta{
 14333  					Name: "foo",
 14334  				},
 14335  				Spec: core.PodSpec{
 14336  					NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14337  				}},
 14338  			err:  "spec.tolerations[1].effect",
 14339  			test: "added invalid new toleration to existing tolerations in pod spec updates",
 14340  		}, {
 14341  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14342  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
 14343  			err:  "spec: Forbidden: pod updates may not change fields",
 14344  			test: "removed nodeName from pod spec",
 14345  		}, {
 14346  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 14347  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14348  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14349  			test: "added mirror pod annotation",
 14350  		}, {
 14351  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14352  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 14353  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14354  			test: "removed mirror pod annotation",
 14355  		}, {
 14356  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}},
 14357  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}},
 14358  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14359  			test: "changed mirror pod annotation",
 14360  		}, {
 14361  			new: core.Pod{
 14362  				ObjectMeta: metav1.ObjectMeta{
 14363  					Name: "foo",
 14364  				},
 14365  				Spec: core.PodSpec{
 14366  					NodeName:          "node1",
 14367  					PriorityClassName: "bar-priority",
 14368  				},
 14369  			},
 14370  			old: core.Pod{
 14371  				ObjectMeta: metav1.ObjectMeta{
 14372  					Name: "foo",
 14373  				},
 14374  				Spec: core.PodSpec{
 14375  					NodeName:          "node1",
 14376  					PriorityClassName: "foo-priority",
 14377  				},
 14378  			},
 14379  			err:  "spec: Forbidden: pod updates",
 14380  			test: "changed priority class name",
 14381  		}, {
 14382  			new: core.Pod{
 14383  				ObjectMeta: metav1.ObjectMeta{
 14384  					Name: "foo",
 14385  				},
 14386  				Spec: core.PodSpec{
 14387  					NodeName:          "node1",
 14388  					PriorityClassName: "",
 14389  				},
 14390  			},
 14391  			old: core.Pod{
 14392  				ObjectMeta: metav1.ObjectMeta{
 14393  					Name: "foo",
 14394  				},
 14395  				Spec: core.PodSpec{
 14396  					NodeName:          "node1",
 14397  					PriorityClassName: "foo-priority",
 14398  				},
 14399  			},
 14400  			err:  "spec: Forbidden: pod updates",
 14401  			test: "removed priority class name",
 14402  		}, {
 14403  			new: core.Pod{
 14404  				ObjectMeta: metav1.ObjectMeta{
 14405  					Name: "foo",
 14406  				},
 14407  				Spec: core.PodSpec{
 14408  					TerminationGracePeriodSeconds: utilpointer.Int64(1),
 14409  				},
 14410  			},
 14411  			old: core.Pod{
 14412  				ObjectMeta: metav1.ObjectMeta{
 14413  					Name: "foo",
 14414  				},
 14415  				Spec: core.PodSpec{
 14416  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 14417  				},
 14418  			},
 14419  			err:  "",
 14420  			test: "update termination grace period seconds",
 14421  		}, {
 14422  			new: core.Pod{
 14423  				ObjectMeta: metav1.ObjectMeta{
 14424  					Name: "foo",
 14425  				},
 14426  				Spec: core.PodSpec{
 14427  					TerminationGracePeriodSeconds: utilpointer.Int64(0),
 14428  				},
 14429  			},
 14430  			old: core.Pod{
 14431  				ObjectMeta: metav1.ObjectMeta{
 14432  					Name: "foo",
 14433  				},
 14434  				Spec: core.PodSpec{
 14435  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 14436  				},
 14437  			},
 14438  			err:  "spec: Forbidden: pod updates",
 14439  			test: "update termination grace period seconds not 1",
 14440  		}, {
 14441  			new: core.Pod{
 14442  				ObjectMeta: metav1.ObjectMeta{
 14443  					Name: "foo",
 14444  				},
 14445  				Spec: core.PodSpec{
 14446  					OS:              &core.PodOS{Name: core.Windows},
 14447  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14448  				},
 14449  			},
 14450  			old: core.Pod{
 14451  				ObjectMeta: metav1.ObjectMeta{
 14452  					Name: "foo",
 14453  				},
 14454  				Spec: core.PodSpec{
 14455  					OS:              &core.PodOS{Name: core.Linux},
 14456  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14457  				},
 14458  			},
 14459  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14460  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set",
 14461  		}, {
 14462  			new: core.Pod{
 14463  				ObjectMeta: metav1.ObjectMeta{
 14464  					Name: "foo",
 14465  				},
 14466  				Spec: core.PodSpec{
 14467  					OS:              &core.PodOS{Name: core.Windows},
 14468  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14469  				},
 14470  			},
 14471  			old: core.Pod{
 14472  				ObjectMeta: metav1.ObjectMeta{
 14473  					Name: "foo",
 14474  				},
 14475  				Spec: core.PodSpec{
 14476  					OS:              &core.PodOS{Name: core.Linux},
 14477  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14478  				},
 14479  			},
 14480  			err:  "spec.securityContext.seLinuxOptions: Forbidden",
 14481  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well",
 14482  		}, {
 14483  			new: core.Pod{
 14484  				ObjectMeta: metav1.ObjectMeta{
 14485  					Name: "foo",
 14486  				},
 14487  				Spec: core.PodSpec{
 14488  					OS: &core.PodOS{Name: "dummy"},
 14489  				},
 14490  			},
 14491  			old: core.Pod{
 14492  				ObjectMeta: metav1.ObjectMeta{
 14493  					Name: "foo",
 14494  				},
 14495  				Spec: core.PodSpec{},
 14496  			},
 14497  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14498  			test: "invalid PodOS update, IdentifyPodOS featuregate set",
 14499  		}, {
 14500  			new: core.Pod{
 14501  				ObjectMeta: metav1.ObjectMeta{
 14502  					Name: "foo",
 14503  				},
 14504  				Spec: core.PodSpec{
 14505  					OS: &core.PodOS{Name: core.Linux},
 14506  				},
 14507  			},
 14508  			old: core.Pod{
 14509  				ObjectMeta: metav1.ObjectMeta{
 14510  					Name: "foo",
 14511  				},
 14512  				Spec: core.PodSpec{
 14513  					OS: &core.PodOS{Name: core.Windows},
 14514  				},
 14515  			},
 14516  			err:  "Forbidden: pod updates may not change fields other than ",
 14517  			test: "update pod spec OS to a valid value, featuregate disabled",
 14518  		}, {
 14519  			new: core.Pod{
 14520  				Spec: core.PodSpec{
 14521  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14522  				},
 14523  			},
 14524  			old:  core.Pod{},
 14525  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
 14526  			test: "update pod spec schedulingGates: add new scheduling gate",
 14527  		}, {
 14528  			new: core.Pod{
 14529  				Spec: core.PodSpec{
 14530  					SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
 14531  				},
 14532  			},
 14533  			old: core.Pod{
 14534  				Spec: core.PodSpec{
 14535  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14536  				},
 14537  			},
 14538  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
 14539  			test: "update pod spec schedulingGates: mutating an existing scheduling gate",
 14540  		}, {
 14541  			new: core.Pod{
 14542  				Spec: core.PodSpec{
 14543  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14544  				},
 14545  			},
 14546  			old: core.Pod{
 14547  				Spec: core.PodSpec{
 14548  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
 14549  				},
 14550  			},
 14551  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
 14552  			test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
 14553  		}, {
 14554  			new: core.Pod{},
 14555  			old: core.Pod{
 14556  				Spec: core.PodSpec{
 14557  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14558  				},
 14559  			},
 14560  			err:  "",
 14561  			test: "update pod spec schedulingGates: legal deletion",
 14562  		}, {
 14563  			old: core.Pod{
 14564  				Spec: core.PodSpec{
 14565  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14566  				},
 14567  			},
 14568  			new: core.Pod{
 14569  				Spec: core.PodSpec{
 14570  					NodeSelector: map[string]string{
 14571  						"foo": "bar",
 14572  					},
 14573  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14574  				},
 14575  			},
 14576  			test: "adding node selector is allowed for gated pods",
 14577  		}, {
 14578  			old: core.Pod{
 14579  				Spec: core.PodSpec{
 14580  					NodeSelector: map[string]string{
 14581  						"foo": "bar",
 14582  					},
 14583  				},
 14584  			},
 14585  			new: core.Pod{
 14586  				Spec: core.PodSpec{
 14587  					NodeSelector: map[string]string{
 14588  						"foo":  "bar",
 14589  						"foo2": "bar2",
 14590  					},
 14591  				},
 14592  			},
 14593  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14594  			test: "adding node selector is not allowed for non-gated pods",
 14595  		}, {
 14596  			old: core.Pod{
 14597  				Spec: core.PodSpec{
 14598  					NodeSelector: map[string]string{
 14599  						"foo": "bar",
 14600  					},
 14601  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14602  				},
 14603  			},
 14604  			new: core.Pod{
 14605  				Spec: core.PodSpec{
 14606  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14607  				},
 14608  			},
 14609  			err:  "spec.nodeSelector: Invalid value:",
 14610  			test: "removing node selector is not allowed for gated pods",
 14611  		}, {
 14612  			old: core.Pod{
 14613  				Spec: core.PodSpec{
 14614  					NodeSelector: map[string]string{
 14615  						"foo": "bar",
 14616  					},
 14617  				},
 14618  			},
 14619  			new:  core.Pod{},
 14620  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14621  			test: "removing node selector is not allowed for non-gated pods",
 14622  		}, {
 14623  			old: core.Pod{
 14624  				Spec: core.PodSpec{
 14625  					NodeSelector: map[string]string{
 14626  						"foo": "bar",
 14627  					},
 14628  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14629  				},
 14630  			},
 14631  			new: core.Pod{
 14632  				Spec: core.PodSpec{
 14633  					NodeSelector: map[string]string{
 14634  						"foo":  "bar",
 14635  						"foo2": "bar2",
 14636  					},
 14637  				},
 14638  			},
 14639  			test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added",
 14640  		}, {
 14641  			old: core.Pod{
 14642  				Spec: core.PodSpec{
 14643  					NodeSelector: map[string]string{
 14644  						"foo": "bar",
 14645  					},
 14646  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14647  				},
 14648  			},
 14649  			new: core.Pod{
 14650  				Spec: core.PodSpec{
 14651  					NodeSelector: map[string]string{
 14652  						"foo": "new value",
 14653  					},
 14654  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14655  				},
 14656  			},
 14657  			err:  "spec.nodeSelector: Invalid value:",
 14658  			test: "modifying value of existing node selector is not allowed",
 14659  		}, {
 14660  			old: core.Pod{
 14661  				Spec: core.PodSpec{
 14662  					Affinity: &core.Affinity{
 14663  						NodeAffinity: &core.NodeAffinity{
 14664  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14665  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14666  									MatchExpressions: []core.NodeSelectorRequirement{{
 14667  										Key:      "expr",
 14668  										Operator: core.NodeSelectorOpIn,
 14669  										Values:   []string{"foo"},
 14670  									}},
 14671  								}},
 14672  							},
 14673  						},
 14674  					},
 14675  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14676  				},
 14677  			},
 14678  			new: core.Pod{
 14679  				Spec: core.PodSpec{
 14680  					Affinity: &core.Affinity{
 14681  						NodeAffinity: &core.NodeAffinity{
 14682  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14683  								// Add 1 MatchExpression and 1 MatchField.
 14684  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14685  									MatchExpressions: []core.NodeSelectorRequirement{{
 14686  										Key:      "expr",
 14687  										Operator: core.NodeSelectorOpIn,
 14688  										Values:   []string{"foo"},
 14689  									}, {
 14690  										Key:      "expr2",
 14691  										Operator: core.NodeSelectorOpIn,
 14692  										Values:   []string{"foo2"},
 14693  									}},
 14694  									MatchFields: []core.NodeSelectorRequirement{{
 14695  										Key:      "metadata.name",
 14696  										Operator: core.NodeSelectorOpIn,
 14697  										Values:   []string{"foo"},
 14698  									}},
 14699  								}},
 14700  							},
 14701  						},
 14702  					},
 14703  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14704  				},
 14705  			},
 14706  			test: "addition to nodeAffinity is allowed for gated pods",
 14707  		}, {
 14708  			old: core.Pod{
 14709  				Spec: core.PodSpec{
 14710  					Affinity: &core.Affinity{
 14711  						NodeAffinity: &core.NodeAffinity{
 14712  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14713  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14714  									MatchExpressions: []core.NodeSelectorRequirement{{
 14715  										Key:      "expr",
 14716  										Operator: core.NodeSelectorOpIn,
 14717  										Values:   []string{"foo"},
 14718  									}},
 14719  								}},
 14720  							},
 14721  						},
 14722  					},
 14723  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14724  				},
 14725  			},
 14726  			new: core.Pod{
 14727  				Spec: core.PodSpec{
 14728  					Affinity: &core.Affinity{
 14729  						NodeAffinity: &core.NodeAffinity{},
 14730  					},
 14731  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14732  				},
 14733  			},
 14734  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 14735  			test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated",
 14736  		}, {
 14737  			old: core.Pod{
 14738  				Spec: core.PodSpec{
 14739  					Affinity: &core.Affinity{
 14740  						NodeAffinity: &core.NodeAffinity{
 14741  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14742  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14743  									MatchExpressions: []core.NodeSelectorRequirement{{
 14744  										Key:      "expr",
 14745  										Operator: core.NodeSelectorOpIn,
 14746  										Values:   []string{"foo"},
 14747  									}},
 14748  								}},
 14749  							},
 14750  						},
 14751  					},
 14752  				},
 14753  			},
 14754  			new: core.Pod{
 14755  				Spec: core.PodSpec{
 14756  					Affinity: &core.Affinity{
 14757  						NodeAffinity: &core.NodeAffinity{
 14758  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14759  								// Add 1 MatchExpression and 1 MatchField.
 14760  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14761  									MatchExpressions: []core.NodeSelectorRequirement{{
 14762  										Key:      "expr",
 14763  										Operator: core.NodeSelectorOpIn,
 14764  										Values:   []string{"foo"},
 14765  									}, {
 14766  										Key:      "expr2",
 14767  										Operator: core.NodeSelectorOpIn,
 14768  										Values:   []string{"foo2"},
 14769  									}},
 14770  									MatchFields: []core.NodeSelectorRequirement{{
 14771  										Key:      "metadata.name",
 14772  										Operator: core.NodeSelectorOpIn,
 14773  										Values:   []string{"foo"},
 14774  									}},
 14775  								}},
 14776  							},
 14777  						},
 14778  					},
 14779  				},
 14780  			},
 14781  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14782  			test: "addition to nodeAffinity is not allowed for non-gated pods",
 14783  		}, {
 14784  			old: core.Pod{
 14785  				Spec: core.PodSpec{
 14786  					Affinity: &core.Affinity{
 14787  						NodeAffinity: &core.NodeAffinity{
 14788  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14789  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14790  									MatchExpressions: []core.NodeSelectorRequirement{{
 14791  										Key:      "expr",
 14792  										Operator: core.NodeSelectorOpIn,
 14793  										Values:   []string{"foo"},
 14794  									}},
 14795  								}},
 14796  							},
 14797  						},
 14798  					},
 14799  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14800  				},
 14801  			},
 14802  			new: core.Pod{
 14803  				Spec: core.PodSpec{
 14804  					Affinity: &core.Affinity{
 14805  						NodeAffinity: &core.NodeAffinity{
 14806  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14807  								// Add 1 MatchExpression and 1 MatchField.
 14808  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14809  									MatchExpressions: []core.NodeSelectorRequirement{{
 14810  										Key:      "expr",
 14811  										Operator: core.NodeSelectorOpIn,
 14812  										Values:   []string{"foo"},
 14813  									}, {
 14814  										Key:      "expr2",
 14815  										Operator: core.NodeSelectorOpIn,
 14816  										Values:   []string{"foo2"},
 14817  									}},
 14818  									MatchFields: []core.NodeSelectorRequirement{{
 14819  										Key:      "metadata.name",
 14820  										Operator: core.NodeSelectorOpIn,
 14821  										Values:   []string{"foo"},
 14822  									}},
 14823  								}},
 14824  							},
 14825  						},
 14826  					},
 14827  				},
 14828  			},
 14829  			test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs",
 14830  		}, {
 14831  			old: core.Pod{
 14832  				Spec: core.PodSpec{
 14833  					Affinity: &core.Affinity{
 14834  						NodeAffinity: &core.NodeAffinity{
 14835  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14836  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14837  									MatchExpressions: []core.NodeSelectorRequirement{{
 14838  										Key:      "expr",
 14839  										Operator: core.NodeSelectorOpIn,
 14840  										Values:   []string{"foo"},
 14841  									}},
 14842  								}},
 14843  							},
 14844  						},
 14845  					},
 14846  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14847  				},
 14848  			},
 14849  			new: core.Pod{
 14850  				Spec: core.PodSpec{
 14851  					Affinity: &core.Affinity{
 14852  						NodeAffinity: &core.NodeAffinity{
 14853  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14854  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14855  									MatchFields: []core.NodeSelectorRequirement{{
 14856  										Key:      "metadata.name",
 14857  										Operator: core.NodeSelectorOpIn,
 14858  										Values:   []string{"foo"},
 14859  									}},
 14860  								}},
 14861  							},
 14862  						},
 14863  					},
 14864  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14865  				},
 14866  			},
 14867  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14868  			test: "nodeAffinity deletion from MatchExpressions not allowed",
 14869  		}, {
 14870  			old: core.Pod{
 14871  				Spec: core.PodSpec{
 14872  					Affinity: &core.Affinity{
 14873  						NodeAffinity: &core.NodeAffinity{
 14874  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14875  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14876  									MatchExpressions: []core.NodeSelectorRequirement{{
 14877  										Key:      "expr",
 14878  										Operator: core.NodeSelectorOpIn,
 14879  										Values:   []string{"foo"},
 14880  									}},
 14881  									MatchFields: []core.NodeSelectorRequirement{{
 14882  										Key:      "metadata.name",
 14883  										Operator: core.NodeSelectorOpIn,
 14884  										Values:   []string{"foo"},
 14885  									}},
 14886  								}},
 14887  							},
 14888  						},
 14889  					},
 14890  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14891  				},
 14892  			},
 14893  			new: core.Pod{
 14894  				Spec: core.PodSpec{
 14895  					Affinity: &core.Affinity{
 14896  						NodeAffinity: &core.NodeAffinity{
 14897  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14898  								// Add 1 MatchExpression and 1 MatchField.
 14899  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14900  									MatchExpressions: []core.NodeSelectorRequirement{{
 14901  										Key:      "expr",
 14902  										Operator: core.NodeSelectorOpIn,
 14903  										Values:   []string{"foo"},
 14904  									}},
 14905  								}},
 14906  							},
 14907  						},
 14908  					},
 14909  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14910  				},
 14911  			},
 14912  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14913  			test: "nodeAffinity deletion from MatchFields not allowed",
 14914  		}, {
 14915  			old: core.Pod{
 14916  				Spec: core.PodSpec{
 14917  					Affinity: &core.Affinity{
 14918  						NodeAffinity: &core.NodeAffinity{
 14919  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14920  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14921  									MatchExpressions: []core.NodeSelectorRequirement{{
 14922  										Key:      "expr",
 14923  										Operator: core.NodeSelectorOpIn,
 14924  										Values:   []string{"foo"},
 14925  									}},
 14926  									MatchFields: []core.NodeSelectorRequirement{{
 14927  										Key:      "metadata.name",
 14928  										Operator: core.NodeSelectorOpIn,
 14929  										Values:   []string{"foo"},
 14930  									}},
 14931  								}},
 14932  							},
 14933  						},
 14934  					},
 14935  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14936  				},
 14937  			},
 14938  			new: core.Pod{
 14939  				Spec: core.PodSpec{
 14940  					Affinity: &core.Affinity{
 14941  						NodeAffinity: &core.NodeAffinity{
 14942  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14943  								// Add 1 MatchExpression and 1 MatchField.
 14944  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14945  									MatchExpressions: []core.NodeSelectorRequirement{{
 14946  										Key:      "expr",
 14947  										Operator: core.NodeSelectorOpIn,
 14948  										Values:   []string{"bar"},
 14949  									}},
 14950  									MatchFields: []core.NodeSelectorRequirement{{
 14951  										Key:      "metadata.name",
 14952  										Operator: core.NodeSelectorOpIn,
 14953  										Values:   []string{"foo"},
 14954  									}},
 14955  								}},
 14956  							},
 14957  						},
 14958  					},
 14959  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14960  				},
 14961  			},
 14962  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14963  			test: "nodeAffinity modification of item in MatchExpressions not allowed",
 14964  		}, {
 14965  			old: core.Pod{
 14966  				Spec: core.PodSpec{
 14967  					Affinity: &core.Affinity{
 14968  						NodeAffinity: &core.NodeAffinity{
 14969  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14970  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14971  									MatchExpressions: []core.NodeSelectorRequirement{{
 14972  										Key:      "expr",
 14973  										Operator: core.NodeSelectorOpIn,
 14974  										Values:   []string{"foo"},
 14975  									}},
 14976  									MatchFields: []core.NodeSelectorRequirement{{
 14977  										Key:      "metadata.name",
 14978  										Operator: core.NodeSelectorOpIn,
 14979  										Values:   []string{"foo"},
 14980  									}},
 14981  								}},
 14982  							},
 14983  						},
 14984  					},
 14985  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14986  				},
 14987  			},
 14988  			new: core.Pod{
 14989  				Spec: core.PodSpec{
 14990  					Affinity: &core.Affinity{
 14991  						NodeAffinity: &core.NodeAffinity{
 14992  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14993  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14994  									MatchExpressions: []core.NodeSelectorRequirement{{
 14995  										Key:      "expr",
 14996  										Operator: core.NodeSelectorOpIn,
 14997  										Values:   []string{"foo"},
 14998  									}},
 14999  									MatchFields: []core.NodeSelectorRequirement{{
 15000  										Key:      "metadata.name",
 15001  										Operator: core.NodeSelectorOpIn,
 15002  										Values:   []string{"bar"},
 15003  									}},
 15004  								}},
 15005  							},
 15006  						},
 15007  					},
 15008  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15009  				},
 15010  			},
 15011  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15012  			test: "nodeAffinity modification of item in MatchFields not allowed",
 15013  		}, {
 15014  			old: core.Pod{
 15015  				Spec: core.PodSpec{
 15016  					Affinity: &core.Affinity{
 15017  						NodeAffinity: &core.NodeAffinity{
 15018  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15019  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15020  									MatchExpressions: []core.NodeSelectorRequirement{{
 15021  										Key:      "expr",
 15022  										Operator: core.NodeSelectorOpIn,
 15023  										Values:   []string{"foo"},
 15024  									}},
 15025  									MatchFields: []core.NodeSelectorRequirement{{
 15026  										Key:      "metadata.name",
 15027  										Operator: core.NodeSelectorOpIn,
 15028  										Values:   []string{"foo"},
 15029  									}},
 15030  								}},
 15031  							},
 15032  						},
 15033  					},
 15034  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15035  				},
 15036  			},
 15037  			new: core.Pod{
 15038  				Spec: core.PodSpec{
 15039  					Affinity: &core.Affinity{
 15040  						NodeAffinity: &core.NodeAffinity{
 15041  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15042  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15043  									MatchExpressions: []core.NodeSelectorRequirement{{
 15044  										Key:      "expr",
 15045  										Operator: core.NodeSelectorOpIn,
 15046  										Values:   []string{"foo"},
 15047  									}},
 15048  									MatchFields: []core.NodeSelectorRequirement{{
 15049  										Key:      "metadata.name",
 15050  										Operator: core.NodeSelectorOpIn,
 15051  										Values:   []string{"bar"},
 15052  									}},
 15053  								}, {
 15054  									MatchExpressions: []core.NodeSelectorRequirement{{
 15055  										Key:      "expr",
 15056  										Operator: core.NodeSelectorOpIn,
 15057  										Values:   []string{"foo2"},
 15058  									}},
 15059  									MatchFields: []core.NodeSelectorRequirement{{
 15060  										Key:      "metadata.name",
 15061  										Operator: core.NodeSelectorOpIn,
 15062  										Values:   []string{"bar2"},
 15063  									}},
 15064  								}},
 15065  							},
 15066  						},
 15067  					},
 15068  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15069  				},
 15070  			},
 15071  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 15072  			test: "nodeSelectorTerms addition on gated pod should fail",
 15073  		}, {
 15074  			old: core.Pod{
 15075  				Spec: core.PodSpec{
 15076  					Affinity: &core.Affinity{
 15077  						NodeAffinity: &core.NodeAffinity{
 15078  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15079  								Weight: 1.0,
 15080  								Preference: core.NodeSelectorTerm{
 15081  									MatchExpressions: []core.NodeSelectorRequirement{{
 15082  										Key:      "expr",
 15083  										Operator: core.NodeSelectorOpIn,
 15084  										Values:   []string{"foo"},
 15085  									}},
 15086  								},
 15087  							}},
 15088  						},
 15089  					},
 15090  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15091  				},
 15092  			},
 15093  			new: core.Pod{
 15094  				Spec: core.PodSpec{
 15095  					Affinity: &core.Affinity{
 15096  						NodeAffinity: &core.NodeAffinity{
 15097  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15098  								Weight: 1.0,
 15099  								Preference: core.NodeSelectorTerm{
 15100  									MatchExpressions: []core.NodeSelectorRequirement{{
 15101  										Key:      "expr",
 15102  										Operator: core.NodeSelectorOpIn,
 15103  										Values:   []string{"foo2"},
 15104  									}},
 15105  								},
 15106  							}},
 15107  						},
 15108  					},
 15109  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15110  				},
 15111  			},
 15112  			test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods",
 15113  		}, {
 15114  			old: core.Pod{
 15115  				Spec: core.PodSpec{
 15116  					Affinity: &core.Affinity{
 15117  						NodeAffinity: &core.NodeAffinity{
 15118  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15119  								Weight: 1.0,
 15120  								Preference: core.NodeSelectorTerm{
 15121  									MatchExpressions: []core.NodeSelectorRequirement{{
 15122  										Key:      "expr",
 15123  										Operator: core.NodeSelectorOpIn,
 15124  										Values:   []string{"foo"},
 15125  									}},
 15126  								},
 15127  							}},
 15128  						},
 15129  					},
 15130  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15131  				},
 15132  			},
 15133  			new: core.Pod{
 15134  				Spec: core.PodSpec{
 15135  					Affinity: &core.Affinity{
 15136  						NodeAffinity: &core.NodeAffinity{
 15137  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15138  								Weight: 1.0,
 15139  								Preference: core.NodeSelectorTerm{
 15140  									MatchExpressions: []core.NodeSelectorRequirement{{
 15141  										Key:      "expr",
 15142  										Operator: core.NodeSelectorOpIn,
 15143  										Values:   []string{"foo"},
 15144  									}, {
 15145  										Key:      "expr2",
 15146  										Operator: core.NodeSelectorOpIn,
 15147  										Values:   []string{"foo2"},
 15148  									}},
 15149  									MatchFields: []core.NodeSelectorRequirement{{
 15150  										Key:      "metadata.name",
 15151  										Operator: core.NodeSelectorOpIn,
 15152  										Values:   []string{"bar"},
 15153  									}},
 15154  								},
 15155  							}},
 15156  						},
 15157  					},
 15158  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15159  				},
 15160  			},
 15161  			test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods",
 15162  		}, {
 15163  			old: core.Pod{
 15164  				Spec: core.PodSpec{
 15165  					Affinity: &core.Affinity{
 15166  						NodeAffinity: &core.NodeAffinity{
 15167  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15168  								Weight: 1.0,
 15169  								Preference: core.NodeSelectorTerm{
 15170  									MatchExpressions: []core.NodeSelectorRequirement{{
 15171  										Key:      "expr",
 15172  										Operator: core.NodeSelectorOpIn,
 15173  										Values:   []string{"foo"},
 15174  									}},
 15175  								},
 15176  							}},
 15177  						},
 15178  					},
 15179  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15180  				},
 15181  			},
 15182  			new: core.Pod{
 15183  				Spec: core.PodSpec{
 15184  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15185  				},
 15186  			},
 15187  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 15188  		}, {
 15189  			old: core.Pod{
 15190  				Spec: core.PodSpec{
 15191  					Affinity: &core.Affinity{
 15192  						NodeAffinity: &core.NodeAffinity{
 15193  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15194  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15195  									MatchExpressions: []core.NodeSelectorRequirement{{
 15196  										Key:      "expr",
 15197  										Operator: core.NodeSelectorOpIn,
 15198  										Values:   []string{"foo"},
 15199  									}},
 15200  								}},
 15201  							},
 15202  						},
 15203  					},
 15204  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15205  				},
 15206  			},
 15207  			new: core.Pod{
 15208  				Spec: core.PodSpec{
 15209  					Affinity:        &core.Affinity{},
 15210  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15211  				},
 15212  			},
 15213  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 15214  			test: "new node affinity is nil",
 15215  		}, {
 15216  			old: core.Pod{
 15217  				Spec: core.PodSpec{
 15218  					Affinity: &core.Affinity{
 15219  						NodeAffinity: &core.NodeAffinity{
 15220  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15221  								Weight: 1.0,
 15222  								Preference: core.NodeSelectorTerm{
 15223  									MatchExpressions: []core.NodeSelectorRequirement{{
 15224  										Key:      "expr",
 15225  										Operator: core.NodeSelectorOpIn,
 15226  										Values:   []string{"foo"},
 15227  									}},
 15228  								},
 15229  							}},
 15230  						},
 15231  					},
 15232  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15233  				},
 15234  			},
 15235  			new: core.Pod{
 15236  				Spec: core.PodSpec{
 15237  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15238  				},
 15239  			},
 15240  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 15241  		}, {
 15242  			old: core.Pod{
 15243  				Spec: core.PodSpec{
 15244  					Affinity: &core.Affinity{
 15245  						NodeAffinity: &core.NodeAffinity{
 15246  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15247  								NodeSelectorTerms: []core.NodeSelectorTerm{
 15248  									{},
 15249  								},
 15250  							},
 15251  						},
 15252  					},
 15253  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15254  				},
 15255  			},
 15256  			new: core.Pod{
 15257  				Spec: core.PodSpec{
 15258  					Affinity: &core.Affinity{
 15259  						NodeAffinity: &core.NodeAffinity{
 15260  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15261  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15262  									MatchExpressions: []core.NodeSelectorRequirement{{
 15263  										Key:      "expr",
 15264  										Operator: core.NodeSelectorOpIn,
 15265  										Values:   []string{"foo"},
 15266  									}},
 15267  								}},
 15268  							},
 15269  						},
 15270  					},
 15271  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15272  				},
 15273  			},
 15274  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15275  			test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)",
 15276  		}, {
 15277  			old: core.Pod{
 15278  				Spec: core.PodSpec{
 15279  					Affinity:        nil,
 15280  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15281  				},
 15282  			},
 15283  			new: core.Pod{
 15284  				Spec: core.PodSpec{
 15285  					Affinity: &core.Affinity{
 15286  						NodeAffinity: &core.NodeAffinity{
 15287  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15288  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15289  									MatchExpressions: []core.NodeSelectorRequirement{{
 15290  										Key:      "expr",
 15291  										Operator: core.NodeSelectorOpIn,
 15292  										Values:   []string{"foo"},
 15293  									}},
 15294  								}},
 15295  							},
 15296  						},
 15297  					},
 15298  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15299  				},
 15300  			},
 15301  			test: "nil affinity can be mutated for gated pods",
 15302  		},
 15303  		{
 15304  			old: core.Pod{
 15305  				Spec: core.PodSpec{
 15306  					Affinity:        nil,
 15307  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15308  				},
 15309  			},
 15310  			new: core.Pod{
 15311  				Spec: core.PodSpec{
 15312  					Affinity: &core.Affinity{
 15313  						NodeAffinity: &core.NodeAffinity{
 15314  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15315  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15316  									MatchExpressions: []core.NodeSelectorRequirement{{
 15317  										Key:      "expr",
 15318  										Operator: core.NodeSelectorOpIn,
 15319  										Values:   []string{"foo"},
 15320  									}},
 15321  								}},
 15322  							},
 15323  						},
 15324  						PodAffinity: &core.PodAffinity{
 15325  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 15326  								{
 15327  									TopologyKey: "foo",
 15328  									LabelSelector: &metav1.LabelSelector{
 15329  										MatchLabels: map[string]string{"foo": "bar"},
 15330  									},
 15331  								},
 15332  							},
 15333  						},
 15334  					},
 15335  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15336  				},
 15337  			},
 15338  			err:  "pod updates may not change fields other than",
 15339  			test: "the podAffinity cannot be updated on gated pods",
 15340  		},
 15341  		{
 15342  			old: core.Pod{
 15343  				Spec: core.PodSpec{
 15344  					Affinity:        nil,
 15345  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15346  				},
 15347  			},
 15348  			new: core.Pod{
 15349  				Spec: core.PodSpec{
 15350  					Affinity: &core.Affinity{
 15351  						NodeAffinity: &core.NodeAffinity{
 15352  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15353  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15354  									MatchExpressions: []core.NodeSelectorRequirement{{
 15355  										Key:      "expr",
 15356  										Operator: core.NodeSelectorOpIn,
 15357  										Values:   []string{"foo"},
 15358  									}},
 15359  								}},
 15360  							},
 15361  						},
 15362  						PodAntiAffinity: &core.PodAntiAffinity{
 15363  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 15364  								{
 15365  									TopologyKey: "foo",
 15366  									LabelSelector: &metav1.LabelSelector{
 15367  										MatchLabels: map[string]string{"foo": "bar"},
 15368  									},
 15369  								},
 15370  							},
 15371  						},
 15372  					},
 15373  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15374  				},
 15375  			},
 15376  			err:  "pod updates may not change fields other than",
 15377  			test: "the podAntiAffinity cannot be updated on gated pods",
 15378  		},
 15379  	}
 15380  	for _, test := range tests {
 15381  		test.new.ObjectMeta.ResourceVersion = "1"
 15382  		test.old.ObjectMeta.ResourceVersion = "1"
 15383  
 15384  		// set required fields if old and new match and have no opinion on the value
 15385  		if test.new.Name == "" && test.old.Name == "" {
 15386  			test.new.Name = "name"
 15387  			test.old.Name = "name"
 15388  		}
 15389  		if test.new.Namespace == "" && test.old.Namespace == "" {
 15390  			test.new.Namespace = "namespace"
 15391  			test.old.Namespace = "namespace"
 15392  		}
 15393  		if test.new.Spec.Containers == nil && test.old.Spec.Containers == nil {
 15394  			test.new.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 15395  			test.old.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 15396  		}
 15397  		if len(test.new.Spec.DNSPolicy) == 0 && len(test.old.Spec.DNSPolicy) == 0 {
 15398  			test.new.Spec.DNSPolicy = core.DNSClusterFirst
 15399  			test.old.Spec.DNSPolicy = core.DNSClusterFirst
 15400  		}
 15401  		if len(test.new.Spec.RestartPolicy) == 0 && len(test.old.Spec.RestartPolicy) == 0 {
 15402  			test.new.Spec.RestartPolicy = "Always"
 15403  			test.old.Spec.RestartPolicy = "Always"
 15404  		}
 15405  
 15406  		errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
 15407  		if test.err == "" {
 15408  			if len(errs) != 0 {
 15409  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 15410  			}
 15411  		} else {
 15412  			if len(errs) == 0 {
 15413  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 15414  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 15415  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 15416  			}
 15417  		}
 15418  	}
 15419  }
 15420  
 15421  func TestValidatePodStatusUpdate(t *testing.T) {
 15422  	tests := []struct {
 15423  		new  core.Pod
 15424  		old  core.Pod
 15425  		err  string
 15426  		test string
 15427  	}{{
 15428  		core.Pod{
 15429  			ObjectMeta: metav1.ObjectMeta{
 15430  				Name: "foo",
 15431  			},
 15432  			Spec: core.PodSpec{
 15433  				NodeName: "node1",
 15434  			},
 15435  			Status: core.PodStatus{
 15436  				NominatedNodeName: "node1",
 15437  			},
 15438  		},
 15439  		core.Pod{
 15440  			ObjectMeta: metav1.ObjectMeta{
 15441  				Name: "foo",
 15442  			},
 15443  			Spec: core.PodSpec{
 15444  				NodeName: "node1",
 15445  			},
 15446  			Status: core.PodStatus{},
 15447  		},
 15448  		"",
 15449  		"removed nominatedNodeName",
 15450  	}, {
 15451  		core.Pod{
 15452  			ObjectMeta: metav1.ObjectMeta{
 15453  				Name: "foo",
 15454  			},
 15455  			Spec: core.PodSpec{
 15456  				NodeName: "node1",
 15457  			},
 15458  		},
 15459  		core.Pod{
 15460  			ObjectMeta: metav1.ObjectMeta{
 15461  				Name: "foo",
 15462  			},
 15463  			Spec: core.PodSpec{
 15464  				NodeName: "node1",
 15465  			},
 15466  			Status: core.PodStatus{
 15467  				NominatedNodeName: "node1",
 15468  			},
 15469  		},
 15470  		"",
 15471  		"add valid nominatedNodeName",
 15472  	}, {
 15473  		core.Pod{
 15474  			ObjectMeta: metav1.ObjectMeta{
 15475  				Name: "foo",
 15476  			},
 15477  			Spec: core.PodSpec{
 15478  				NodeName: "node1",
 15479  			},
 15480  			Status: core.PodStatus{
 15481  				NominatedNodeName: "Node1",
 15482  			},
 15483  		},
 15484  		core.Pod{
 15485  			ObjectMeta: metav1.ObjectMeta{
 15486  				Name: "foo",
 15487  			},
 15488  			Spec: core.PodSpec{
 15489  				NodeName: "node1",
 15490  			},
 15491  		},
 15492  		"nominatedNodeName",
 15493  		"Add invalid nominatedNodeName",
 15494  	}, {
 15495  		core.Pod{
 15496  			ObjectMeta: metav1.ObjectMeta{
 15497  				Name: "foo",
 15498  			},
 15499  			Spec: core.PodSpec{
 15500  				NodeName: "node1",
 15501  			},
 15502  			Status: core.PodStatus{
 15503  				NominatedNodeName: "node1",
 15504  			},
 15505  		},
 15506  		core.Pod{
 15507  			ObjectMeta: metav1.ObjectMeta{
 15508  				Name: "foo",
 15509  			},
 15510  			Spec: core.PodSpec{
 15511  				NodeName: "node1",
 15512  			},
 15513  			Status: core.PodStatus{
 15514  				NominatedNodeName: "node2",
 15515  			},
 15516  		},
 15517  		"",
 15518  		"Update nominatedNodeName",
 15519  	}, {
 15520  		core.Pod{
 15521  			ObjectMeta: metav1.ObjectMeta{
 15522  				Name: "foo",
 15523  			},
 15524  			Status: core.PodStatus{
 15525  				InitContainerStatuses: []core.ContainerStatus{{
 15526  					ContainerID: "docker://numbers",
 15527  					Image:       "alpine",
 15528  					Name:        "init",
 15529  					Ready:       false,
 15530  					Started:     proto.Bool(false),
 15531  					State: core.ContainerState{
 15532  						Waiting: &core.ContainerStateWaiting{
 15533  							Reason: "PodInitializing",
 15534  						},
 15535  					},
 15536  				}},
 15537  				ContainerStatuses: []core.ContainerStatus{{
 15538  					ContainerID: "docker://numbers",
 15539  					Image:       "nginx:alpine",
 15540  					Name:        "main",
 15541  					Ready:       false,
 15542  					Started:     proto.Bool(false),
 15543  					State: core.ContainerState{
 15544  						Waiting: &core.ContainerStateWaiting{
 15545  							Reason: "PodInitializing",
 15546  						},
 15547  					},
 15548  				}},
 15549  			},
 15550  		},
 15551  		core.Pod{
 15552  			ObjectMeta: metav1.ObjectMeta{
 15553  				Name: "foo",
 15554  			},
 15555  		},
 15556  		"",
 15557  		"Container statuses pending",
 15558  	}, {
 15559  		core.Pod{
 15560  			ObjectMeta: metav1.ObjectMeta{
 15561  				Name: "foo",
 15562  			},
 15563  			Status: core.PodStatus{
 15564  				InitContainerStatuses: []core.ContainerStatus{{
 15565  					ContainerID: "docker://numbers",
 15566  					Image:       "alpine",
 15567  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15568  					Name:        "init",
 15569  					Ready:       true,
 15570  					State: core.ContainerState{
 15571  						Terminated: &core.ContainerStateTerminated{
 15572  							ContainerID: "docker://numbers",
 15573  							Reason:      "Completed",
 15574  						},
 15575  					},
 15576  				}},
 15577  				ContainerStatuses: []core.ContainerStatus{{
 15578  					ContainerID: "docker://numbers",
 15579  					Image:       "nginx:alpine",
 15580  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15581  					Name:        "nginx",
 15582  					Ready:       true,
 15583  					Started:     proto.Bool(true),
 15584  					State: core.ContainerState{
 15585  						Running: &core.ContainerStateRunning{
 15586  							StartedAt: metav1.NewTime(time.Now()),
 15587  						},
 15588  					},
 15589  				}},
 15590  			},
 15591  		},
 15592  		core.Pod{
 15593  			ObjectMeta: metav1.ObjectMeta{
 15594  				Name: "foo",
 15595  			},
 15596  			Status: core.PodStatus{
 15597  				InitContainerStatuses: []core.ContainerStatus{{
 15598  					ContainerID: "docker://numbers",
 15599  					Image:       "alpine",
 15600  					Name:        "init",
 15601  					Ready:       false,
 15602  					State: core.ContainerState{
 15603  						Waiting: &core.ContainerStateWaiting{
 15604  							Reason: "PodInitializing",
 15605  						},
 15606  					},
 15607  				}},
 15608  				ContainerStatuses: []core.ContainerStatus{{
 15609  					ContainerID: "docker://numbers",
 15610  					Image:       "nginx:alpine",
 15611  					Name:        "main",
 15612  					Ready:       false,
 15613  					Started:     proto.Bool(false),
 15614  					State: core.ContainerState{
 15615  						Waiting: &core.ContainerStateWaiting{
 15616  							Reason: "PodInitializing",
 15617  						},
 15618  					},
 15619  				}},
 15620  			},
 15621  		},
 15622  		"",
 15623  		"Container statuses running",
 15624  	}, {
 15625  		core.Pod{
 15626  			ObjectMeta: metav1.ObjectMeta{
 15627  				Name: "foo",
 15628  			},
 15629  			Status: core.PodStatus{
 15630  				ContainerStatuses: []core.ContainerStatus{{
 15631  					ContainerID: "docker://numbers",
 15632  					Image:       "nginx:alpine",
 15633  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15634  					Name:        "nginx",
 15635  					Ready:       true,
 15636  					Started:     proto.Bool(true),
 15637  					State: core.ContainerState{
 15638  						Running: &core.ContainerStateRunning{
 15639  							StartedAt: metav1.NewTime(time.Now()),
 15640  						},
 15641  					},
 15642  				}},
 15643  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15644  					ContainerID: "docker://numbers",
 15645  					Image:       "busybox",
 15646  					Name:        "debug",
 15647  					Ready:       false,
 15648  					State: core.ContainerState{
 15649  						Waiting: &core.ContainerStateWaiting{
 15650  							Reason: "PodInitializing",
 15651  						},
 15652  					},
 15653  				}},
 15654  			},
 15655  		},
 15656  		core.Pod{
 15657  			ObjectMeta: metav1.ObjectMeta{
 15658  				Name: "foo",
 15659  			},
 15660  			Status: core.PodStatus{
 15661  				ContainerStatuses: []core.ContainerStatus{{
 15662  					ContainerID: "docker://numbers",
 15663  					Image:       "nginx:alpine",
 15664  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15665  					Name:        "nginx",
 15666  					Ready:       true,
 15667  					Started:     proto.Bool(true),
 15668  					State: core.ContainerState{
 15669  						Running: &core.ContainerStateRunning{
 15670  							StartedAt: metav1.NewTime(time.Now()),
 15671  						},
 15672  					},
 15673  				}},
 15674  			},
 15675  		},
 15676  		"",
 15677  		"Container statuses add ephemeral container",
 15678  	}, {
 15679  		core.Pod{
 15680  			ObjectMeta: metav1.ObjectMeta{
 15681  				Name: "foo",
 15682  			},
 15683  			Status: core.PodStatus{
 15684  				ContainerStatuses: []core.ContainerStatus{{
 15685  					ContainerID: "docker://numbers",
 15686  					Image:       "nginx:alpine",
 15687  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15688  					Name:        "nginx",
 15689  					Ready:       true,
 15690  					Started:     proto.Bool(true),
 15691  					State: core.ContainerState{
 15692  						Running: &core.ContainerStateRunning{
 15693  							StartedAt: metav1.NewTime(time.Now()),
 15694  						},
 15695  					},
 15696  				}},
 15697  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15698  					ContainerID: "docker://numbers",
 15699  					Image:       "busybox",
 15700  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15701  					Name:        "debug",
 15702  					Ready:       false,
 15703  					State: core.ContainerState{
 15704  						Running: &core.ContainerStateRunning{
 15705  							StartedAt: metav1.NewTime(time.Now()),
 15706  						},
 15707  					},
 15708  				}},
 15709  			},
 15710  		},
 15711  		core.Pod{
 15712  			ObjectMeta: metav1.ObjectMeta{
 15713  				Name: "foo",
 15714  			},
 15715  			Status: core.PodStatus{
 15716  				ContainerStatuses: []core.ContainerStatus{{
 15717  					ContainerID: "docker://numbers",
 15718  					Image:       "nginx:alpine",
 15719  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15720  					Name:        "nginx",
 15721  					Ready:       true,
 15722  					Started:     proto.Bool(true),
 15723  					State: core.ContainerState{
 15724  						Running: &core.ContainerStateRunning{
 15725  							StartedAt: metav1.NewTime(time.Now()),
 15726  						},
 15727  					},
 15728  				}},
 15729  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15730  					ContainerID: "docker://numbers",
 15731  					Image:       "busybox",
 15732  					Name:        "debug",
 15733  					Ready:       false,
 15734  					State: core.ContainerState{
 15735  						Waiting: &core.ContainerStateWaiting{
 15736  							Reason: "PodInitializing",
 15737  						},
 15738  					},
 15739  				}},
 15740  			},
 15741  		},
 15742  		"",
 15743  		"Container statuses ephemeral container running",
 15744  	}, {
 15745  		core.Pod{
 15746  			ObjectMeta: metav1.ObjectMeta{
 15747  				Name: "foo",
 15748  			},
 15749  			Status: core.PodStatus{
 15750  				ContainerStatuses: []core.ContainerStatus{{
 15751  					ContainerID: "docker://numbers",
 15752  					Image:       "nginx:alpine",
 15753  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15754  					Name:        "nginx",
 15755  					Ready:       true,
 15756  					Started:     proto.Bool(true),
 15757  					State: core.ContainerState{
 15758  						Running: &core.ContainerStateRunning{
 15759  							StartedAt: metav1.NewTime(time.Now()),
 15760  						},
 15761  					},
 15762  				}},
 15763  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15764  					ContainerID: "docker://numbers",
 15765  					Image:       "busybox",
 15766  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15767  					Name:        "debug",
 15768  					Ready:       false,
 15769  					State: core.ContainerState{
 15770  						Terminated: &core.ContainerStateTerminated{
 15771  							ContainerID: "docker://numbers",
 15772  							Reason:      "Completed",
 15773  							StartedAt:   metav1.NewTime(time.Now()),
 15774  							FinishedAt:  metav1.NewTime(time.Now()),
 15775  						},
 15776  					},
 15777  				}},
 15778  			},
 15779  		},
 15780  		core.Pod{
 15781  			ObjectMeta: metav1.ObjectMeta{
 15782  				Name: "foo",
 15783  			},
 15784  			Status: core.PodStatus{
 15785  				ContainerStatuses: []core.ContainerStatus{{
 15786  					ContainerID: "docker://numbers",
 15787  					Image:       "nginx:alpine",
 15788  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15789  					Name:        "nginx",
 15790  					Ready:       true,
 15791  					Started:     proto.Bool(true),
 15792  					State: core.ContainerState{
 15793  						Running: &core.ContainerStateRunning{
 15794  							StartedAt: metav1.NewTime(time.Now()),
 15795  						},
 15796  					},
 15797  				}},
 15798  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15799  					ContainerID: "docker://numbers",
 15800  					Image:       "busybox",
 15801  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15802  					Name:        "debug",
 15803  					Ready:       false,
 15804  					State: core.ContainerState{
 15805  						Running: &core.ContainerStateRunning{
 15806  							StartedAt: metav1.NewTime(time.Now()),
 15807  						},
 15808  					},
 15809  				}},
 15810  			},
 15811  		},
 15812  		"",
 15813  		"Container statuses ephemeral container exited",
 15814  	}, {
 15815  		core.Pod{
 15816  			ObjectMeta: metav1.ObjectMeta{
 15817  				Name: "foo",
 15818  			},
 15819  			Status: core.PodStatus{
 15820  				InitContainerStatuses: []core.ContainerStatus{{
 15821  					ContainerID: "docker://numbers",
 15822  					Image:       "alpine",
 15823  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15824  					Name:        "init",
 15825  					Ready:       true,
 15826  					State: core.ContainerState{
 15827  						Terminated: &core.ContainerStateTerminated{
 15828  							ContainerID: "docker://numbers",
 15829  							Reason:      "Completed",
 15830  						},
 15831  					},
 15832  				}},
 15833  				ContainerStatuses: []core.ContainerStatus{{
 15834  					ContainerID: "docker://numbers",
 15835  					Image:       "nginx:alpine",
 15836  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15837  					Name:        "nginx",
 15838  					Ready:       true,
 15839  					Started:     proto.Bool(true),
 15840  					State: core.ContainerState{
 15841  						Terminated: &core.ContainerStateTerminated{
 15842  							ContainerID: "docker://numbers",
 15843  							Reason:      "Completed",
 15844  							StartedAt:   metav1.NewTime(time.Now()),
 15845  							FinishedAt:  metav1.NewTime(time.Now()),
 15846  						},
 15847  					},
 15848  				}},
 15849  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15850  					ContainerID: "docker://numbers",
 15851  					Image:       "busybox",
 15852  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15853  					Name:        "debug",
 15854  					Ready:       false,
 15855  					State: core.ContainerState{
 15856  						Terminated: &core.ContainerStateTerminated{
 15857  							ContainerID: "docker://numbers",
 15858  							Reason:      "Completed",
 15859  							StartedAt:   metav1.NewTime(time.Now()),
 15860  							FinishedAt:  metav1.NewTime(time.Now()),
 15861  						},
 15862  					},
 15863  				}},
 15864  			},
 15865  		},
 15866  		core.Pod{
 15867  			ObjectMeta: metav1.ObjectMeta{
 15868  				Name: "foo",
 15869  			},
 15870  			Status: core.PodStatus{
 15871  				InitContainerStatuses: []core.ContainerStatus{{
 15872  					ContainerID: "docker://numbers",
 15873  					Image:       "alpine",
 15874  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15875  					Name:        "init",
 15876  					Ready:       true,
 15877  					State: core.ContainerState{
 15878  						Terminated: &core.ContainerStateTerminated{
 15879  							ContainerID: "docker://numbers",
 15880  							Reason:      "Completed",
 15881  						},
 15882  					},
 15883  				}},
 15884  				ContainerStatuses: []core.ContainerStatus{{
 15885  					ContainerID: "docker://numbers",
 15886  					Image:       "nginx:alpine",
 15887  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15888  					Name:        "nginx",
 15889  					Ready:       true,
 15890  					Started:     proto.Bool(true),
 15891  					State: core.ContainerState{
 15892  						Running: &core.ContainerStateRunning{
 15893  							StartedAt: metav1.NewTime(time.Now()),
 15894  						},
 15895  					},
 15896  				}},
 15897  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15898  					ContainerID: "docker://numbers",
 15899  					Image:       "busybox",
 15900  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15901  					Name:        "debug",
 15902  					Ready:       false,
 15903  					State: core.ContainerState{
 15904  						Running: &core.ContainerStateRunning{
 15905  							StartedAt: metav1.NewTime(time.Now()),
 15906  						},
 15907  					},
 15908  				}},
 15909  			},
 15910  		},
 15911  		"",
 15912  		"Container statuses all containers terminated",
 15913  	}, {
 15914  		core.Pod{
 15915  			ObjectMeta: metav1.ObjectMeta{
 15916  				Name: "foo",
 15917  			},
 15918  			Status: core.PodStatus{
 15919  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15920  					{Name: "no-such-claim", ResourceClaimName: utilpointer.String("my-claim")},
 15921  				},
 15922  			},
 15923  		},
 15924  		core.Pod{
 15925  			ObjectMeta: metav1.ObjectMeta{
 15926  				Name: "foo",
 15927  			},
 15928  		},
 15929  		"status.resourceClaimStatuses[0].name: Invalid value: \"no-such-claim\": must match the name of an entry in `spec.resourceClaims`",
 15930  		"Non-existent PodResourceClaim",
 15931  	}, {
 15932  		core.Pod{
 15933  			ObjectMeta: metav1.ObjectMeta{
 15934  				Name: "foo",
 15935  			},
 15936  			Spec: core.PodSpec{
 15937  				ResourceClaims: []core.PodResourceClaim{
 15938  					{Name: "my-claim"},
 15939  				},
 15940  			},
 15941  			Status: core.PodStatus{
 15942  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15943  					{Name: "my-claim", ResourceClaimName: utilpointer.String("%$!#")},
 15944  				},
 15945  			},
 15946  		},
 15947  		core.Pod{
 15948  			ObjectMeta: metav1.ObjectMeta{
 15949  				Name: "foo",
 15950  			},
 15951  			Spec: core.PodSpec{
 15952  				ResourceClaims: []core.PodResourceClaim{
 15953  					{Name: "my-claim"},
 15954  				},
 15955  			},
 15956  		},
 15957  		`status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`,
 15958  		"Invalid ResourceClaim name",
 15959  	}, {
 15960  		core.Pod{
 15961  			ObjectMeta: metav1.ObjectMeta{
 15962  				Name: "foo",
 15963  			},
 15964  			Spec: core.PodSpec{
 15965  				ResourceClaims: []core.PodResourceClaim{
 15966  					{Name: "my-claim"},
 15967  					{Name: "my-other-claim"},
 15968  				},
 15969  			},
 15970  			Status: core.PodStatus{
 15971  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15972  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 15973  					{Name: "my-other-claim", ResourceClaimName: nil},
 15974  					{Name: "my-other-claim", ResourceClaimName: nil},
 15975  				},
 15976  			},
 15977  		},
 15978  		core.Pod{
 15979  			ObjectMeta: metav1.ObjectMeta{
 15980  				Name: "foo",
 15981  			},
 15982  			Spec: core.PodSpec{
 15983  				ResourceClaims: []core.PodResourceClaim{
 15984  					{Name: "my-claim"},
 15985  				},
 15986  			},
 15987  		},
 15988  		`status.resourceClaimStatuses[2].name: Duplicate value: "my-other-claim"`,
 15989  		"Duplicate ResourceClaimStatuses.Name",
 15990  	}, {
 15991  		core.Pod{
 15992  			ObjectMeta: metav1.ObjectMeta{
 15993  				Name: "foo",
 15994  			},
 15995  			Spec: core.PodSpec{
 15996  				ResourceClaims: []core.PodResourceClaim{
 15997  					{Name: "my-claim"},
 15998  					{Name: "my-other-claim"},
 15999  				},
 16000  			},
 16001  			Status: core.PodStatus{
 16002  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 16003  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 16004  					{Name: "my-other-claim", ResourceClaimName: nil},
 16005  				},
 16006  			},
 16007  		},
 16008  		core.Pod{
 16009  			ObjectMeta: metav1.ObjectMeta{
 16010  				Name: "foo",
 16011  			},
 16012  			Spec: core.PodSpec{
 16013  				ResourceClaims: []core.PodResourceClaim{
 16014  					{Name: "my-claim"},
 16015  				},
 16016  			},
 16017  		},
 16018  		"",
 16019  		"ResourceClaimStatuses okay",
 16020  	}, {
 16021  		core.Pod{
 16022  			ObjectMeta: metav1.ObjectMeta{
 16023  				Name: "foo",
 16024  			},
 16025  			Spec: core.PodSpec{
 16026  				InitContainers: []core.Container{
 16027  					{
 16028  						Name: "init",
 16029  					},
 16030  				},
 16031  				Containers: []core.Container{
 16032  					{
 16033  						Name: "nginx",
 16034  					},
 16035  				},
 16036  			},
 16037  			Status: core.PodStatus{
 16038  				InitContainerStatuses: []core.ContainerStatus{{
 16039  					ContainerID: "docker://numbers",
 16040  					Image:       "alpine",
 16041  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16042  					Name:        "init",
 16043  					Ready:       true,
 16044  					State: core.ContainerState{
 16045  						Running: &core.ContainerStateRunning{
 16046  							StartedAt: metav1.NewTime(time.Now()),
 16047  						},
 16048  					},
 16049  				}},
 16050  				ContainerStatuses: []core.ContainerStatus{{
 16051  					ContainerID: "docker://numbers",
 16052  					Image:       "nginx:alpine",
 16053  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16054  					Name:        "nginx",
 16055  					Ready:       true,
 16056  					Started:     proto.Bool(true),
 16057  					State: core.ContainerState{
 16058  						Running: &core.ContainerStateRunning{
 16059  							StartedAt: metav1.NewTime(time.Now()),
 16060  						},
 16061  					},
 16062  				}},
 16063  			},
 16064  		},
 16065  		core.Pod{
 16066  			ObjectMeta: metav1.ObjectMeta{
 16067  				Name: "foo",
 16068  			},
 16069  			Spec: core.PodSpec{
 16070  				InitContainers: []core.Container{
 16071  					{
 16072  						Name: "init",
 16073  					},
 16074  				},
 16075  				Containers: []core.Container{
 16076  					{
 16077  						Name: "nginx",
 16078  					},
 16079  				},
 16080  				RestartPolicy: core.RestartPolicyNever,
 16081  			},
 16082  			Status: core.PodStatus{
 16083  				InitContainerStatuses: []core.ContainerStatus{{
 16084  					ContainerID: "docker://numbers",
 16085  					Image:       "alpine",
 16086  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16087  					Name:        "init",
 16088  					Ready:       false,
 16089  					State: core.ContainerState{
 16090  						Terminated: &core.ContainerStateTerminated{
 16091  							ContainerID: "docker://numbers",
 16092  							Reason:      "Completed",
 16093  						},
 16094  					},
 16095  				}},
 16096  				ContainerStatuses: []core.ContainerStatus{{
 16097  					ContainerID: "docker://numbers",
 16098  					Image:       "nginx:alpine",
 16099  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16100  					Name:        "nginx",
 16101  					Ready:       true,
 16102  					Started:     proto.Bool(true),
 16103  					State: core.ContainerState{
 16104  						Running: &core.ContainerStateRunning{
 16105  							StartedAt: metav1.NewTime(time.Now()),
 16106  						},
 16107  					},
 16108  				}},
 16109  			},
 16110  		},
 16111  		`status.initContainerStatuses[0].state: Forbidden: may not be transitioned to non-terminated state`,
 16112  		"init container cannot restart if RestartPolicyNever",
 16113  	}, {
 16114  		core.Pod{
 16115  			ObjectMeta: metav1.ObjectMeta{
 16116  				Name: "foo",
 16117  			},
 16118  			Spec: core.PodSpec{
 16119  				InitContainers: []core.Container{
 16120  					{
 16121  						Name:          "restartable-init",
 16122  						RestartPolicy: &containerRestartPolicyAlways,
 16123  					},
 16124  				},
 16125  				Containers: []core.Container{
 16126  					{
 16127  						Name: "nginx",
 16128  					},
 16129  				},
 16130  				RestartPolicy: core.RestartPolicyNever,
 16131  			},
 16132  			Status: core.PodStatus{
 16133  				InitContainerStatuses: []core.ContainerStatus{{
 16134  					ContainerID: "docker://numbers",
 16135  					Image:       "alpine",
 16136  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16137  					Name:        "restartable-init",
 16138  					Ready:       true,
 16139  					State: core.ContainerState{
 16140  						Running: &core.ContainerStateRunning{
 16141  							StartedAt: metav1.NewTime(time.Now()),
 16142  						},
 16143  					},
 16144  				}},
 16145  				ContainerStatuses: []core.ContainerStatus{{
 16146  					ContainerID: "docker://numbers",
 16147  					Image:       "nginx:alpine",
 16148  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16149  					Name:        "nginx",
 16150  					Ready:       true,
 16151  					Started:     proto.Bool(true),
 16152  					State: core.ContainerState{
 16153  						Running: &core.ContainerStateRunning{
 16154  							StartedAt: metav1.NewTime(time.Now()),
 16155  						},
 16156  					},
 16157  				}},
 16158  			},
 16159  		},
 16160  		core.Pod{
 16161  			ObjectMeta: metav1.ObjectMeta{
 16162  				Name: "foo",
 16163  			},
 16164  			Spec: core.PodSpec{
 16165  				InitContainers: []core.Container{
 16166  					{
 16167  						Name:          "restartable-init",
 16168  						RestartPolicy: &containerRestartPolicyAlways,
 16169  					},
 16170  				},
 16171  				Containers: []core.Container{
 16172  					{
 16173  						Name: "nginx",
 16174  					},
 16175  				},
 16176  				RestartPolicy: core.RestartPolicyNever,
 16177  			},
 16178  			Status: core.PodStatus{
 16179  				InitContainerStatuses: []core.ContainerStatus{{
 16180  					ContainerID: "docker://numbers",
 16181  					Image:       "alpine",
 16182  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16183  					Name:        "restartable-init",
 16184  					Ready:       false,
 16185  					State: core.ContainerState{
 16186  						Terminated: &core.ContainerStateTerminated{
 16187  							ContainerID: "docker://numbers",
 16188  							Reason:      "Completed",
 16189  						},
 16190  					},
 16191  				}},
 16192  				ContainerStatuses: []core.ContainerStatus{{
 16193  					ContainerID: "docker://numbers",
 16194  					Image:       "nginx:alpine",
 16195  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16196  					Name:        "nginx",
 16197  					Ready:       true,
 16198  					Started:     proto.Bool(true),
 16199  					State: core.ContainerState{
 16200  						Running: &core.ContainerStateRunning{
 16201  							StartedAt: metav1.NewTime(time.Now()),
 16202  						},
 16203  					},
 16204  				}},
 16205  			},
 16206  		},
 16207  		"",
 16208  		"restartable init container can restart if RestartPolicyNever",
 16209  	}, {
 16210  		core.Pod{
 16211  			ObjectMeta: metav1.ObjectMeta{
 16212  				Name: "foo",
 16213  			},
 16214  			Spec: core.PodSpec{
 16215  				InitContainers: []core.Container{
 16216  					{
 16217  						Name:          "restartable-init",
 16218  						RestartPolicy: &containerRestartPolicyAlways,
 16219  					},
 16220  				},
 16221  				Containers: []core.Container{
 16222  					{
 16223  						Name: "nginx",
 16224  					},
 16225  				},
 16226  				RestartPolicy: core.RestartPolicyOnFailure,
 16227  			},
 16228  			Status: core.PodStatus{
 16229  				InitContainerStatuses: []core.ContainerStatus{{
 16230  					ContainerID: "docker://numbers",
 16231  					Image:       "alpine",
 16232  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16233  					Name:        "restartable-init",
 16234  					Ready:       true,
 16235  					State: core.ContainerState{
 16236  						Running: &core.ContainerStateRunning{
 16237  							StartedAt: metav1.NewTime(time.Now()),
 16238  						},
 16239  					},
 16240  				}},
 16241  				ContainerStatuses: []core.ContainerStatus{{
 16242  					ContainerID: "docker://numbers",
 16243  					Image:       "nginx:alpine",
 16244  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16245  					Name:        "nginx",
 16246  					Ready:       true,
 16247  					Started:     proto.Bool(true),
 16248  					State: core.ContainerState{
 16249  						Running: &core.ContainerStateRunning{
 16250  							StartedAt: metav1.NewTime(time.Now()),
 16251  						},
 16252  					},
 16253  				}},
 16254  			},
 16255  		},
 16256  		core.Pod{
 16257  			ObjectMeta: metav1.ObjectMeta{
 16258  				Name: "foo",
 16259  			},
 16260  			Spec: core.PodSpec{
 16261  				InitContainers: []core.Container{
 16262  					{
 16263  						Name:          "restartable-init",
 16264  						RestartPolicy: &containerRestartPolicyAlways,
 16265  					},
 16266  				},
 16267  				Containers: []core.Container{
 16268  					{
 16269  						Name: "nginx",
 16270  					},
 16271  				},
 16272  				RestartPolicy: core.RestartPolicyOnFailure,
 16273  			},
 16274  			Status: core.PodStatus{
 16275  				InitContainerStatuses: []core.ContainerStatus{{
 16276  					ContainerID: "docker://numbers",
 16277  					Image:       "alpine",
 16278  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16279  					Name:        "restartable-init",
 16280  					Ready:       false,
 16281  					State: core.ContainerState{
 16282  						Terminated: &core.ContainerStateTerminated{
 16283  							ContainerID: "docker://numbers",
 16284  							Reason:      "Completed",
 16285  						},
 16286  					},
 16287  				}},
 16288  				ContainerStatuses: []core.ContainerStatus{{
 16289  					ContainerID: "docker://numbers",
 16290  					Image:       "nginx:alpine",
 16291  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16292  					Name:        "nginx",
 16293  					Ready:       true,
 16294  					Started:     proto.Bool(true),
 16295  					State: core.ContainerState{
 16296  						Running: &core.ContainerStateRunning{
 16297  							StartedAt: metav1.NewTime(time.Now()),
 16298  						},
 16299  					},
 16300  				}},
 16301  			},
 16302  		},
 16303  		"",
 16304  		"restartable init container can restart if RestartPolicyOnFailure",
 16305  	}, {
 16306  		core.Pod{
 16307  			ObjectMeta: metav1.ObjectMeta{
 16308  				Name: "foo",
 16309  			},
 16310  			Spec: core.PodSpec{
 16311  				InitContainers: []core.Container{
 16312  					{
 16313  						Name:          "restartable-init",
 16314  						RestartPolicy: &containerRestartPolicyAlways,
 16315  					},
 16316  				},
 16317  				Containers: []core.Container{
 16318  					{
 16319  						Name: "nginx",
 16320  					},
 16321  				},
 16322  				RestartPolicy: core.RestartPolicyAlways,
 16323  			},
 16324  			Status: core.PodStatus{
 16325  				InitContainerStatuses: []core.ContainerStatus{{
 16326  					ContainerID: "docker://numbers",
 16327  					Image:       "alpine",
 16328  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16329  					Name:        "restartable-init",
 16330  					Ready:       true,
 16331  					State: core.ContainerState{
 16332  						Running: &core.ContainerStateRunning{
 16333  							StartedAt: metav1.NewTime(time.Now()),
 16334  						},
 16335  					},
 16336  				}},
 16337  				ContainerStatuses: []core.ContainerStatus{{
 16338  					ContainerID: "docker://numbers",
 16339  					Image:       "nginx:alpine",
 16340  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16341  					Name:        "nginx",
 16342  					Ready:       true,
 16343  					Started:     proto.Bool(true),
 16344  					State: core.ContainerState{
 16345  						Running: &core.ContainerStateRunning{
 16346  							StartedAt: metav1.NewTime(time.Now()),
 16347  						},
 16348  					},
 16349  				}},
 16350  			},
 16351  		},
 16352  		core.Pod{
 16353  			ObjectMeta: metav1.ObjectMeta{
 16354  				Name: "foo",
 16355  			},
 16356  			Spec: core.PodSpec{
 16357  				InitContainers: []core.Container{
 16358  					{
 16359  						Name:          "restartable-init",
 16360  						RestartPolicy: &containerRestartPolicyAlways,
 16361  					},
 16362  				},
 16363  				Containers: []core.Container{
 16364  					{
 16365  						Name: "nginx",
 16366  					},
 16367  				},
 16368  				RestartPolicy: core.RestartPolicyAlways,
 16369  			},
 16370  			Status: core.PodStatus{
 16371  				InitContainerStatuses: []core.ContainerStatus{{
 16372  					ContainerID: "docker://numbers",
 16373  					Image:       "alpine",
 16374  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16375  					Name:        "restartable-init",
 16376  					Ready:       false,
 16377  					State: core.ContainerState{
 16378  						Terminated: &core.ContainerStateTerminated{
 16379  							ContainerID: "docker://numbers",
 16380  							Reason:      "Completed",
 16381  						},
 16382  					},
 16383  				}},
 16384  				ContainerStatuses: []core.ContainerStatus{{
 16385  					ContainerID: "docker://numbers",
 16386  					Image:       "nginx:alpine",
 16387  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16388  					Name:        "nginx",
 16389  					Ready:       true,
 16390  					Started:     proto.Bool(true),
 16391  					State: core.ContainerState{
 16392  						Running: &core.ContainerStateRunning{
 16393  							StartedAt: metav1.NewTime(time.Now()),
 16394  						},
 16395  					},
 16396  				}},
 16397  			},
 16398  		},
 16399  		"",
 16400  		"restartable init container can restart if RestartPolicyAlways",
 16401  	},
 16402  	}
 16403  
 16404  	for _, test := range tests {
 16405  		test.new.ObjectMeta.ResourceVersion = "1"
 16406  		test.old.ObjectMeta.ResourceVersion = "1"
 16407  		errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{})
 16408  		if test.err == "" {
 16409  			if len(errs) != 0 {
 16410  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 16411  			}
 16412  		} else {
 16413  			if len(errs) == 0 {
 16414  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 16415  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 16416  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 16417  			}
 16418  		}
 16419  	}
 16420  }
 16421  
 16422  func makeValidService() core.Service {
 16423  	clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster
 16424  	return core.Service{
 16425  		ObjectMeta: metav1.ObjectMeta{
 16426  			Name:            "valid",
 16427  			Namespace:       "valid",
 16428  			Labels:          map[string]string{},
 16429  			Annotations:     map[string]string{},
 16430  			ResourceVersion: "1",
 16431  		},
 16432  		Spec: core.ServiceSpec{
 16433  			Selector:              map[string]string{"key": "val"},
 16434  			SessionAffinity:       "None",
 16435  			Type:                  core.ServiceTypeClusterIP,
 16436  			Ports:                 []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt32(8675)}},
 16437  			InternalTrafficPolicy: &clusterInternalTrafficPolicy,
 16438  		},
 16439  	}
 16440  }
 16441  
 16442  func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
 16443  	makePod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 16444  		return &core.Pod{
 16445  			ObjectMeta: metav1.ObjectMeta{
 16446  				Annotations:     map[string]string{},
 16447  				Labels:          map[string]string{},
 16448  				Name:            "pod",
 16449  				Namespace:       "ns",
 16450  				ResourceVersion: "1",
 16451  			},
 16452  			Spec: core.PodSpec{
 16453  				Containers: []core.Container{{
 16454  					Name:                     "cnt",
 16455  					Image:                    "image",
 16456  					ImagePullPolicy:          "IfNotPresent",
 16457  					TerminationMessagePolicy: "File",
 16458  				}},
 16459  				DNSPolicy:           core.DNSClusterFirst,
 16460  				EphemeralContainers: ephemeralContainers,
 16461  				RestartPolicy:       core.RestartPolicyOnFailure,
 16462  			},
 16463  		}
 16464  	}
 16465  
 16466  	// Some tests use Windows host pods as an example of fields that might
 16467  	// conflict between an ephemeral container and the rest of the pod.
 16468  	capabilities.SetForTests(capabilities.Capabilities{
 16469  		AllowPrivileged: true,
 16470  	})
 16471  	makeWindowsHostPod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 16472  		return &core.Pod{
 16473  			ObjectMeta: metav1.ObjectMeta{
 16474  				Annotations:     map[string]string{},
 16475  				Labels:          map[string]string{},
 16476  				Name:            "pod",
 16477  				Namespace:       "ns",
 16478  				ResourceVersion: "1",
 16479  			},
 16480  			Spec: core.PodSpec{
 16481  				Containers: []core.Container{{
 16482  					Name:            "cnt",
 16483  					Image:           "image",
 16484  					ImagePullPolicy: "IfNotPresent",
 16485  					SecurityContext: &core.SecurityContext{
 16486  						WindowsOptions: &core.WindowsSecurityContextOptions{
 16487  							HostProcess: proto.Bool(true),
 16488  						},
 16489  					},
 16490  					TerminationMessagePolicy: "File",
 16491  				}},
 16492  				DNSPolicy:           core.DNSClusterFirst,
 16493  				EphemeralContainers: ephemeralContainers,
 16494  				RestartPolicy:       core.RestartPolicyOnFailure,
 16495  				SecurityContext: &core.PodSecurityContext{
 16496  					HostNetwork: true,
 16497  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16498  						HostProcess: proto.Bool(true),
 16499  					},
 16500  				},
 16501  			},
 16502  		}
 16503  	}
 16504  
 16505  	tests := []struct {
 16506  		name     string
 16507  		new, old *core.Pod
 16508  		err      string
 16509  	}{{
 16510  		"no ephemeral containers",
 16511  		makePod([]core.EphemeralContainer{}),
 16512  		makePod([]core.EphemeralContainer{}),
 16513  		"",
 16514  	}, {
 16515  		"No change in Ephemeral Containers",
 16516  		makePod([]core.EphemeralContainer{{
 16517  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16518  				Name:                     "debugger",
 16519  				Image:                    "busybox",
 16520  				ImagePullPolicy:          "IfNotPresent",
 16521  				TerminationMessagePolicy: "File",
 16522  			},
 16523  		}, {
 16524  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16525  				Name:                     "debugger2",
 16526  				Image:                    "busybox",
 16527  				ImagePullPolicy:          "IfNotPresent",
 16528  				TerminationMessagePolicy: "File",
 16529  			},
 16530  		}}),
 16531  		makePod([]core.EphemeralContainer{{
 16532  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16533  				Name:                     "debugger",
 16534  				Image:                    "busybox",
 16535  				ImagePullPolicy:          "IfNotPresent",
 16536  				TerminationMessagePolicy: "File",
 16537  			},
 16538  		}, {
 16539  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16540  				Name:                     "debugger2",
 16541  				Image:                    "busybox",
 16542  				ImagePullPolicy:          "IfNotPresent",
 16543  				TerminationMessagePolicy: "File",
 16544  			},
 16545  		}}),
 16546  		"",
 16547  	}, {
 16548  		"Ephemeral Container list order changes",
 16549  		makePod([]core.EphemeralContainer{{
 16550  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16551  				Name:                     "debugger",
 16552  				Image:                    "busybox",
 16553  				ImagePullPolicy:          "IfNotPresent",
 16554  				TerminationMessagePolicy: "File",
 16555  			},
 16556  		}, {
 16557  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16558  				Name:                     "debugger2",
 16559  				Image:                    "busybox",
 16560  				ImagePullPolicy:          "IfNotPresent",
 16561  				TerminationMessagePolicy: "File",
 16562  			},
 16563  		}}),
 16564  		makePod([]core.EphemeralContainer{{
 16565  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16566  				Name:                     "debugger2",
 16567  				Image:                    "busybox",
 16568  				ImagePullPolicy:          "IfNotPresent",
 16569  				TerminationMessagePolicy: "File",
 16570  			},
 16571  		}, {
 16572  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16573  				Name:                     "debugger",
 16574  				Image:                    "busybox",
 16575  				ImagePullPolicy:          "IfNotPresent",
 16576  				TerminationMessagePolicy: "File",
 16577  			},
 16578  		}}),
 16579  		"",
 16580  	}, {
 16581  		"Add an Ephemeral Container",
 16582  		makePod([]core.EphemeralContainer{{
 16583  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16584  				Name:                     "debugger",
 16585  				Image:                    "busybox",
 16586  				ImagePullPolicy:          "IfNotPresent",
 16587  				TerminationMessagePolicy: "File",
 16588  			},
 16589  		}}),
 16590  		makePod([]core.EphemeralContainer{}),
 16591  		"",
 16592  	}, {
 16593  		"Add two Ephemeral Containers",
 16594  		makePod([]core.EphemeralContainer{{
 16595  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16596  				Name:                     "debugger1",
 16597  				Image:                    "busybox",
 16598  				ImagePullPolicy:          "IfNotPresent",
 16599  				TerminationMessagePolicy: "File",
 16600  			},
 16601  		}, {
 16602  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16603  				Name:                     "debugger2",
 16604  				Image:                    "busybox",
 16605  				ImagePullPolicy:          "IfNotPresent",
 16606  				TerminationMessagePolicy: "File",
 16607  			},
 16608  		}}),
 16609  		makePod([]core.EphemeralContainer{}),
 16610  		"",
 16611  	}, {
 16612  		"Add to an existing Ephemeral Containers",
 16613  		makePod([]core.EphemeralContainer{{
 16614  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16615  				Name:                     "debugger",
 16616  				Image:                    "busybox",
 16617  				ImagePullPolicy:          "IfNotPresent",
 16618  				TerminationMessagePolicy: "File",
 16619  			},
 16620  		}, {
 16621  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16622  				Name:                     "debugger2",
 16623  				Image:                    "busybox",
 16624  				ImagePullPolicy:          "IfNotPresent",
 16625  				TerminationMessagePolicy: "File",
 16626  			},
 16627  		}}),
 16628  		makePod([]core.EphemeralContainer{{
 16629  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16630  				Name:                     "debugger",
 16631  				Image:                    "busybox",
 16632  				ImagePullPolicy:          "IfNotPresent",
 16633  				TerminationMessagePolicy: "File",
 16634  			},
 16635  		}}),
 16636  		"",
 16637  	}, {
 16638  		"Add to an existing Ephemeral Containers, list order changes",
 16639  		makePod([]core.EphemeralContainer{{
 16640  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16641  				Name:                     "debugger3",
 16642  				Image:                    "busybox",
 16643  				ImagePullPolicy:          "IfNotPresent",
 16644  				TerminationMessagePolicy: "File",
 16645  			},
 16646  		}, {
 16647  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16648  				Name:                     "debugger2",
 16649  				Image:                    "busybox",
 16650  				ImagePullPolicy:          "IfNotPresent",
 16651  				TerminationMessagePolicy: "File",
 16652  			},
 16653  		}, {
 16654  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16655  				Name:                     "debugger",
 16656  				Image:                    "busybox",
 16657  				ImagePullPolicy:          "IfNotPresent",
 16658  				TerminationMessagePolicy: "File",
 16659  			},
 16660  		}}),
 16661  		makePod([]core.EphemeralContainer{{
 16662  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16663  				Name:                     "debugger",
 16664  				Image:                    "busybox",
 16665  				ImagePullPolicy:          "IfNotPresent",
 16666  				TerminationMessagePolicy: "File",
 16667  			},
 16668  		}, {
 16669  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16670  				Name:                     "debugger2",
 16671  				Image:                    "busybox",
 16672  				ImagePullPolicy:          "IfNotPresent",
 16673  				TerminationMessagePolicy: "File",
 16674  			},
 16675  		}}),
 16676  		"",
 16677  	}, {
 16678  		"Remove an Ephemeral Container",
 16679  		makePod([]core.EphemeralContainer{}),
 16680  		makePod([]core.EphemeralContainer{{
 16681  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16682  				Name:                     "debugger",
 16683  				Image:                    "busybox",
 16684  				ImagePullPolicy:          "IfNotPresent",
 16685  				TerminationMessagePolicy: "File",
 16686  			},
 16687  		}}),
 16688  		"may not be removed",
 16689  	}, {
 16690  		"Replace an Ephemeral Container",
 16691  		makePod([]core.EphemeralContainer{{
 16692  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16693  				Name:                     "firstone",
 16694  				Image:                    "busybox",
 16695  				ImagePullPolicy:          "IfNotPresent",
 16696  				TerminationMessagePolicy: "File",
 16697  			},
 16698  		}}),
 16699  		makePod([]core.EphemeralContainer{{
 16700  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16701  				Name:                     "thentheother",
 16702  				Image:                    "busybox",
 16703  				ImagePullPolicy:          "IfNotPresent",
 16704  				TerminationMessagePolicy: "File",
 16705  			},
 16706  		}}),
 16707  		"may not be removed",
 16708  	}, {
 16709  		"Change an Ephemeral Containers",
 16710  		makePod([]core.EphemeralContainer{{
 16711  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16712  				Name:                     "debugger1",
 16713  				Image:                    "busybox",
 16714  				ImagePullPolicy:          "IfNotPresent",
 16715  				TerminationMessagePolicy: "File",
 16716  			},
 16717  		}, {
 16718  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16719  				Name:                     "debugger2",
 16720  				Image:                    "busybox",
 16721  				ImagePullPolicy:          "IfNotPresent",
 16722  				TerminationMessagePolicy: "File",
 16723  			},
 16724  		}}),
 16725  		makePod([]core.EphemeralContainer{{
 16726  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16727  				Name:                     "debugger1",
 16728  				Image:                    "debian",
 16729  				ImagePullPolicy:          "IfNotPresent",
 16730  				TerminationMessagePolicy: "File",
 16731  			},
 16732  		}, {
 16733  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16734  				Name:                     "debugger2",
 16735  				Image:                    "busybox",
 16736  				ImagePullPolicy:          "IfNotPresent",
 16737  				TerminationMessagePolicy: "File",
 16738  			},
 16739  		}}),
 16740  		"may not be changed",
 16741  	}, {
 16742  		"Ephemeral container with potential conflict with regular containers, but conflict not present",
 16743  		makeWindowsHostPod([]core.EphemeralContainer{{
 16744  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16745  				Name:            "debugger1",
 16746  				Image:           "image",
 16747  				ImagePullPolicy: "IfNotPresent",
 16748  				SecurityContext: &core.SecurityContext{
 16749  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16750  						HostProcess: proto.Bool(true),
 16751  					},
 16752  				},
 16753  				TerminationMessagePolicy: "File",
 16754  			},
 16755  		}}),
 16756  		makeWindowsHostPod(nil),
 16757  		"",
 16758  	}, {
 16759  		"Ephemeral container with potential conflict with regular containers, and conflict is present",
 16760  		makeWindowsHostPod([]core.EphemeralContainer{{
 16761  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16762  				Name:            "debugger1",
 16763  				Image:           "image",
 16764  				ImagePullPolicy: "IfNotPresent",
 16765  				SecurityContext: &core.SecurityContext{
 16766  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16767  						HostProcess: proto.Bool(false),
 16768  					},
 16769  				},
 16770  				TerminationMessagePolicy: "File",
 16771  			},
 16772  		}}),
 16773  		makeWindowsHostPod(nil),
 16774  		"spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical",
 16775  	}, {
 16776  		"Add ephemeral container to static pod",
 16777  		func() *core.Pod {
 16778  			p := makePod(nil)
 16779  			p.Spec.NodeName = "some-name"
 16780  			p.ObjectMeta.Annotations = map[string]string{
 16781  				core.MirrorPodAnnotationKey: "foo",
 16782  			}
 16783  			p.Spec.EphemeralContainers = []core.EphemeralContainer{{
 16784  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 16785  					Name:                     "debugger1",
 16786  					Image:                    "debian",
 16787  					ImagePullPolicy:          "IfNotPresent",
 16788  					TerminationMessagePolicy: "File",
 16789  				},
 16790  			}}
 16791  			return p
 16792  		}(),
 16793  		func() *core.Pod {
 16794  			p := makePod(nil)
 16795  			p.Spec.NodeName = "some-name"
 16796  			p.ObjectMeta.Annotations = map[string]string{
 16797  				core.MirrorPodAnnotationKey: "foo",
 16798  			}
 16799  			return p
 16800  		}(),
 16801  		"Forbidden: static pods do not support ephemeral containers",
 16802  	},
 16803  	}
 16804  
 16805  	for _, tc := range tests {
 16806  		errs := ValidatePodEphemeralContainersUpdate(tc.new, tc.old, PodValidationOptions{})
 16807  		if tc.err == "" {
 16808  			if len(errs) != 0 {
 16809  				t.Errorf("unexpected invalid for test: %s\nErrors returned: %+v\nLocal diff of test objects (-old +new):\n%s", tc.name, errs, cmp.Diff(tc.old, tc.new))
 16810  			}
 16811  		} else {
 16812  			if len(errs) == 0 {
 16813  				t.Errorf("unexpected valid for test: %s\nLocal diff of test objects (-old +new):\n%s", tc.name, cmp.Diff(tc.old, tc.new))
 16814  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, tc.err) {
 16815  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", tc.name, tc.err, actualErr)
 16816  			}
 16817  		}
 16818  	}
 16819  }
 16820  
 16821  func TestValidateServiceCreate(t *testing.T) {
 16822  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 16823  	singleStack := core.IPFamilyPolicySingleStack
 16824  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 16825  
 16826  	testCases := []struct {
 16827  		name         string
 16828  		tweakSvc     func(svc *core.Service) // given a basic valid service, each test case can customize it
 16829  		numErrs      int
 16830  		featureGates []featuregate.Feature
 16831  	}{{
 16832  		name: "missing namespace",
 16833  		tweakSvc: func(s *core.Service) {
 16834  			s.Namespace = ""
 16835  		},
 16836  		numErrs: 1,
 16837  	}, {
 16838  		name: "invalid namespace",
 16839  		tweakSvc: func(s *core.Service) {
 16840  			s.Namespace = "-123"
 16841  		},
 16842  		numErrs: 1,
 16843  	}, {
 16844  		name: "missing name",
 16845  		tweakSvc: func(s *core.Service) {
 16846  			s.Name = ""
 16847  		},
 16848  		numErrs: 1,
 16849  	}, {
 16850  		name: "invalid name",
 16851  		tweakSvc: func(s *core.Service) {
 16852  			s.Name = "-123"
 16853  		},
 16854  		numErrs: 1,
 16855  	}, {
 16856  		name: "too long name",
 16857  		tweakSvc: func(s *core.Service) {
 16858  			s.Name = strings.Repeat("a", 64)
 16859  		},
 16860  		numErrs: 1,
 16861  	}, {
 16862  		name: "invalid generateName",
 16863  		tweakSvc: func(s *core.Service) {
 16864  			s.GenerateName = "-123"
 16865  		},
 16866  		numErrs: 1,
 16867  	}, {
 16868  		name: "too long generateName",
 16869  		tweakSvc: func(s *core.Service) {
 16870  			s.GenerateName = strings.Repeat("a", 64)
 16871  		},
 16872  		numErrs: 1,
 16873  	}, {
 16874  		name: "invalid label",
 16875  		tweakSvc: func(s *core.Service) {
 16876  			s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar"
 16877  		},
 16878  		numErrs: 1,
 16879  	}, {
 16880  		name: "invalid annotation",
 16881  		tweakSvc: func(s *core.Service) {
 16882  			s.Annotations["NoSpecialCharsLike=Equals"] = "bar"
 16883  		},
 16884  		numErrs: 1,
 16885  	}, {
 16886  		name: "nil selector",
 16887  		tweakSvc: func(s *core.Service) {
 16888  			s.Spec.Selector = nil
 16889  		},
 16890  		numErrs: 0,
 16891  	}, {
 16892  		name: "invalid selector",
 16893  		tweakSvc: func(s *core.Service) {
 16894  			s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar"
 16895  		},
 16896  		numErrs: 1,
 16897  	}, {
 16898  		name: "missing session affinity",
 16899  		tweakSvc: func(s *core.Service) {
 16900  			s.Spec.SessionAffinity = ""
 16901  		},
 16902  		numErrs: 1,
 16903  	}, {
 16904  		name: "missing type",
 16905  		tweakSvc: func(s *core.Service) {
 16906  			s.Spec.Type = ""
 16907  		},
 16908  		numErrs: 1,
 16909  	}, {
 16910  		name: "missing ports",
 16911  		tweakSvc: func(s *core.Service) {
 16912  			s.Spec.Ports = nil
 16913  		},
 16914  		numErrs: 1,
 16915  	}, {
 16916  		name: "missing ports but headless",
 16917  		tweakSvc: func(s *core.Service) {
 16918  			s.Spec.Ports = nil
 16919  			s.Spec.ClusterIP = core.ClusterIPNone
 16920  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16921  		},
 16922  		numErrs: 0,
 16923  	}, {
 16924  		name: "empty port[0] name",
 16925  		tweakSvc: func(s *core.Service) {
 16926  			s.Spec.Ports[0].Name = ""
 16927  		},
 16928  		numErrs: 0,
 16929  	}, {
 16930  		name: "empty port[1] name",
 16931  		tweakSvc: func(s *core.Service) {
 16932  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 16933  		},
 16934  		numErrs: 1,
 16935  	}, {
 16936  		name: "empty multi-port port[0] name",
 16937  		tweakSvc: func(s *core.Service) {
 16938  			s.Spec.Ports[0].Name = ""
 16939  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 16940  		},
 16941  		numErrs: 1,
 16942  	}, {
 16943  		name: "invalid port name",
 16944  		tweakSvc: func(s *core.Service) {
 16945  			s.Spec.Ports[0].Name = "INVALID"
 16946  		},
 16947  		numErrs: 1,
 16948  	}, {
 16949  		name: "missing protocol",
 16950  		tweakSvc: func(s *core.Service) {
 16951  			s.Spec.Ports[0].Protocol = ""
 16952  		},
 16953  		numErrs: 1,
 16954  	}, {
 16955  		name: "invalid protocol",
 16956  		tweakSvc: func(s *core.Service) {
 16957  			s.Spec.Ports[0].Protocol = "INVALID"
 16958  		},
 16959  		numErrs: 1,
 16960  	}, {
 16961  		name: "invalid cluster ip",
 16962  		tweakSvc: func(s *core.Service) {
 16963  			s.Spec.ClusterIP = "invalid"
 16964  			s.Spec.ClusterIPs = []string{"invalid"}
 16965  		},
 16966  		numErrs: 1,
 16967  	}, {
 16968  		name: "missing port",
 16969  		tweakSvc: func(s *core.Service) {
 16970  			s.Spec.Ports[0].Port = 0
 16971  		},
 16972  		numErrs: 1,
 16973  	}, {
 16974  		name: "invalid port",
 16975  		tweakSvc: func(s *core.Service) {
 16976  			s.Spec.Ports[0].Port = 65536
 16977  		},
 16978  		numErrs: 1,
 16979  	}, {
 16980  		name: "invalid TargetPort int",
 16981  		tweakSvc: func(s *core.Service) {
 16982  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536)
 16983  		},
 16984  		numErrs: 1,
 16985  	}, {
 16986  		name: "valid port headless",
 16987  		tweakSvc: func(s *core.Service) {
 16988  			s.Spec.Ports[0].Port = 11722
 16989  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722)
 16990  			s.Spec.ClusterIP = core.ClusterIPNone
 16991  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16992  		},
 16993  		numErrs: 0,
 16994  	}, {
 16995  		name: "invalid port headless 1",
 16996  		tweakSvc: func(s *core.Service) {
 16997  			s.Spec.Ports[0].Port = 11722
 16998  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721)
 16999  			s.Spec.ClusterIP = core.ClusterIPNone
 17000  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17001  		},
 17002  		// in the v1 API, targetPorts on headless services were tolerated.
 17003  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 17004  		// numErrs: 1,
 17005  		numErrs: 0,
 17006  	}, {
 17007  		name: "invalid port headless 2",
 17008  		tweakSvc: func(s *core.Service) {
 17009  			s.Spec.Ports[0].Port = 11722
 17010  			s.Spec.Ports[0].TargetPort = intstr.FromString("target")
 17011  			s.Spec.ClusterIP = core.ClusterIPNone
 17012  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17013  		},
 17014  		// in the v1 API, targetPorts on headless services were tolerated.
 17015  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 17016  		// numErrs: 1,
 17017  		numErrs: 0,
 17018  	}, {
 17019  		name: "invalid publicIPs localhost",
 17020  		tweakSvc: func(s *core.Service) {
 17021  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17022  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 17023  		},
 17024  		numErrs: 1,
 17025  	}, {
 17026  		name: "invalid publicIPs unspecified",
 17027  		tweakSvc: func(s *core.Service) {
 17028  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17029  			s.Spec.ExternalIPs = []string{"0.0.0.0"}
 17030  		},
 17031  		numErrs: 1,
 17032  	}, {
 17033  		name: "invalid publicIPs loopback",
 17034  		tweakSvc: func(s *core.Service) {
 17035  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17036  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 17037  		},
 17038  		numErrs: 1,
 17039  	}, {
 17040  		name: "invalid publicIPs host",
 17041  		tweakSvc: func(s *core.Service) {
 17042  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17043  			s.Spec.ExternalIPs = []string{"myhost.mydomain"}
 17044  		},
 17045  		numErrs: 1,
 17046  	}, {
 17047  		name: "valid publicIPs",
 17048  		tweakSvc: func(s *core.Service) {
 17049  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17050  			s.Spec.ExternalIPs = []string{"1.2.3.4"}
 17051  		},
 17052  		numErrs: 0,
 17053  	}, {
 17054  		name: "dup port name",
 17055  		tweakSvc: func(s *core.Service) {
 17056  			s.Spec.Ports[0].Name = "p"
 17057  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17058  		},
 17059  		numErrs: 1,
 17060  	}, {
 17061  		name: "valid load balancer protocol UDP 1",
 17062  		tweakSvc: func(s *core.Service) {
 17063  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17064  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17065  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17066  			s.Spec.Ports[0].Protocol = "UDP"
 17067  		},
 17068  		numErrs: 0,
 17069  	}, {
 17070  		name: "valid load balancer protocol UDP 2",
 17071  		tweakSvc: func(s *core.Service) {
 17072  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17073  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17074  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17075  			s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}
 17076  		},
 17077  		numErrs: 0,
 17078  	}, {
 17079  		name: "load balancer with mix protocol",
 17080  		tweakSvc: func(s *core.Service) {
 17081  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17082  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17083  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17084  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)})
 17085  		},
 17086  		numErrs: 0,
 17087  	}, {
 17088  		name: "valid 1",
 17089  		tweakSvc: func(s *core.Service) {
 17090  			// do nothing
 17091  		},
 17092  		numErrs: 0,
 17093  	}, {
 17094  		name: "valid 2",
 17095  		tweakSvc: func(s *core.Service) {
 17096  			s.Spec.Ports[0].Protocol = "UDP"
 17097  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345)
 17098  		},
 17099  		numErrs: 0,
 17100  	}, {
 17101  		name: "valid 3",
 17102  		tweakSvc: func(s *core.Service) {
 17103  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 17104  		},
 17105  		numErrs: 0,
 17106  	}, {
 17107  		name: "valid cluster ip - none ",
 17108  		tweakSvc: func(s *core.Service) {
 17109  			s.Spec.ClusterIP = core.ClusterIPNone
 17110  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17111  		},
 17112  		numErrs: 0,
 17113  	}, {
 17114  		name: "valid cluster ip - empty",
 17115  		tweakSvc: func(s *core.Service) {
 17116  			s.Spec.ClusterIPs = nil
 17117  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 17118  		},
 17119  		numErrs: 0,
 17120  	}, {
 17121  		name: "valid type - clusterIP",
 17122  		tweakSvc: func(s *core.Service) {
 17123  			s.Spec.Type = core.ServiceTypeClusterIP
 17124  		},
 17125  		numErrs: 0,
 17126  	}, {
 17127  		name: "valid type - loadbalancer",
 17128  		tweakSvc: func(s *core.Service) {
 17129  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17130  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17131  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17132  		},
 17133  		numErrs: 0,
 17134  	}, {
 17135  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false",
 17136  		tweakSvc: func(s *core.Service) {
 17137  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17138  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17139  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 17140  		},
 17141  		numErrs: 0,
 17142  	}, {
 17143  		name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type",
 17144  		tweakSvc: func(s *core.Service) {
 17145  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17146  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17147  		},
 17148  		numErrs: 1,
 17149  	}, {
 17150  		name: "valid type loadbalancer 2 ports",
 17151  		tweakSvc: func(s *core.Service) {
 17152  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17153  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17154  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17155  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17156  		},
 17157  		numErrs: 0,
 17158  	}, {
 17159  		name: "valid external load balancer 2 ports",
 17160  		tweakSvc: func(s *core.Service) {
 17161  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17162  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17163  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17164  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17165  		},
 17166  		numErrs: 0,
 17167  	}, {
 17168  		name: "duplicate nodeports",
 17169  		tweakSvc: func(s *core.Service) {
 17170  			s.Spec.Type = core.ServiceTypeNodePort
 17171  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17172  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17173  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17174  		},
 17175  		numErrs: 1,
 17176  	}, {
 17177  		name: "duplicate nodeports (different protocols)",
 17178  		tweakSvc: func(s *core.Service) {
 17179  			s.Spec.Type = core.ServiceTypeNodePort
 17180  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17181  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17182  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17183  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)})
 17184  		},
 17185  		numErrs: 0,
 17186  	}, {
 17187  		name: "invalid duplicate ports (with same protocol)",
 17188  		tweakSvc: func(s *core.Service) {
 17189  			s.Spec.Type = core.ServiceTypeClusterIP
 17190  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 17191  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)})
 17192  		},
 17193  		numErrs: 1,
 17194  	}, {
 17195  		name: "valid duplicate ports (with different protocols)",
 17196  		tweakSvc: func(s *core.Service) {
 17197  			s.Spec.Type = core.ServiceTypeClusterIP
 17198  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 17199  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)})
 17200  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)})
 17201  		},
 17202  		numErrs: 0,
 17203  	}, {
 17204  		name: "valid type - cluster",
 17205  		tweakSvc: func(s *core.Service) {
 17206  			s.Spec.Type = core.ServiceTypeClusterIP
 17207  		},
 17208  		numErrs: 0,
 17209  	}, {
 17210  		name: "valid type - nodeport",
 17211  		tweakSvc: func(s *core.Service) {
 17212  			s.Spec.Type = core.ServiceTypeNodePort
 17213  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17214  		},
 17215  		numErrs: 0,
 17216  	}, {
 17217  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true",
 17218  		tweakSvc: func(s *core.Service) {
 17219  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17220  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17221  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17222  		},
 17223  		numErrs: 0,
 17224  	}, {
 17225  		name: "valid type loadbalancer 2 ports",
 17226  		tweakSvc: func(s *core.Service) {
 17227  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17228  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17229  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17230  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17231  		},
 17232  		numErrs: 0,
 17233  	}, {
 17234  		name: "valid type loadbalancer with NodePort",
 17235  		tweakSvc: func(s *core.Service) {
 17236  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17237  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17238  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17239  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17240  		},
 17241  		numErrs: 0,
 17242  	}, {
 17243  		name: "valid type=NodePort service with NodePort",
 17244  		tweakSvc: func(s *core.Service) {
 17245  			s.Spec.Type = core.ServiceTypeNodePort
 17246  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17247  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17248  		},
 17249  		numErrs: 0,
 17250  	}, {
 17251  		name: "valid type=NodePort service without NodePort",
 17252  		tweakSvc: func(s *core.Service) {
 17253  			s.Spec.Type = core.ServiceTypeNodePort
 17254  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17255  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17256  		},
 17257  		numErrs: 0,
 17258  	}, {
 17259  		name: "valid cluster service without NodePort",
 17260  		tweakSvc: func(s *core.Service) {
 17261  			s.Spec.Type = core.ServiceTypeClusterIP
 17262  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17263  		},
 17264  		numErrs: 0,
 17265  	}, {
 17266  		name: "invalid cluster service with NodePort",
 17267  		tweakSvc: func(s *core.Service) {
 17268  			s.Spec.Type = core.ServiceTypeClusterIP
 17269  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17270  		},
 17271  		numErrs: 1,
 17272  	}, {
 17273  		name: "invalid public service with duplicate NodePort",
 17274  		tweakSvc: func(s *core.Service) {
 17275  			s.Spec.Type = core.ServiceTypeNodePort
 17276  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17277  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17278  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17279  		},
 17280  		numErrs: 1,
 17281  	}, {
 17282  		name: "valid type=LoadBalancer",
 17283  		tweakSvc: func(s *core.Service) {
 17284  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17285  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17286  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17287  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17288  		},
 17289  		numErrs: 0,
 17290  	}, {
 17291  		// For now we open firewalls, and its insecure if we open 10250, remove this
 17292  		// when we have better protections in place.
 17293  		name: "invalid port type=LoadBalancer",
 17294  		tweakSvc: func(s *core.Service) {
 17295  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17296  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17297  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17298  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17299  		},
 17300  		numErrs: 1,
 17301  	}, {
 17302  		name: "valid LoadBalancer source range annotation",
 17303  		tweakSvc: func(s *core.Service) {
 17304  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17305  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17306  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17307  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24,  5.6.0.0/16"
 17308  		},
 17309  		numErrs: 0,
 17310  	}, {
 17311  		name: "valid empty LoadBalancer source range annotation",
 17312  		tweakSvc: func(s *core.Service) {
 17313  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17314  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17315  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17316  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
 17317  		},
 17318  		numErrs: 0,
 17319  	}, {
 17320  		name: "valid whitespace-only LoadBalancer source range annotation",
 17321  		tweakSvc: func(s *core.Service) {
 17322  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17323  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17324  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17325  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "  "
 17326  		},
 17327  		numErrs: 0,
 17328  	}, {
 17329  		name: "invalid LoadBalancer source range annotation (hostname)",
 17330  		tweakSvc: func(s *core.Service) {
 17331  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17332  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17333  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17334  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
 17335  		},
 17336  		numErrs: 1,
 17337  	}, {
 17338  		name: "invalid LoadBalancer source range annotation (invalid CIDR)",
 17339  		tweakSvc: func(s *core.Service) {
 17340  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17341  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17342  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17343  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33"
 17344  		},
 17345  		numErrs: 1,
 17346  	}, {
 17347  		name: "invalid LoadBalancer source range annotation for non LoadBalancer type service",
 17348  		tweakSvc: func(s *core.Service) {
 17349  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24"
 17350  		},
 17351  		numErrs: 1,
 17352  	}, {
 17353  		name: "invalid empty-but-set LoadBalancer source range annotation for non LoadBalancer type service",
 17354  		tweakSvc: func(s *core.Service) {
 17355  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
 17356  		},
 17357  		numErrs: 1,
 17358  	}, {
 17359  		name: "valid LoadBalancer source range",
 17360  		tweakSvc: func(s *core.Service) {
 17361  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17362  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17363  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17364  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17365  		},
 17366  		numErrs: 0,
 17367  	}, {
 17368  		name: "valid LoadBalancer source range with whitespace",
 17369  		tweakSvc: func(s *core.Service) {
 17370  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17371  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17372  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17373  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24  ", " 5.6.0.0/16"}
 17374  		},
 17375  		numErrs: 0,
 17376  	}, {
 17377  		name: "invalid empty LoadBalancer source range",
 17378  		tweakSvc: func(s *core.Service) {
 17379  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17380  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17381  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17382  			s.Spec.LoadBalancerSourceRanges = []string{"   "}
 17383  		},
 17384  		numErrs: 1,
 17385  	}, {
 17386  		name: "invalid LoadBalancer source range (hostname)",
 17387  		tweakSvc: func(s *core.Service) {
 17388  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17389  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17390  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17391  			s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"}
 17392  		},
 17393  		numErrs: 1,
 17394  	}, {
 17395  		name: "invalid LoadBalancer source range (invalid CIDR)",
 17396  		tweakSvc: func(s *core.Service) {
 17397  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17398  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17399  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17400  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/33"}
 17401  		},
 17402  		numErrs: 1,
 17403  	}, {
 17404  		name: "invalid source range for non LoadBalancer type service",
 17405  		tweakSvc: func(s *core.Service) {
 17406  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17407  		},
 17408  		numErrs: 1,
 17409  	}, {
 17410  		name: "invalid source range annotation ignored with valid source range field",
 17411  		tweakSvc: func(s *core.Service) {
 17412  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17413  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17414  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17415  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
 17416  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17417  		},
 17418  		numErrs: 0,
 17419  	}, {
 17420  		name: "valid ExternalName",
 17421  		tweakSvc: func(s *core.Service) {
 17422  			s.Spec.Type = core.ServiceTypeExternalName
 17423  			s.Spec.ExternalName = "foo.bar.example.com"
 17424  		},
 17425  		numErrs: 0,
 17426  	}, {
 17427  		name: "valid ExternalName (trailing dot)",
 17428  		tweakSvc: func(s *core.Service) {
 17429  			s.Spec.Type = core.ServiceTypeExternalName
 17430  			s.Spec.ExternalName = "foo.bar.example.com."
 17431  		},
 17432  		numErrs: 0,
 17433  	}, {
 17434  		name: "invalid ExternalName clusterIP (valid IP)",
 17435  		tweakSvc: func(s *core.Service) {
 17436  			s.Spec.Type = core.ServiceTypeExternalName
 17437  			s.Spec.ClusterIP = "1.2.3.4"
 17438  			s.Spec.ClusterIPs = []string{"1.2.3.4"}
 17439  			s.Spec.ExternalName = "foo.bar.example.com"
 17440  		},
 17441  		numErrs: 1,
 17442  	}, {
 17443  		name: "invalid ExternalName clusterIP (None)",
 17444  		tweakSvc: func(s *core.Service) {
 17445  			s.Spec.Type = core.ServiceTypeExternalName
 17446  			s.Spec.ClusterIP = "None"
 17447  			s.Spec.ClusterIPs = []string{"None"}
 17448  			s.Spec.ExternalName = "foo.bar.example.com"
 17449  		},
 17450  		numErrs: 1,
 17451  	}, {
 17452  		name: "invalid ExternalName (not a DNS name)",
 17453  		tweakSvc: func(s *core.Service) {
 17454  			s.Spec.Type = core.ServiceTypeExternalName
 17455  			s.Spec.ExternalName = "-123"
 17456  		},
 17457  		numErrs: 1,
 17458  	}, {
 17459  		name: "LoadBalancer type cannot have None ClusterIP",
 17460  		tweakSvc: func(s *core.Service) {
 17461  			s.Spec.ClusterIP = "None"
 17462  			s.Spec.ClusterIPs = []string{"None"}
 17463  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17464  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17465  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17466  		},
 17467  		numErrs: 1,
 17468  	}, {
 17469  		name: "invalid node port with clusterIP None",
 17470  		tweakSvc: func(s *core.Service) {
 17471  			s.Spec.Type = core.ServiceTypeNodePort
 17472  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17473  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17474  			s.Spec.ClusterIP = "None"
 17475  			s.Spec.ClusterIPs = []string{"None"}
 17476  		},
 17477  		numErrs: 1,
 17478  	},
 17479  		// ESIPP section begins.
 17480  		{
 17481  			name: "invalid externalTraffic field",
 17482  			tweakSvc: func(s *core.Service) {
 17483  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17484  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17485  				s.Spec.ExternalTrafficPolicy = "invalid"
 17486  			},
 17487  			numErrs: 1,
 17488  		}, {
 17489  			name: "nil internalTraffic field when feature gate is on",
 17490  			tweakSvc: func(s *core.Service) {
 17491  				s.Spec.InternalTrafficPolicy = nil
 17492  			},
 17493  			numErrs: 1,
 17494  		}, {
 17495  			name: "internalTrafficPolicy field nil when type is ExternalName",
 17496  			tweakSvc: func(s *core.Service) {
 17497  				s.Spec.InternalTrafficPolicy = nil
 17498  				s.Spec.Type = core.ServiceTypeExternalName
 17499  				s.Spec.ExternalName = "foo.bar.com"
 17500  			},
 17501  			numErrs: 0,
 17502  		}, {
 17503  			// Typically this should fail validation, but in v1.22 we have existing clusters
 17504  			// that may have allowed internalTrafficPolicy when Type=ExternalName.
 17505  			// This test case ensures we don't break compatibility for internalTrafficPolicy
 17506  			// when Type=ExternalName
 17507  			name: "internalTrafficPolicy field is set when type is ExternalName",
 17508  			tweakSvc: func(s *core.Service) {
 17509  				cluster := core.ServiceInternalTrafficPolicyCluster
 17510  				s.Spec.InternalTrafficPolicy = &cluster
 17511  				s.Spec.Type = core.ServiceTypeExternalName
 17512  				s.Spec.ExternalName = "foo.bar.com"
 17513  			},
 17514  			numErrs: 0,
 17515  		}, {
 17516  			name: "invalid internalTraffic field",
 17517  			tweakSvc: func(s *core.Service) {
 17518  				invalid := core.ServiceInternalTrafficPolicy("invalid")
 17519  				s.Spec.InternalTrafficPolicy = &invalid
 17520  			},
 17521  			numErrs: 1,
 17522  		}, {
 17523  			name: "internalTrafficPolicy field set to Cluster",
 17524  			tweakSvc: func(s *core.Service) {
 17525  				cluster := core.ServiceInternalTrafficPolicyCluster
 17526  				s.Spec.InternalTrafficPolicy = &cluster
 17527  			},
 17528  			numErrs: 0,
 17529  		}, {
 17530  			name: "internalTrafficPolicy field set to Local",
 17531  			tweakSvc: func(s *core.Service) {
 17532  				local := core.ServiceInternalTrafficPolicyLocal
 17533  				s.Spec.InternalTrafficPolicy = &local
 17534  			},
 17535  			numErrs: 0,
 17536  		}, {
 17537  			name: "negative healthCheckNodePort field",
 17538  			tweakSvc: func(s *core.Service) {
 17539  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17540  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17541  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17542  				s.Spec.HealthCheckNodePort = -1
 17543  			},
 17544  			numErrs: 1,
 17545  		}, {
 17546  			name: "negative healthCheckNodePort field",
 17547  			tweakSvc: func(s *core.Service) {
 17548  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17549  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17550  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17551  				s.Spec.HealthCheckNodePort = 31100
 17552  			},
 17553  			numErrs: 0,
 17554  		},
 17555  		// ESIPP section ends.
 17556  		{
 17557  			name: "invalid timeoutSeconds field",
 17558  			tweakSvc: func(s *core.Service) {
 17559  				s.Spec.Type = core.ServiceTypeClusterIP
 17560  				s.Spec.SessionAffinity = core.ServiceAffinityClientIP
 17561  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 17562  					ClientIP: &core.ClientIPConfig{
 17563  						TimeoutSeconds: utilpointer.Int32(-1),
 17564  					},
 17565  				}
 17566  			},
 17567  			numErrs: 1,
 17568  		}, {
 17569  			name: "sessionAffinityConfig can't be set when session affinity is None",
 17570  			tweakSvc: func(s *core.Service) {
 17571  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17572  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17573  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17574  				s.Spec.SessionAffinity = core.ServiceAffinityNone
 17575  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 17576  					ClientIP: &core.ClientIPConfig{
 17577  						TimeoutSeconds: utilpointer.Int32(90),
 17578  					},
 17579  				}
 17580  			},
 17581  			numErrs: 1,
 17582  		},
 17583  		/* ip families validation */
 17584  		{
 17585  			name: "invalid, service with invalid ipFamilies",
 17586  			tweakSvc: func(s *core.Service) {
 17587  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 17588  				s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily}
 17589  			},
 17590  			numErrs: 1,
 17591  		}, {
 17592  			name: "invalid, service with invalid ipFamilies (2nd)",
 17593  			tweakSvc: func(s *core.Service) {
 17594  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 17595  				s.Spec.IPFamilyPolicy = &requireDualStack
 17596  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily}
 17597  			},
 17598  			numErrs: 1,
 17599  		}, {
 17600  			name: "IPFamilyPolicy(singleStack) is set for two families",
 17601  			tweakSvc: func(s *core.Service) {
 17602  				s.Spec.IPFamilyPolicy = &singleStack
 17603  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17604  			},
 17605  			numErrs: 0, // this validated in alloc code.
 17606  		}, {
 17607  			name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)",
 17608  			tweakSvc: func(s *core.Service) {
 17609  				s.Spec.IPFamilyPolicy = &preferDualStack
 17610  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17611  			},
 17612  			numErrs: 0,
 17613  		},
 17614  
 17615  		{
 17616  			name: "invalid, service with 2+ ipFamilies",
 17617  			tweakSvc: func(s *core.Service) {
 17618  				s.Spec.IPFamilyPolicy = &requireDualStack
 17619  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol}
 17620  			},
 17621  			numErrs: 1,
 17622  		}, {
 17623  			name: "invalid, service with same ip families",
 17624  			tweakSvc: func(s *core.Service) {
 17625  				s.Spec.IPFamilyPolicy = &requireDualStack
 17626  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol}
 17627  			},
 17628  			numErrs: 1,
 17629  		}, {
 17630  			name: "valid, nil service ipFamilies",
 17631  			tweakSvc: func(s *core.Service) {
 17632  				s.Spec.IPFamilies = nil
 17633  			},
 17634  			numErrs: 0,
 17635  		}, {
 17636  			name: "valid, service with valid ipFamilies (v4)",
 17637  			tweakSvc: func(s *core.Service) {
 17638  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17639  			},
 17640  			numErrs: 0,
 17641  		}, {
 17642  			name: "valid, service with valid ipFamilies (v6)",
 17643  			tweakSvc: func(s *core.Service) {
 17644  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17645  			},
 17646  			numErrs: 0,
 17647  		}, {
 17648  			name: "valid, service with valid ipFamilies(v4,v6)",
 17649  			tweakSvc: func(s *core.Service) {
 17650  				s.Spec.IPFamilyPolicy = &requireDualStack
 17651  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17652  			},
 17653  			numErrs: 0,
 17654  		}, {
 17655  			name: "valid, service with valid ipFamilies(v6,v4)",
 17656  			tweakSvc: func(s *core.Service) {
 17657  				s.Spec.IPFamilyPolicy = &requireDualStack
 17658  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17659  			},
 17660  			numErrs: 0,
 17661  		}, {
 17662  			name: "valid, service preferred dual stack with single family",
 17663  			tweakSvc: func(s *core.Service) {
 17664  				s.Spec.IPFamilyPolicy = &preferDualStack
 17665  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17666  			},
 17667  			numErrs: 0,
 17668  		},
 17669  		/* cluster IPs. some tests are redundant */
 17670  		{
 17671  			name: "invalid, garbage single ip",
 17672  			tweakSvc: func(s *core.Service) {
 17673  				s.Spec.ClusterIP = "garbage-ip"
 17674  				s.Spec.ClusterIPs = []string{"garbage-ip"}
 17675  			},
 17676  			numErrs: 1,
 17677  		}, {
 17678  			name: "invalid, garbage ips",
 17679  			tweakSvc: func(s *core.Service) {
 17680  				s.Spec.IPFamilyPolicy = &requireDualStack
 17681  				s.Spec.ClusterIP = "garbage-ip"
 17682  				s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"}
 17683  			},
 17684  			numErrs: 2,
 17685  		}, {
 17686  			name: "invalid, garbage first ip",
 17687  			tweakSvc: func(s *core.Service) {
 17688  				s.Spec.IPFamilyPolicy = &requireDualStack
 17689  				s.Spec.ClusterIP = "garbage-ip"
 17690  				s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"}
 17691  			},
 17692  			numErrs: 1,
 17693  		}, {
 17694  			name: "invalid, garbage second ip",
 17695  			tweakSvc: func(s *core.Service) {
 17696  				s.Spec.IPFamilyPolicy = &requireDualStack
 17697  				s.Spec.ClusterIP = "2001::1"
 17698  				s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"}
 17699  			},
 17700  			numErrs: 1,
 17701  		}, {
 17702  			name: "invalid, NONE + IP",
 17703  			tweakSvc: func(s *core.Service) {
 17704  				s.Spec.IPFamilyPolicy = &requireDualStack
 17705  				s.Spec.ClusterIP = "None"
 17706  				s.Spec.ClusterIPs = []string{"None", "2001::1"}
 17707  			},
 17708  			numErrs: 1,
 17709  		}, {
 17710  			name: "invalid, IP + NONE",
 17711  			tweakSvc: func(s *core.Service) {
 17712  				s.Spec.IPFamilyPolicy = &requireDualStack
 17713  				s.Spec.ClusterIP = "2001::1"
 17714  				s.Spec.ClusterIPs = []string{"2001::1", "None"}
 17715  			},
 17716  			numErrs: 1,
 17717  		}, {
 17718  			name: "invalid, EMPTY STRING + IP",
 17719  			tweakSvc: func(s *core.Service) {
 17720  				s.Spec.IPFamilyPolicy = &requireDualStack
 17721  				s.Spec.ClusterIP = ""
 17722  				s.Spec.ClusterIPs = []string{"", "2001::1"}
 17723  			},
 17724  			numErrs: 2,
 17725  		}, {
 17726  			name: "invalid, IP + EMPTY STRING",
 17727  			tweakSvc: func(s *core.Service) {
 17728  				s.Spec.IPFamilyPolicy = &requireDualStack
 17729  				s.Spec.ClusterIP = "2001::1"
 17730  				s.Spec.ClusterIPs = []string{"2001::1", ""}
 17731  			},
 17732  			numErrs: 1,
 17733  		}, {
 17734  			name: "invalid, same ip family (v6)",
 17735  			tweakSvc: func(s *core.Service) {
 17736  				s.Spec.IPFamilyPolicy = &requireDualStack
 17737  				s.Spec.ClusterIP = "2001::1"
 17738  				s.Spec.ClusterIPs = []string{"2001::1", "2001::4"}
 17739  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17740  			},
 17741  			numErrs: 2,
 17742  		}, {
 17743  			name: "invalid, same ip family (v4)",
 17744  			tweakSvc: func(s *core.Service) {
 17745  				s.Spec.IPFamilyPolicy = &requireDualStack
 17746  				s.Spec.ClusterIP = "10.0.0.1"
 17747  				s.Spec.ClusterIPs = []string{"10.0.0.1", "10.0.0.10"}
 17748  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17749  
 17750  			},
 17751  			numErrs: 2,
 17752  		}, {
 17753  			name: "invalid, more than two ips",
 17754  			tweakSvc: func(s *core.Service) {
 17755  				s.Spec.IPFamilyPolicy = &requireDualStack
 17756  				s.Spec.ClusterIP = "10.0.0.1"
 17757  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1", "10.0.0.10"}
 17758  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17759  			},
 17760  			numErrs: 1,
 17761  		}, {
 17762  			name: " multi ip, dualstack not set (request for downgrade)",
 17763  			tweakSvc: func(s *core.Service) {
 17764  				s.Spec.IPFamilyPolicy = &singleStack
 17765  				s.Spec.ClusterIP = "10.0.0.1"
 17766  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17767  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17768  			},
 17769  			numErrs: 0,
 17770  		}, {
 17771  			name: "valid, headless-no-selector + multi family + gate off",
 17772  			tweakSvc: func(s *core.Service) {
 17773  				s.Spec.IPFamilyPolicy = &requireDualStack
 17774  				s.Spec.ClusterIP = "None"
 17775  				s.Spec.ClusterIPs = []string{"None"}
 17776  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17777  				s.Spec.Selector = nil
 17778  			},
 17779  			numErrs: 0,
 17780  		}, {
 17781  			name: "valid, multi ip, single ipfamilies preferDualStack",
 17782  			tweakSvc: func(s *core.Service) {
 17783  				s.Spec.IPFamilyPolicy = &preferDualStack
 17784  				s.Spec.ClusterIP = "10.0.0.1"
 17785  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17786  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17787  			},
 17788  			numErrs: 0,
 17789  		},
 17790  
 17791  		{
 17792  			name: "valid, multi ip, single ipfamilies (must match when provided) + requireDualStack",
 17793  			tweakSvc: func(s *core.Service) {
 17794  				s.Spec.IPFamilyPolicy = &requireDualStack
 17795  				s.Spec.ClusterIP = "10.0.0.1"
 17796  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17797  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17798  			},
 17799  			numErrs: 0,
 17800  		}, {
 17801  			name: "invalid, families don't match (v4=>v6)",
 17802  			tweakSvc: func(s *core.Service) {
 17803  				s.Spec.ClusterIP = "10.0.0.1"
 17804  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17805  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17806  			},
 17807  			numErrs: 1,
 17808  		}, {
 17809  			name: "invalid, families don't match (v6=>v4)",
 17810  			tweakSvc: func(s *core.Service) {
 17811  				s.Spec.ClusterIP = "2001::1"
 17812  				s.Spec.ClusterIPs = []string{"2001::1"}
 17813  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17814  			},
 17815  			numErrs: 1,
 17816  		}, {
 17817  			name: "valid. no field set",
 17818  			tweakSvc: func(s *core.Service) {
 17819  			},
 17820  			numErrs: 0,
 17821  		},
 17822  
 17823  		{
 17824  			name: "valid, single ip",
 17825  			tweakSvc: func(s *core.Service) {
 17826  				s.Spec.IPFamilyPolicy = &singleStack
 17827  				s.Spec.ClusterIP = "10.0.0.1"
 17828  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17829  			},
 17830  			numErrs: 0,
 17831  		}, {
 17832  			name: "valid, single family",
 17833  			tweakSvc: func(s *core.Service) {
 17834  				s.Spec.IPFamilyPolicy = &singleStack
 17835  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17836  
 17837  			},
 17838  			numErrs: 0,
 17839  		}, {
 17840  			name: "valid, single ip + single family",
 17841  			tweakSvc: func(s *core.Service) {
 17842  				s.Spec.IPFamilyPolicy = &singleStack
 17843  				s.Spec.ClusterIP = "2001::1"
 17844  				s.Spec.ClusterIPs = []string{"2001::1"}
 17845  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17846  
 17847  			},
 17848  			numErrs: 0,
 17849  		}, {
 17850  			name: "valid, single ip + single family (dual stack requested)",
 17851  			tweakSvc: func(s *core.Service) {
 17852  				s.Spec.IPFamilyPolicy = &preferDualStack
 17853  				s.Spec.ClusterIP = "2001::1"
 17854  				s.Spec.ClusterIPs = []string{"2001::1"}
 17855  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17856  
 17857  			},
 17858  			numErrs: 0,
 17859  		}, {
 17860  			name: "valid, single ip, multi ipfamilies",
 17861  			tweakSvc: func(s *core.Service) {
 17862  				s.Spec.IPFamilyPolicy = &requireDualStack
 17863  				s.Spec.ClusterIP = "10.0.0.1"
 17864  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17865  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17866  			},
 17867  			numErrs: 0,
 17868  		}, {
 17869  			name: "valid, multi ips, multi ipfamilies (4,6)",
 17870  			tweakSvc: func(s *core.Service) {
 17871  				s.Spec.IPFamilyPolicy = &requireDualStack
 17872  				s.Spec.ClusterIP = "10.0.0.1"
 17873  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17874  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17875  			},
 17876  			numErrs: 0,
 17877  		}, {
 17878  			name: "valid, ips, multi ipfamilies (6,4)",
 17879  			tweakSvc: func(s *core.Service) {
 17880  				s.Spec.IPFamilyPolicy = &requireDualStack
 17881  				s.Spec.ClusterIP = "2001::1"
 17882  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17883  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17884  			},
 17885  			numErrs: 0,
 17886  		}, {
 17887  			name: "valid, multi ips (6,4)",
 17888  			tweakSvc: func(s *core.Service) {
 17889  				s.Spec.IPFamilyPolicy = &requireDualStack
 17890  				s.Spec.ClusterIP = "2001::1"
 17891  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17892  			},
 17893  			numErrs: 0,
 17894  		}, {
 17895  			name: "valid, multi ipfamilies (6,4)",
 17896  			tweakSvc: func(s *core.Service) {
 17897  				s.Spec.IPFamilyPolicy = &requireDualStack
 17898  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17899  			},
 17900  			numErrs: 0,
 17901  		}, {
 17902  			name: "valid, multi ips (4,6)",
 17903  			tweakSvc: func(s *core.Service) {
 17904  				s.Spec.IPFamilyPolicy = &requireDualStack
 17905  				s.Spec.ClusterIP = "10.0.0.1"
 17906  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17907  			},
 17908  			numErrs: 0,
 17909  		}, {
 17910  			name: "valid,  multi ipfamilies (4,6)",
 17911  			tweakSvc: func(s *core.Service) {
 17912  				s.Spec.IPFamilyPolicy = &requireDualStack
 17913  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17914  			},
 17915  			numErrs: 0,
 17916  		}, {
 17917  			name: "valid, dual stack",
 17918  			tweakSvc: func(s *core.Service) {
 17919  				s.Spec.IPFamilyPolicy = &requireDualStack
 17920  			},
 17921  			numErrs: 0,
 17922  		},
 17923  
 17924  		{
 17925  			name: `valid appProtocol`,
 17926  			tweakSvc: func(s *core.Service) {
 17927  				s.Spec.Ports = []core.ServicePort{{
 17928  					Port:        12345,
 17929  					TargetPort:  intstr.FromInt32(12345),
 17930  					Protocol:    "TCP",
 17931  					AppProtocol: utilpointer.String("HTTP"),
 17932  				}}
 17933  			},
 17934  			numErrs: 0,
 17935  		}, {
 17936  			name: `valid custom appProtocol`,
 17937  			tweakSvc: func(s *core.Service) {
 17938  				s.Spec.Ports = []core.ServicePort{{
 17939  					Port:        12345,
 17940  					TargetPort:  intstr.FromInt32(12345),
 17941  					Protocol:    "TCP",
 17942  					AppProtocol: utilpointer.String("example.com/protocol"),
 17943  				}}
 17944  			},
 17945  			numErrs: 0,
 17946  		}, {
 17947  			name: `invalid appProtocol`,
 17948  			tweakSvc: func(s *core.Service) {
 17949  				s.Spec.Ports = []core.ServicePort{{
 17950  					Port:        12345,
 17951  					TargetPort:  intstr.FromInt32(12345),
 17952  					Protocol:    "TCP",
 17953  					AppProtocol: utilpointer.String("example.com/protocol_with{invalid}[characters]"),
 17954  				}}
 17955  			},
 17956  			numErrs: 1,
 17957  		},
 17958  
 17959  		{
 17960  			name: "invalid cluster ip != clusterIP in multi ip service",
 17961  			tweakSvc: func(s *core.Service) {
 17962  				s.Spec.IPFamilyPolicy = &requireDualStack
 17963  				s.Spec.ClusterIP = "10.0.0.10"
 17964  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17965  			},
 17966  			numErrs: 1,
 17967  		}, {
 17968  			name: "invalid cluster ip != clusterIP in single ip service",
 17969  			tweakSvc: func(s *core.Service) {
 17970  				s.Spec.ClusterIP = "10.0.0.10"
 17971  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17972  			},
 17973  			numErrs: 1,
 17974  		}, {
 17975  			name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 17976  			tweakSvc: func(s *core.Service) {
 17977  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17978  			},
 17979  			numErrs: 1,
 17980  		}, {
 17981  			name: "valid LoadBalancerClass when type is LoadBalancer",
 17982  			tweakSvc: func(s *core.Service) {
 17983  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17984  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17985  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17986  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 17987  			},
 17988  			numErrs: 0,
 17989  		}, {
 17990  			name: "invalid LoadBalancerClass",
 17991  			tweakSvc: func(s *core.Service) {
 17992  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17993  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17994  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17995  				s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass")
 17996  			},
 17997  			numErrs: 1,
 17998  		}, {
 17999  			name: "invalid: set LoadBalancerClass when type is not LoadBalancer",
 18000  			tweakSvc: func(s *core.Service) {
 18001  				s.Spec.Type = core.ServiceTypeClusterIP
 18002  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 18003  			},
 18004  			numErrs: 1,
 18005  		}, {
 18006  			name: "topology annotations are mismatched",
 18007  			tweakSvc: func(s *core.Service) {
 18008  				s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 18009  				s.Annotations[core.AnnotationTopologyMode] = "different"
 18010  			},
 18011  			numErrs: 1,
 18012  		}, {
 18013  			name: "valid: trafficDistribution field set to PreferClose",
 18014  			tweakSvc: func(s *core.Service) {
 18015  				s.Spec.TrafficDistribution = utilpointer.String("PreferClose")
 18016  			},
 18017  			numErrs: 0,
 18018  		}, {
 18019  			name: "invalid: trafficDistribution field set to Random",
 18020  			tweakSvc: func(s *core.Service) {
 18021  				s.Spec.TrafficDistribution = utilpointer.String("Random")
 18022  			},
 18023  			numErrs: 1,
 18024  		},
 18025  	}
 18026  
 18027  	for _, tc := range testCases {
 18028  		t.Run(tc.name, func(t *testing.T) {
 18029  			for i := range tc.featureGates {
 18030  				featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tc.featureGates[i], true)
 18031  			}
 18032  			svc := makeValidService()
 18033  			tc.tweakSvc(&svc)
 18034  			errs := ValidateServiceCreate(&svc)
 18035  			if len(errs) != tc.numErrs {
 18036  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 18037  			}
 18038  		})
 18039  	}
 18040  }
 18041  
 18042  func TestValidateServiceExternalTrafficPolicy(t *testing.T) {
 18043  	testCases := []struct {
 18044  		name     string
 18045  		tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it.
 18046  		numErrs  int
 18047  	}{{
 18048  		name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
 18049  		tweakSvc: func(s *core.Service) {
 18050  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18051  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18052  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18053  			s.Spec.HealthCheckNodePort = 34567
 18054  		},
 18055  		numErrs: 0,
 18056  	}, {
 18057  		name: "valid nodePort service with externalTrafficPolicy set",
 18058  		tweakSvc: func(s *core.Service) {
 18059  			s.Spec.Type = core.ServiceTypeNodePort
 18060  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18061  		},
 18062  		numErrs: 0,
 18063  	}, {
 18064  		name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
 18065  		tweakSvc: func(s *core.Service) {
 18066  			s.Spec.Type = core.ServiceTypeClusterIP
 18067  		},
 18068  		numErrs: 0,
 18069  	}, {
 18070  		name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
 18071  		tweakSvc: func(s *core.Service) {
 18072  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18073  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18074  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18075  			s.Spec.HealthCheckNodePort = 34567
 18076  		},
 18077  		numErrs: 1,
 18078  	}, {
 18079  		name: "cannot set healthCheckNodePort field on nodePort service",
 18080  		tweakSvc: func(s *core.Service) {
 18081  			s.Spec.Type = core.ServiceTypeNodePort
 18082  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18083  			s.Spec.HealthCheckNodePort = 34567
 18084  		},
 18085  		numErrs: 1,
 18086  	}, {
 18087  		name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
 18088  		tweakSvc: func(s *core.Service) {
 18089  			s.Spec.Type = core.ServiceTypeClusterIP
 18090  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18091  			s.Spec.HealthCheckNodePort = 34567
 18092  		},
 18093  		numErrs: 2,
 18094  	}, {
 18095  		name: "cannot set externalTrafficPolicy field on ExternalName service",
 18096  		tweakSvc: func(s *core.Service) {
 18097  			s.Spec.Type = core.ServiceTypeExternalName
 18098  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18099  		},
 18100  		numErrs: 1,
 18101  	}, {
 18102  		name: "externalTrafficPolicy is required on NodePort service",
 18103  		tweakSvc: func(s *core.Service) {
 18104  			s.Spec.Type = core.ServiceTypeNodePort
 18105  		},
 18106  		numErrs: 1,
 18107  	}, {
 18108  		name: "externalTrafficPolicy is required on LoadBalancer service",
 18109  		tweakSvc: func(s *core.Service) {
 18110  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18111  		},
 18112  		numErrs: 1,
 18113  	}, {
 18114  		name: "externalTrafficPolicy is required on ClusterIP service with externalIPs",
 18115  		tweakSvc: func(s *core.Service) {
 18116  			s.Spec.Type = core.ServiceTypeClusterIP
 18117  			s.Spec.ExternalIPs = []string{"1.2.3,4"}
 18118  		},
 18119  		numErrs: 1,
 18120  	},
 18121  	}
 18122  
 18123  	for _, tc := range testCases {
 18124  		svc := makeValidService()
 18125  		tc.tweakSvc(&svc)
 18126  		errs := validateServiceExternalTrafficPolicy(&svc)
 18127  		if len(errs) != tc.numErrs {
 18128  			t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
 18129  		}
 18130  	}
 18131  }
 18132  
 18133  func TestValidateReplicationControllerStatus(t *testing.T) {
 18134  	tests := []struct {
 18135  		name string
 18136  
 18137  		replicas             int32
 18138  		fullyLabeledReplicas int32
 18139  		readyReplicas        int32
 18140  		availableReplicas    int32
 18141  		observedGeneration   int64
 18142  
 18143  		expectedErr bool
 18144  	}{{
 18145  		name:                 "valid status",
 18146  		replicas:             3,
 18147  		fullyLabeledReplicas: 3,
 18148  		readyReplicas:        2,
 18149  		availableReplicas:    1,
 18150  		observedGeneration:   2,
 18151  		expectedErr:          false,
 18152  	}, {
 18153  		name:                 "invalid replicas",
 18154  		replicas:             -1,
 18155  		fullyLabeledReplicas: 3,
 18156  		readyReplicas:        2,
 18157  		availableReplicas:    1,
 18158  		observedGeneration:   2,
 18159  		expectedErr:          true,
 18160  	}, {
 18161  		name:                 "invalid fullyLabeledReplicas",
 18162  		replicas:             3,
 18163  		fullyLabeledReplicas: -1,
 18164  		readyReplicas:        2,
 18165  		availableReplicas:    1,
 18166  		observedGeneration:   2,
 18167  		expectedErr:          true,
 18168  	}, {
 18169  		name:                 "invalid readyReplicas",
 18170  		replicas:             3,
 18171  		fullyLabeledReplicas: 3,
 18172  		readyReplicas:        -1,
 18173  		availableReplicas:    1,
 18174  		observedGeneration:   2,
 18175  		expectedErr:          true,
 18176  	}, {
 18177  		name:                 "invalid availableReplicas",
 18178  		replicas:             3,
 18179  		fullyLabeledReplicas: 3,
 18180  		readyReplicas:        3,
 18181  		availableReplicas:    -1,
 18182  		observedGeneration:   2,
 18183  		expectedErr:          true,
 18184  	}, {
 18185  		name:                 "invalid observedGeneration",
 18186  		replicas:             3,
 18187  		fullyLabeledReplicas: 3,
 18188  		readyReplicas:        3,
 18189  		availableReplicas:    3,
 18190  		observedGeneration:   -1,
 18191  		expectedErr:          true,
 18192  	}, {
 18193  		name:                 "fullyLabeledReplicas greater than replicas",
 18194  		replicas:             3,
 18195  		fullyLabeledReplicas: 4,
 18196  		readyReplicas:        3,
 18197  		availableReplicas:    3,
 18198  		observedGeneration:   1,
 18199  		expectedErr:          true,
 18200  	}, {
 18201  		name:                 "readyReplicas greater than replicas",
 18202  		replicas:             3,
 18203  		fullyLabeledReplicas: 3,
 18204  		readyReplicas:        4,
 18205  		availableReplicas:    3,
 18206  		observedGeneration:   1,
 18207  		expectedErr:          true,
 18208  	}, {
 18209  		name:                 "availableReplicas greater than replicas",
 18210  		replicas:             3,
 18211  		fullyLabeledReplicas: 3,
 18212  		readyReplicas:        3,
 18213  		availableReplicas:    4,
 18214  		observedGeneration:   1,
 18215  		expectedErr:          true,
 18216  	}, {
 18217  		name:                 "availableReplicas greater than readyReplicas",
 18218  		replicas:             3,
 18219  		fullyLabeledReplicas: 3,
 18220  		readyReplicas:        2,
 18221  		availableReplicas:    3,
 18222  		observedGeneration:   1,
 18223  		expectedErr:          true,
 18224  	},
 18225  	}
 18226  
 18227  	for _, test := range tests {
 18228  		status := core.ReplicationControllerStatus{
 18229  			Replicas:             test.replicas,
 18230  			FullyLabeledReplicas: test.fullyLabeledReplicas,
 18231  			ReadyReplicas:        test.readyReplicas,
 18232  			AvailableReplicas:    test.availableReplicas,
 18233  			ObservedGeneration:   test.observedGeneration,
 18234  		}
 18235  
 18236  		if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
 18237  			t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
 18238  		}
 18239  	}
 18240  }
 18241  
 18242  func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
 18243  	validSelector := map[string]string{"a": "b"}
 18244  	validPodTemplate := core.PodTemplate{
 18245  		Template: core.PodTemplateSpec{
 18246  			ObjectMeta: metav1.ObjectMeta{
 18247  				Labels: validSelector,
 18248  			},
 18249  			Spec: core.PodSpec{
 18250  				RestartPolicy: core.RestartPolicyAlways,
 18251  				DNSPolicy:     core.DNSClusterFirst,
 18252  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18253  			},
 18254  		},
 18255  	}
 18256  	type rcUpdateTest struct {
 18257  		old    core.ReplicationController
 18258  		update core.ReplicationController
 18259  	}
 18260  	successCases := []rcUpdateTest{{
 18261  		old: core.ReplicationController{
 18262  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18263  			Spec: core.ReplicationControllerSpec{
 18264  				Selector: validSelector,
 18265  				Template: &validPodTemplate.Template,
 18266  			},
 18267  			Status: core.ReplicationControllerStatus{
 18268  				Replicas: 2,
 18269  			},
 18270  		},
 18271  		update: core.ReplicationController{
 18272  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18273  			Spec: core.ReplicationControllerSpec{
 18274  				Replicas: 3,
 18275  				Selector: validSelector,
 18276  				Template: &validPodTemplate.Template,
 18277  			},
 18278  			Status: core.ReplicationControllerStatus{
 18279  				Replicas: 4,
 18280  			},
 18281  		},
 18282  	},
 18283  	}
 18284  	for _, successCase := range successCases {
 18285  		successCase.old.ObjectMeta.ResourceVersion = "1"
 18286  		successCase.update.ObjectMeta.ResourceVersion = "1"
 18287  		if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
 18288  			t.Errorf("expected success: %v", errs)
 18289  		}
 18290  	}
 18291  	errorCases := map[string]rcUpdateTest{
 18292  		"negative replicas": {
 18293  			old: core.ReplicationController{
 18294  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18295  				Spec: core.ReplicationControllerSpec{
 18296  					Selector: validSelector,
 18297  					Template: &validPodTemplate.Template,
 18298  				},
 18299  				Status: core.ReplicationControllerStatus{
 18300  					Replicas: 3,
 18301  				},
 18302  			},
 18303  			update: core.ReplicationController{
 18304  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18305  				Spec: core.ReplicationControllerSpec{
 18306  					Replicas: 2,
 18307  					Selector: validSelector,
 18308  					Template: &validPodTemplate.Template,
 18309  				},
 18310  				Status: core.ReplicationControllerStatus{
 18311  					Replicas: -3,
 18312  				},
 18313  			},
 18314  		},
 18315  	}
 18316  	for testName, errorCase := range errorCases {
 18317  		if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
 18318  			t.Errorf("expected failure: %s", testName)
 18319  		}
 18320  	}
 18321  
 18322  }
 18323  
 18324  func TestValidateReplicationControllerUpdate(t *testing.T) {
 18325  	validSelector := map[string]string{"a": "b"}
 18326  	validPodTemplate := core.PodTemplate{
 18327  		Template: core.PodTemplateSpec{
 18328  			ObjectMeta: metav1.ObjectMeta{
 18329  				Labels: validSelector,
 18330  			},
 18331  			Spec: core.PodSpec{
 18332  				RestartPolicy: core.RestartPolicyAlways,
 18333  				DNSPolicy:     core.DNSClusterFirst,
 18334  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18335  			},
 18336  		},
 18337  	}
 18338  	readWriteVolumePodTemplate := core.PodTemplate{
 18339  		Template: core.PodTemplateSpec{
 18340  			ObjectMeta: metav1.ObjectMeta{
 18341  				Labels: validSelector,
 18342  			},
 18343  			Spec: core.PodSpec{
 18344  				RestartPolicy: core.RestartPolicyAlways,
 18345  				DNSPolicy:     core.DNSClusterFirst,
 18346  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18347  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 18348  			},
 18349  		},
 18350  	}
 18351  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18352  	invalidPodTemplate := core.PodTemplate{
 18353  		Template: core.PodTemplateSpec{
 18354  			Spec: core.PodSpec{
 18355  				RestartPolicy: core.RestartPolicyAlways,
 18356  				DNSPolicy:     core.DNSClusterFirst,
 18357  			},
 18358  			ObjectMeta: metav1.ObjectMeta{
 18359  				Labels: invalidSelector,
 18360  			},
 18361  		},
 18362  	}
 18363  	type rcUpdateTest struct {
 18364  		old    core.ReplicationController
 18365  		update core.ReplicationController
 18366  	}
 18367  	successCases := []rcUpdateTest{{
 18368  		old: core.ReplicationController{
 18369  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18370  			Spec: core.ReplicationControllerSpec{
 18371  				Selector: validSelector,
 18372  				Template: &validPodTemplate.Template,
 18373  			},
 18374  		},
 18375  		update: core.ReplicationController{
 18376  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18377  			Spec: core.ReplicationControllerSpec{
 18378  				Replicas: 3,
 18379  				Selector: validSelector,
 18380  				Template: &validPodTemplate.Template,
 18381  			},
 18382  		},
 18383  	}, {
 18384  		old: core.ReplicationController{
 18385  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18386  			Spec: core.ReplicationControllerSpec{
 18387  				Selector: validSelector,
 18388  				Template: &validPodTemplate.Template,
 18389  			},
 18390  		},
 18391  		update: core.ReplicationController{
 18392  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18393  			Spec: core.ReplicationControllerSpec{
 18394  				Replicas: 1,
 18395  				Selector: validSelector,
 18396  				Template: &readWriteVolumePodTemplate.Template,
 18397  			},
 18398  		},
 18399  	},
 18400  	}
 18401  	for _, successCase := range successCases {
 18402  		successCase.old.ObjectMeta.ResourceVersion = "1"
 18403  		successCase.update.ObjectMeta.ResourceVersion = "1"
 18404  		if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old, PodValidationOptions{}); len(errs) != 0 {
 18405  			t.Errorf("expected success: %v", errs)
 18406  		}
 18407  	}
 18408  	errorCases := map[string]rcUpdateTest{
 18409  		"more than one read/write": {
 18410  			old: core.ReplicationController{
 18411  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18412  				Spec: core.ReplicationControllerSpec{
 18413  					Selector: validSelector,
 18414  					Template: &validPodTemplate.Template,
 18415  				},
 18416  			},
 18417  			update: core.ReplicationController{
 18418  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18419  				Spec: core.ReplicationControllerSpec{
 18420  					Replicas: 2,
 18421  					Selector: validSelector,
 18422  					Template: &readWriteVolumePodTemplate.Template,
 18423  				},
 18424  			},
 18425  		},
 18426  		"invalid selector": {
 18427  			old: core.ReplicationController{
 18428  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18429  				Spec: core.ReplicationControllerSpec{
 18430  					Selector: validSelector,
 18431  					Template: &validPodTemplate.Template,
 18432  				},
 18433  			},
 18434  			update: core.ReplicationController{
 18435  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18436  				Spec: core.ReplicationControllerSpec{
 18437  					Replicas: 2,
 18438  					Selector: invalidSelector,
 18439  					Template: &validPodTemplate.Template,
 18440  				},
 18441  			},
 18442  		},
 18443  		"invalid pod": {
 18444  			old: core.ReplicationController{
 18445  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18446  				Spec: core.ReplicationControllerSpec{
 18447  					Selector: validSelector,
 18448  					Template: &validPodTemplate.Template,
 18449  				},
 18450  			},
 18451  			update: core.ReplicationController{
 18452  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18453  				Spec: core.ReplicationControllerSpec{
 18454  					Replicas: 2,
 18455  					Selector: validSelector,
 18456  					Template: &invalidPodTemplate.Template,
 18457  				},
 18458  			},
 18459  		},
 18460  		"negative replicas": {
 18461  			old: core.ReplicationController{
 18462  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18463  				Spec: core.ReplicationControllerSpec{
 18464  					Selector: validSelector,
 18465  					Template: &validPodTemplate.Template,
 18466  				},
 18467  			},
 18468  			update: core.ReplicationController{
 18469  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18470  				Spec: core.ReplicationControllerSpec{
 18471  					Replicas: -1,
 18472  					Selector: validSelector,
 18473  					Template: &validPodTemplate.Template,
 18474  				},
 18475  			},
 18476  		},
 18477  	}
 18478  	for testName, errorCase := range errorCases {
 18479  		if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old, PodValidationOptions{}); len(errs) == 0 {
 18480  			t.Errorf("expected failure: %s", testName)
 18481  		}
 18482  	}
 18483  }
 18484  
 18485  func TestValidateReplicationController(t *testing.T) {
 18486  	validSelector := map[string]string{"a": "b"}
 18487  	validPodTemplate := core.PodTemplate{
 18488  		Template: core.PodTemplateSpec{
 18489  			ObjectMeta: metav1.ObjectMeta{
 18490  				Labels: validSelector,
 18491  			},
 18492  			Spec: core.PodSpec{
 18493  				RestartPolicy: core.RestartPolicyAlways,
 18494  				DNSPolicy:     core.DNSClusterFirst,
 18495  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18496  			},
 18497  		},
 18498  	}
 18499  	readWriteVolumePodTemplate := core.PodTemplate{
 18500  		Template: core.PodTemplateSpec{
 18501  			ObjectMeta: metav1.ObjectMeta{
 18502  				Labels: validSelector,
 18503  			},
 18504  			Spec: core.PodSpec{
 18505  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 18506  				RestartPolicy: core.RestartPolicyAlways,
 18507  				DNSPolicy:     core.DNSClusterFirst,
 18508  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18509  			},
 18510  		},
 18511  	}
 18512  	hostnetPodTemplate := core.PodTemplate{
 18513  		Template: core.PodTemplateSpec{
 18514  			ObjectMeta: metav1.ObjectMeta{
 18515  				Labels: validSelector,
 18516  			},
 18517  			Spec: core.PodSpec{
 18518  				SecurityContext: &core.PodSecurityContext{
 18519  					HostNetwork: true,
 18520  				},
 18521  				RestartPolicy: core.RestartPolicyAlways,
 18522  				DNSPolicy:     core.DNSClusterFirst,
 18523  				Containers: []core.Container{{
 18524  					Name:                     "abc",
 18525  					Image:                    "image",
 18526  					ImagePullPolicy:          "IfNotPresent",
 18527  					TerminationMessagePolicy: "File",
 18528  					Ports: []core.ContainerPort{{
 18529  						ContainerPort: 12345,
 18530  						Protocol:      core.ProtocolTCP,
 18531  					}},
 18532  				}},
 18533  			},
 18534  		},
 18535  	}
 18536  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18537  	invalidPodTemplate := core.PodTemplate{
 18538  		Template: core.PodTemplateSpec{
 18539  			Spec: core.PodSpec{
 18540  				RestartPolicy: core.RestartPolicyAlways,
 18541  				DNSPolicy:     core.DNSClusterFirst,
 18542  			},
 18543  			ObjectMeta: metav1.ObjectMeta{
 18544  				Labels: invalidSelector,
 18545  			},
 18546  		},
 18547  	}
 18548  	successCases := []core.ReplicationController{{
 18549  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18550  		Spec: core.ReplicationControllerSpec{
 18551  			Selector: validSelector,
 18552  			Template: &validPodTemplate.Template,
 18553  		},
 18554  	}, {
 18555  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18556  		Spec: core.ReplicationControllerSpec{
 18557  			Selector: validSelector,
 18558  			Template: &validPodTemplate.Template,
 18559  		},
 18560  	}, {
 18561  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18562  		Spec: core.ReplicationControllerSpec{
 18563  			Replicas: 1,
 18564  			Selector: validSelector,
 18565  			Template: &readWriteVolumePodTemplate.Template,
 18566  		},
 18567  	}, {
 18568  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
 18569  		Spec: core.ReplicationControllerSpec{
 18570  			Replicas: 1,
 18571  			Selector: validSelector,
 18572  			Template: &hostnetPodTemplate.Template,
 18573  		},
 18574  	}}
 18575  	for _, successCase := range successCases {
 18576  		if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 {
 18577  			t.Errorf("expected success: %v", errs)
 18578  		}
 18579  	}
 18580  
 18581  	errorCases := map[string]core.ReplicationController{
 18582  		"zero-length ID": {
 18583  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18584  			Spec: core.ReplicationControllerSpec{
 18585  				Selector: validSelector,
 18586  				Template: &validPodTemplate.Template,
 18587  			},
 18588  		},
 18589  		"missing-namespace": {
 18590  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 18591  			Spec: core.ReplicationControllerSpec{
 18592  				Selector: validSelector,
 18593  				Template: &validPodTemplate.Template,
 18594  			},
 18595  		},
 18596  		"empty selector": {
 18597  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18598  			Spec: core.ReplicationControllerSpec{
 18599  				Template: &validPodTemplate.Template,
 18600  			},
 18601  		},
 18602  		"selector_doesnt_match": {
 18603  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18604  			Spec: core.ReplicationControllerSpec{
 18605  				Selector: map[string]string{"foo": "bar"},
 18606  				Template: &validPodTemplate.Template,
 18607  			},
 18608  		},
 18609  		"invalid manifest": {
 18610  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18611  			Spec: core.ReplicationControllerSpec{
 18612  				Selector: validSelector,
 18613  			},
 18614  		},
 18615  		"read-write persistent disk with > 1 pod": {
 18616  			ObjectMeta: metav1.ObjectMeta{Name: "abc"},
 18617  			Spec: core.ReplicationControllerSpec{
 18618  				Replicas: 2,
 18619  				Selector: validSelector,
 18620  				Template: &readWriteVolumePodTemplate.Template,
 18621  			},
 18622  		},
 18623  		"negative_replicas": {
 18624  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18625  			Spec: core.ReplicationControllerSpec{
 18626  				Replicas: -1,
 18627  				Selector: validSelector,
 18628  			},
 18629  		},
 18630  		"invalid_label": {
 18631  			ObjectMeta: metav1.ObjectMeta{
 18632  				Name:      "abc-123",
 18633  				Namespace: metav1.NamespaceDefault,
 18634  				Labels: map[string]string{
 18635  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18636  				},
 18637  			},
 18638  			Spec: core.ReplicationControllerSpec{
 18639  				Selector: validSelector,
 18640  				Template: &validPodTemplate.Template,
 18641  			},
 18642  		},
 18643  		"invalid_label 2": {
 18644  			ObjectMeta: metav1.ObjectMeta{
 18645  				Name:      "abc-123",
 18646  				Namespace: metav1.NamespaceDefault,
 18647  				Labels: map[string]string{
 18648  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18649  				},
 18650  			},
 18651  			Spec: core.ReplicationControllerSpec{
 18652  				Template: &invalidPodTemplate.Template,
 18653  			},
 18654  		},
 18655  		"invalid_annotation": {
 18656  			ObjectMeta: metav1.ObjectMeta{
 18657  				Name:      "abc-123",
 18658  				Namespace: metav1.NamespaceDefault,
 18659  				Annotations: map[string]string{
 18660  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18661  				},
 18662  			},
 18663  			Spec: core.ReplicationControllerSpec{
 18664  				Selector: validSelector,
 18665  				Template: &validPodTemplate.Template,
 18666  			},
 18667  		},
 18668  		"invalid restart policy 1": {
 18669  			ObjectMeta: metav1.ObjectMeta{
 18670  				Name:      "abc-123",
 18671  				Namespace: metav1.NamespaceDefault,
 18672  			},
 18673  			Spec: core.ReplicationControllerSpec{
 18674  				Selector: validSelector,
 18675  				Template: &core.PodTemplateSpec{
 18676  					Spec: core.PodSpec{
 18677  						RestartPolicy: core.RestartPolicyOnFailure,
 18678  						DNSPolicy:     core.DNSClusterFirst,
 18679  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18680  					},
 18681  					ObjectMeta: metav1.ObjectMeta{
 18682  						Labels: validSelector,
 18683  					},
 18684  				},
 18685  			},
 18686  		},
 18687  		"invalid restart policy 2": {
 18688  			ObjectMeta: metav1.ObjectMeta{
 18689  				Name:      "abc-123",
 18690  				Namespace: metav1.NamespaceDefault,
 18691  			},
 18692  			Spec: core.ReplicationControllerSpec{
 18693  				Selector: validSelector,
 18694  				Template: &core.PodTemplateSpec{
 18695  					Spec: core.PodSpec{
 18696  						RestartPolicy: core.RestartPolicyNever,
 18697  						DNSPolicy:     core.DNSClusterFirst,
 18698  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18699  					},
 18700  					ObjectMeta: metav1.ObjectMeta{
 18701  						Labels: validSelector,
 18702  					},
 18703  				},
 18704  			},
 18705  		},
 18706  		"template may not contain ephemeral containers": {
 18707  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18708  			Spec: core.ReplicationControllerSpec{
 18709  				Replicas: 1,
 18710  				Selector: validSelector,
 18711  				Template: &core.PodTemplateSpec{
 18712  					ObjectMeta: metav1.ObjectMeta{
 18713  						Labels: validSelector,
 18714  					},
 18715  					Spec: core.PodSpec{
 18716  						RestartPolicy:       core.RestartPolicyAlways,
 18717  						DNSPolicy:           core.DNSClusterFirst,
 18718  						Containers:          []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18719  						EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
 18720  					},
 18721  				},
 18722  			},
 18723  		},
 18724  	}
 18725  	for k, v := range errorCases {
 18726  		errs := ValidateReplicationController(&v, PodValidationOptions{})
 18727  		if len(errs) == 0 {
 18728  			t.Errorf("expected failure for %s", k)
 18729  		}
 18730  		for i := range errs {
 18731  			field := errs[i].Field
 18732  			if !strings.HasPrefix(field, "spec.template.") &&
 18733  				field != "metadata.name" &&
 18734  				field != "metadata.namespace" &&
 18735  				field != "spec.selector" &&
 18736  				field != "spec.template" &&
 18737  				field != "GCEPersistentDisk.ReadOnly" &&
 18738  				field != "spec.replicas" &&
 18739  				field != "spec.template.labels" &&
 18740  				field != "metadata.annotations" &&
 18741  				field != "metadata.labels" &&
 18742  				field != "status.replicas" {
 18743  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
 18744  			}
 18745  		}
 18746  	}
 18747  }
 18748  
 18749  func TestValidateNode(t *testing.T) {
 18750  	validSelector := map[string]string{"a": "b"}
 18751  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18752  	successCases := []core.Node{{
 18753  		ObjectMeta: metav1.ObjectMeta{
 18754  			Name:   "abc",
 18755  			Labels: validSelector,
 18756  		},
 18757  		Status: core.NodeStatus{
 18758  			Addresses: []core.NodeAddress{
 18759  				{Type: core.NodeExternalIP, Address: "something"},
 18760  			},
 18761  			Capacity: core.ResourceList{
 18762  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18763  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18764  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 18765  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 18766  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("0"),
 18767  			},
 18768  		},
 18769  	}, {
 18770  		ObjectMeta: metav1.ObjectMeta{
 18771  			Name: "abc",
 18772  		},
 18773  		Status: core.NodeStatus{
 18774  			Addresses: []core.NodeAddress{
 18775  				{Type: core.NodeExternalIP, Address: "something"},
 18776  			},
 18777  			Capacity: core.ResourceList{
 18778  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18779  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18780  			},
 18781  		},
 18782  	}, {
 18783  		ObjectMeta: metav1.ObjectMeta{
 18784  			Name:   "abc",
 18785  			Labels: validSelector,
 18786  		},
 18787  		Status: core.NodeStatus{
 18788  			Addresses: []core.NodeAddress{
 18789  				{Type: core.NodeExternalIP, Address: "something"},
 18790  			},
 18791  			Capacity: core.ResourceList{
 18792  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18793  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18794  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 18795  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 18796  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("10Gi"),
 18797  			},
 18798  		},
 18799  	}, {
 18800  		ObjectMeta: metav1.ObjectMeta{
 18801  			Name: "dedicated-node1",
 18802  		},
 18803  		Status: core.NodeStatus{
 18804  			Addresses: []core.NodeAddress{
 18805  				{Type: core.NodeExternalIP, Address: "something"},
 18806  			},
 18807  			Capacity: core.ResourceList{
 18808  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18809  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18810  			},
 18811  		},
 18812  		Spec: core.NodeSpec{
 18813  			// Add a valid taint to a node
 18814  			Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}},
 18815  		},
 18816  	}, {
 18817  		ObjectMeta: metav1.ObjectMeta{
 18818  			Name: "abc",
 18819  			Annotations: map[string]string{
 18820  				core.PreferAvoidPodsAnnotationKey: `
 18821  							{
 18822  							    "preferAvoidPods": [
 18823  							        {
 18824  							            "podSignature": {
 18825  							                "podController": {
 18826  							                    "apiVersion": "v1",
 18827  							                    "kind": "ReplicationController",
 18828  							                    "name": "foo",
 18829  							                    "uid": "abcdef123456",
 18830  							                    "controller": true
 18831  							                }
 18832  							            },
 18833  							            "reason": "some reason",
 18834  							            "message": "some message"
 18835  							        }
 18836  							    ]
 18837  							}`,
 18838  			},
 18839  		},
 18840  		Status: core.NodeStatus{
 18841  			Addresses: []core.NodeAddress{
 18842  				{Type: core.NodeExternalIP, Address: "something"},
 18843  			},
 18844  			Capacity: core.ResourceList{
 18845  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18846  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18847  			},
 18848  		},
 18849  	}, {
 18850  		ObjectMeta: metav1.ObjectMeta{
 18851  			Name: "abc",
 18852  		},
 18853  		Status: core.NodeStatus{
 18854  			Addresses: []core.NodeAddress{
 18855  				{Type: core.NodeExternalIP, Address: "something"},
 18856  			},
 18857  			Capacity: core.ResourceList{
 18858  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18859  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18860  			},
 18861  		},
 18862  		Spec: core.NodeSpec{
 18863  			PodCIDRs: []string{"192.168.0.0/16"},
 18864  		},
 18865  	},
 18866  	}
 18867  	for _, successCase := range successCases {
 18868  		if errs := ValidateNode(&successCase); len(errs) != 0 {
 18869  			t.Errorf("expected success: %v", errs)
 18870  		}
 18871  	}
 18872  
 18873  	errorCases := map[string]core.Node{
 18874  		"zero-length Name": {
 18875  			ObjectMeta: metav1.ObjectMeta{
 18876  				Name:   "",
 18877  				Labels: validSelector,
 18878  			},
 18879  			Status: core.NodeStatus{
 18880  				Addresses: []core.NodeAddress{},
 18881  				Capacity: core.ResourceList{
 18882  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18883  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18884  				},
 18885  			},
 18886  		},
 18887  		"invalid-labels": {
 18888  			ObjectMeta: metav1.ObjectMeta{
 18889  				Name:   "abc-123",
 18890  				Labels: invalidSelector,
 18891  			},
 18892  			Status: core.NodeStatus{
 18893  				Capacity: core.ResourceList{
 18894  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18895  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18896  				},
 18897  			},
 18898  		},
 18899  		"missing-taint-key": {
 18900  			ObjectMeta: metav1.ObjectMeta{
 18901  				Name: "dedicated-node1",
 18902  			},
 18903  			Spec: core.NodeSpec{
 18904  				// Add a taint with an empty key to a node
 18905  				Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}},
 18906  			},
 18907  		},
 18908  		"bad-taint-key": {
 18909  			ObjectMeta: metav1.ObjectMeta{
 18910  				Name: "dedicated-node1",
 18911  			},
 18912  			Spec: core.NodeSpec{
 18913  				// Add a taint with an invalid  key to a node
 18914  				Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}},
 18915  			},
 18916  		},
 18917  		"bad-taint-value": {
 18918  			ObjectMeta: metav1.ObjectMeta{
 18919  				Name: "dedicated-node2",
 18920  			},
 18921  			Status: core.NodeStatus{
 18922  				Addresses: []core.NodeAddress{
 18923  					{Type: core.NodeExternalIP, Address: "something"},
 18924  				},
 18925  				Capacity: core.ResourceList{
 18926  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18927  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18928  				},
 18929  			},
 18930  			Spec: core.NodeSpec{
 18931  				// Add a taint with a bad value to a node
 18932  				Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}},
 18933  			},
 18934  		},
 18935  		"missing-taint-effect": {
 18936  			ObjectMeta: metav1.ObjectMeta{
 18937  				Name: "dedicated-node3",
 18938  			},
 18939  			Status: core.NodeStatus{
 18940  				Addresses: []core.NodeAddress{
 18941  					{Type: core.NodeExternalIP, Address: "something"},
 18942  				},
 18943  				Capacity: core.ResourceList{
 18944  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18945  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18946  				},
 18947  			},
 18948  			Spec: core.NodeSpec{
 18949  				// Add a taint with an empty effect to a node
 18950  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}},
 18951  			},
 18952  		},
 18953  		"invalid-taint-effect": {
 18954  			ObjectMeta: metav1.ObjectMeta{
 18955  				Name: "dedicated-node3",
 18956  			},
 18957  			Status: core.NodeStatus{
 18958  				Addresses: []core.NodeAddress{
 18959  					{Type: core.NodeExternalIP, Address: "something"},
 18960  				},
 18961  				Capacity: core.ResourceList{
 18962  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18963  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18964  				},
 18965  			},
 18966  			Spec: core.NodeSpec{
 18967  				// Add a taint with NoExecute effect to a node
 18968  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}},
 18969  			},
 18970  		},
 18971  		"duplicated-taints-with-same-key-effect": {
 18972  			ObjectMeta: metav1.ObjectMeta{
 18973  				Name: "dedicated-node1",
 18974  			},
 18975  			Spec: core.NodeSpec{
 18976  				// Add two taints to the node with the same key and effect; should be rejected.
 18977  				Taints: []core.Taint{
 18978  					{Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"},
 18979  					{Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"},
 18980  				},
 18981  			},
 18982  		},
 18983  		"missing-podSignature": {
 18984  			ObjectMeta: metav1.ObjectMeta{
 18985  				Name: "abc-123",
 18986  				Annotations: map[string]string{
 18987  					core.PreferAvoidPodsAnnotationKey: `
 18988  							{
 18989  							    "preferAvoidPods": [
 18990  							        {
 18991  							            "reason": "some reason",
 18992  							            "message": "some message"
 18993  							        }
 18994  							    ]
 18995  							}`,
 18996  				},
 18997  			},
 18998  			Status: core.NodeStatus{
 18999  				Addresses: []core.NodeAddress{},
 19000  				Capacity: core.ResourceList{
 19001  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19002  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19003  				},
 19004  			},
 19005  		},
 19006  		"invalid-podController": {
 19007  			ObjectMeta: metav1.ObjectMeta{
 19008  				Name: "abc-123",
 19009  				Annotations: map[string]string{
 19010  					core.PreferAvoidPodsAnnotationKey: `
 19011  							{
 19012  							    "preferAvoidPods": [
 19013  							        {
 19014  							            "podSignature": {
 19015  							                "podController": {
 19016  							                    "apiVersion": "v1",
 19017  							                    "kind": "ReplicationController",
 19018  							                    "name": "foo",
 19019                                                                             "uid": "abcdef123456",
 19020                                                                             "controller": false
 19021  							                }
 19022  							            },
 19023  							            "reason": "some reason",
 19024  							            "message": "some message"
 19025  							        }
 19026  							    ]
 19027  							}`,
 19028  				},
 19029  			},
 19030  			Status: core.NodeStatus{
 19031  				Addresses: []core.NodeAddress{},
 19032  				Capacity: core.ResourceList{
 19033  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19034  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19035  				},
 19036  			},
 19037  		},
 19038  		"invalid-pod-cidr": {
 19039  			ObjectMeta: metav1.ObjectMeta{
 19040  				Name: "abc",
 19041  			},
 19042  			Status: core.NodeStatus{
 19043  				Addresses: []core.NodeAddress{
 19044  					{Type: core.NodeExternalIP, Address: "something"},
 19045  				},
 19046  				Capacity: core.ResourceList{
 19047  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19048  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19049  				},
 19050  			},
 19051  			Spec: core.NodeSpec{
 19052  				PodCIDRs: []string{"192.168.0.0"},
 19053  			},
 19054  		},
 19055  		"duplicate-pod-cidr": {
 19056  			ObjectMeta: metav1.ObjectMeta{
 19057  				Name: "abc",
 19058  			},
 19059  			Status: core.NodeStatus{
 19060  				Addresses: []core.NodeAddress{
 19061  					{Type: core.NodeExternalIP, Address: "something"},
 19062  				},
 19063  				Capacity: core.ResourceList{
 19064  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19065  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19066  				},
 19067  			},
 19068  			Spec: core.NodeSpec{
 19069  				PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"},
 19070  			},
 19071  		},
 19072  	}
 19073  	for k, v := range errorCases {
 19074  		errs := ValidateNode(&v)
 19075  		if len(errs) == 0 {
 19076  			t.Errorf("expected failure for %s", k)
 19077  		}
 19078  		for i := range errs {
 19079  			field := errs[i].Field
 19080  			expectedFields := map[string]bool{
 19081  				"metadata.name":         true,
 19082  				"metadata.labels":       true,
 19083  				"metadata.annotations":  true,
 19084  				"metadata.namespace":    true,
 19085  				"spec.externalID":       true,
 19086  				"spec.taints[0].key":    true,
 19087  				"spec.taints[0].value":  true,
 19088  				"spec.taints[0].effect": true,
 19089  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature":                          true,
 19090  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true,
 19091  			}
 19092  			if val, ok := expectedFields[field]; ok {
 19093  				if !val {
 19094  					t.Errorf("%s: missing prefix for: %v", k, errs[i])
 19095  				}
 19096  			}
 19097  		}
 19098  	}
 19099  }
 19100  
 19101  func TestValidateNodeUpdate(t *testing.T) {
 19102  	tests := []struct {
 19103  		oldNode core.Node
 19104  		node    core.Node
 19105  		valid   bool
 19106  	}{
 19107  		{core.Node{}, core.Node{}, true},
 19108  		{core.Node{
 19109  			ObjectMeta: metav1.ObjectMeta{
 19110  				Name: "foo"}},
 19111  			core.Node{
 19112  				ObjectMeta: metav1.ObjectMeta{
 19113  					Name: "bar"},
 19114  			}, false},
 19115  		{core.Node{
 19116  			ObjectMeta: metav1.ObjectMeta{
 19117  				Name:   "foo",
 19118  				Labels: map[string]string{"foo": "bar"},
 19119  			},
 19120  		}, core.Node{
 19121  			ObjectMeta: metav1.ObjectMeta{
 19122  				Name:   "foo",
 19123  				Labels: map[string]string{"foo": "baz"},
 19124  			},
 19125  		}, true},
 19126  		{core.Node{
 19127  			ObjectMeta: metav1.ObjectMeta{
 19128  				Name: "foo",
 19129  			},
 19130  		}, core.Node{
 19131  			ObjectMeta: metav1.ObjectMeta{
 19132  				Name:   "foo",
 19133  				Labels: map[string]string{"foo": "baz"},
 19134  			},
 19135  		}, true},
 19136  		{core.Node{
 19137  			ObjectMeta: metav1.ObjectMeta{
 19138  				Name:   "foo",
 19139  				Labels: map[string]string{"bar": "foo"},
 19140  			},
 19141  		}, core.Node{
 19142  			ObjectMeta: metav1.ObjectMeta{
 19143  				Name:   "foo",
 19144  				Labels: map[string]string{"foo": "baz"},
 19145  			},
 19146  		}, true},
 19147  		{core.Node{
 19148  			ObjectMeta: metav1.ObjectMeta{
 19149  				Name: "foo",
 19150  			},
 19151  			Spec: core.NodeSpec{
 19152  				PodCIDRs: []string{},
 19153  			},
 19154  		}, core.Node{
 19155  			ObjectMeta: metav1.ObjectMeta{
 19156  				Name: "foo",
 19157  			},
 19158  			Spec: core.NodeSpec{
 19159  				PodCIDRs: []string{"192.168.0.0/16"},
 19160  			},
 19161  		}, true},
 19162  		{core.Node{
 19163  			ObjectMeta: metav1.ObjectMeta{
 19164  				Name: "foo",
 19165  			},
 19166  			Spec: core.NodeSpec{
 19167  				PodCIDRs: []string{"192.123.0.0/16"},
 19168  			},
 19169  		}, core.Node{
 19170  			ObjectMeta: metav1.ObjectMeta{
 19171  				Name: "foo",
 19172  			},
 19173  			Spec: core.NodeSpec{
 19174  				PodCIDRs: []string{"192.168.0.0/16"},
 19175  			},
 19176  		}, false},
 19177  		{core.Node{
 19178  			ObjectMeta: metav1.ObjectMeta{
 19179  				Name: "foo",
 19180  			},
 19181  			Status: core.NodeStatus{
 19182  				Capacity: core.ResourceList{
 19183  					core.ResourceCPU:    resource.MustParse("10000"),
 19184  					core.ResourceMemory: resource.MustParse("100"),
 19185  				},
 19186  			},
 19187  		}, core.Node{
 19188  			ObjectMeta: metav1.ObjectMeta{
 19189  				Name: "foo",
 19190  			},
 19191  			Status: core.NodeStatus{
 19192  				Capacity: core.ResourceList{
 19193  					core.ResourceCPU:    resource.MustParse("100"),
 19194  					core.ResourceMemory: resource.MustParse("10000"),
 19195  				},
 19196  			},
 19197  		}, true},
 19198  		{core.Node{
 19199  			ObjectMeta: metav1.ObjectMeta{
 19200  				Name:   "foo",
 19201  				Labels: map[string]string{"bar": "foo"},
 19202  			},
 19203  			Status: core.NodeStatus{
 19204  				Capacity: core.ResourceList{
 19205  					core.ResourceCPU:    resource.MustParse("10000"),
 19206  					core.ResourceMemory: resource.MustParse("100"),
 19207  				},
 19208  			},
 19209  		}, core.Node{
 19210  			ObjectMeta: metav1.ObjectMeta{
 19211  				Name:   "foo",
 19212  				Labels: map[string]string{"bar": "fooobaz"},
 19213  			},
 19214  			Status: core.NodeStatus{
 19215  				Capacity: core.ResourceList{
 19216  					core.ResourceCPU:    resource.MustParse("100"),
 19217  					core.ResourceMemory: resource.MustParse("10000"),
 19218  				},
 19219  			},
 19220  		}, true},
 19221  		{core.Node{
 19222  			ObjectMeta: metav1.ObjectMeta{
 19223  				Name:   "foo",
 19224  				Labels: map[string]string{"bar": "foo"},
 19225  			},
 19226  			Status: core.NodeStatus{
 19227  				Addresses: []core.NodeAddress{
 19228  					{Type: core.NodeExternalIP, Address: "1.2.3.4"},
 19229  				},
 19230  			},
 19231  		}, core.Node{
 19232  			ObjectMeta: metav1.ObjectMeta{
 19233  				Name:   "foo",
 19234  				Labels: map[string]string{"bar": "fooobaz"},
 19235  			},
 19236  		}, true},
 19237  		{core.Node{
 19238  			ObjectMeta: metav1.ObjectMeta{
 19239  				Name:   "foo",
 19240  				Labels: map[string]string{"foo": "baz"},
 19241  			},
 19242  		}, core.Node{
 19243  			ObjectMeta: metav1.ObjectMeta{
 19244  				Name:   "foo",
 19245  				Labels: map[string]string{"Foo": "baz"},
 19246  			},
 19247  		}, true},
 19248  		{core.Node{
 19249  			ObjectMeta: metav1.ObjectMeta{
 19250  				Name: "foo",
 19251  			},
 19252  			Spec: core.NodeSpec{
 19253  				Unschedulable: false,
 19254  			},
 19255  		}, core.Node{
 19256  			ObjectMeta: metav1.ObjectMeta{
 19257  				Name: "foo",
 19258  			},
 19259  			Spec: core.NodeSpec{
 19260  				Unschedulable: true,
 19261  			},
 19262  		}, true},
 19263  		{core.Node{
 19264  			ObjectMeta: metav1.ObjectMeta{
 19265  				Name: "foo",
 19266  			},
 19267  			Spec: core.NodeSpec{
 19268  				Unschedulable: false,
 19269  			},
 19270  		}, core.Node{
 19271  			ObjectMeta: metav1.ObjectMeta{
 19272  				Name: "foo",
 19273  			},
 19274  			Status: core.NodeStatus{
 19275  				Addresses: []core.NodeAddress{
 19276  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19277  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19278  				},
 19279  			},
 19280  		}, false},
 19281  		{core.Node{
 19282  			ObjectMeta: metav1.ObjectMeta{
 19283  				Name: "foo",
 19284  			},
 19285  			Spec: core.NodeSpec{
 19286  				Unschedulable: false,
 19287  			},
 19288  		}, core.Node{
 19289  			ObjectMeta: metav1.ObjectMeta{
 19290  				Name: "foo",
 19291  			},
 19292  			Status: core.NodeStatus{
 19293  				Addresses: []core.NodeAddress{
 19294  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19295  					{Type: core.NodeInternalIP, Address: "10.1.1.1"},
 19296  				},
 19297  			},
 19298  		}, true},
 19299  		{core.Node{
 19300  			ObjectMeta: metav1.ObjectMeta{
 19301  				Name: "foo",
 19302  			},
 19303  		}, core.Node{
 19304  			ObjectMeta: metav1.ObjectMeta{
 19305  				Name: "foo",
 19306  				Annotations: map[string]string{
 19307  					core.PreferAvoidPodsAnnotationKey: `
 19308  							{
 19309  							    "preferAvoidPods": [
 19310  							        {
 19311  							            "podSignature": {
 19312  							                "podController": {
 19313  							                    "apiVersion": "v1",
 19314  							                    "kind": "ReplicationController",
 19315  							                    "name": "foo",
 19316                                                                             "uid": "abcdef123456",
 19317                                                                             "controller": true
 19318  							                }
 19319  							            },
 19320  							            "reason": "some reason",
 19321  							            "message": "some message"
 19322  							        }
 19323  							    ]
 19324  							}`,
 19325  				},
 19326  			},
 19327  			Spec: core.NodeSpec{
 19328  				Unschedulable: false,
 19329  			},
 19330  		}, true},
 19331  		{core.Node{
 19332  			ObjectMeta: metav1.ObjectMeta{
 19333  				Name: "foo",
 19334  			},
 19335  		}, core.Node{
 19336  			ObjectMeta: metav1.ObjectMeta{
 19337  				Name: "foo",
 19338  				Annotations: map[string]string{
 19339  					core.PreferAvoidPodsAnnotationKey: `
 19340  							{
 19341  							    "preferAvoidPods": [
 19342  							        {
 19343  							            "reason": "some reason",
 19344  							            "message": "some message"
 19345  							        }
 19346  							    ]
 19347  							}`,
 19348  				},
 19349  			},
 19350  		}, false},
 19351  		{core.Node{
 19352  			ObjectMeta: metav1.ObjectMeta{
 19353  				Name: "foo",
 19354  			},
 19355  		}, core.Node{
 19356  			ObjectMeta: metav1.ObjectMeta{
 19357  				Name: "foo",
 19358  				Annotations: map[string]string{
 19359  					core.PreferAvoidPodsAnnotationKey: `
 19360  							{
 19361  							    "preferAvoidPods": [
 19362  							        {
 19363  							            "podSignature": {
 19364  							                "podController": {
 19365  							                    "apiVersion": "v1",
 19366  							                    "kind": "ReplicationController",
 19367  							                    "name": "foo",
 19368  							                    "uid": "abcdef123456",
 19369  							                    "controller": false
 19370  							                }
 19371  							            },
 19372  							            "reason": "some reason",
 19373  							            "message": "some message"
 19374  							        }
 19375  							    ]
 19376  							}`,
 19377  				},
 19378  			},
 19379  		}, false},
 19380  		{core.Node{
 19381  			ObjectMeta: metav1.ObjectMeta{
 19382  				Name: "valid-extended-resources",
 19383  			},
 19384  		}, core.Node{
 19385  			ObjectMeta: metav1.ObjectMeta{
 19386  				Name: "valid-extended-resources",
 19387  			},
 19388  			Status: core.NodeStatus{
 19389  				Capacity: core.ResourceList{
 19390  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19391  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19392  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 19393  					core.ResourceName("example.com/b"):     resource.MustParse("10"),
 19394  				},
 19395  			},
 19396  		}, true},
 19397  		{core.Node{
 19398  			ObjectMeta: metav1.ObjectMeta{
 19399  				Name: "invalid-fractional-extended-capacity",
 19400  			},
 19401  		}, core.Node{
 19402  			ObjectMeta: metav1.ObjectMeta{
 19403  				Name: "invalid-fractional-extended-capacity",
 19404  			},
 19405  			Status: core.NodeStatus{
 19406  				Capacity: core.ResourceList{
 19407  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19408  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19409  					core.ResourceName("example.com/a"):     resource.MustParse("500m"),
 19410  				},
 19411  			},
 19412  		}, false},
 19413  		{core.Node{
 19414  			ObjectMeta: metav1.ObjectMeta{
 19415  				Name: "invalid-fractional-extended-allocatable",
 19416  			},
 19417  		}, core.Node{
 19418  			ObjectMeta: metav1.ObjectMeta{
 19419  				Name: "invalid-fractional-extended-allocatable",
 19420  			},
 19421  			Status: core.NodeStatus{
 19422  				Capacity: core.ResourceList{
 19423  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19424  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19425  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 19426  				},
 19427  				Allocatable: core.ResourceList{
 19428  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19429  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19430  					core.ResourceName("example.com/a"):     resource.MustParse("4.5"),
 19431  				},
 19432  			},
 19433  		}, false},
 19434  		{core.Node{
 19435  			ObjectMeta: metav1.ObjectMeta{
 19436  				Name: "update-provider-id-when-not-set",
 19437  			},
 19438  		}, core.Node{
 19439  			ObjectMeta: metav1.ObjectMeta{
 19440  				Name: "update-provider-id-when-not-set",
 19441  			},
 19442  			Spec: core.NodeSpec{
 19443  				ProviderID: "provider:///new",
 19444  			},
 19445  		}, true},
 19446  		{core.Node{
 19447  			ObjectMeta: metav1.ObjectMeta{
 19448  				Name: "update-provider-id-when-set",
 19449  			},
 19450  			Spec: core.NodeSpec{
 19451  				ProviderID: "provider:///old",
 19452  			},
 19453  		}, core.Node{
 19454  			ObjectMeta: metav1.ObjectMeta{
 19455  				Name: "update-provider-id-when-set",
 19456  			},
 19457  			Spec: core.NodeSpec{
 19458  				ProviderID: "provider:///new",
 19459  			},
 19460  		}, false},
 19461  		{core.Node{
 19462  			ObjectMeta: metav1.ObjectMeta{
 19463  				Name: "pod-cidrs-as-is",
 19464  			},
 19465  			Spec: core.NodeSpec{
 19466  				PodCIDRs: []string{"192.168.0.0/16"},
 19467  			},
 19468  		}, core.Node{
 19469  			ObjectMeta: metav1.ObjectMeta{
 19470  				Name: "pod-cidrs-as-is",
 19471  			},
 19472  			Spec: core.NodeSpec{
 19473  				PodCIDRs: []string{"192.168.0.0/16"},
 19474  			},
 19475  		}, true},
 19476  		{core.Node{
 19477  			ObjectMeta: metav1.ObjectMeta{
 19478  				Name: "pod-cidrs-as-is-2",
 19479  			},
 19480  			Spec: core.NodeSpec{
 19481  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19482  			},
 19483  		}, core.Node{
 19484  			ObjectMeta: metav1.ObjectMeta{
 19485  				Name: "pod-cidrs-as-is-2",
 19486  			},
 19487  			Spec: core.NodeSpec{
 19488  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19489  			},
 19490  		}, true},
 19491  		{core.Node{
 19492  			ObjectMeta: metav1.ObjectMeta{
 19493  				Name: "pod-cidrs-not-same-length",
 19494  			},
 19495  			Spec: core.NodeSpec{
 19496  				PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"},
 19497  			},
 19498  		}, core.Node{
 19499  			ObjectMeta: metav1.ObjectMeta{
 19500  				Name: "pod-cidrs-not-same-length",
 19501  			},
 19502  			Spec: core.NodeSpec{
 19503  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19504  			},
 19505  		}, false},
 19506  		{core.Node{
 19507  			ObjectMeta: metav1.ObjectMeta{
 19508  				Name: "pod-cidrs-not-same",
 19509  			},
 19510  			Spec: core.NodeSpec{
 19511  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19512  			},
 19513  		}, core.Node{
 19514  			ObjectMeta: metav1.ObjectMeta{
 19515  				Name: "pod-cidrs-not-same",
 19516  			},
 19517  			Spec: core.NodeSpec{
 19518  				PodCIDRs: []string{"2000::/10", "192.168.0.0/16"},
 19519  			},
 19520  		}, false},
 19521  	}
 19522  	for i, test := range tests {
 19523  		test.oldNode.ObjectMeta.ResourceVersion = "1"
 19524  		test.node.ObjectMeta.ResourceVersion = "1"
 19525  		errs := ValidateNodeUpdate(&test.node, &test.oldNode)
 19526  		if test.valid && len(errs) > 0 {
 19527  			t.Errorf("%d: Unexpected error: %v", i, errs)
 19528  			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
 19529  		}
 19530  		if !test.valid && len(errs) == 0 {
 19531  			t.Errorf("%d: Unexpected non-error", i)
 19532  		}
 19533  	}
 19534  }
 19535  
 19536  func TestValidateServiceUpdate(t *testing.T) {
 19537  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 19538  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 19539  	singleStack := core.IPFamilyPolicySingleStack
 19540  	testCases := []struct {
 19541  		name     string
 19542  		tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
 19543  		numErrs  int
 19544  	}{{
 19545  		name: "no change",
 19546  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19547  			// do nothing
 19548  		},
 19549  		numErrs: 0,
 19550  	}, {
 19551  		name: "change name",
 19552  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19553  			newSvc.Name += "2"
 19554  		},
 19555  		numErrs: 1,
 19556  	}, {
 19557  		name: "change namespace",
 19558  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19559  			newSvc.Namespace += "2"
 19560  		},
 19561  		numErrs: 1,
 19562  	}, {
 19563  		name: "change label valid",
 19564  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19565  			newSvc.Labels["key"] = "other-value"
 19566  		},
 19567  		numErrs: 0,
 19568  	}, {
 19569  		name: "add label",
 19570  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19571  			newSvc.Labels["key2"] = "value2"
 19572  		},
 19573  		numErrs: 0,
 19574  	}, {
 19575  		name: "change cluster IP",
 19576  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19577  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19578  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19579  
 19580  			newSvc.Spec.ClusterIP = "8.6.7.5"
 19581  			newSvc.Spec.ClusterIPs = []string{"8.6.7.5"}
 19582  		},
 19583  		numErrs: 1,
 19584  	}, {
 19585  		name: "remove cluster IP",
 19586  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19587  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19588  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19589  
 19590  			newSvc.Spec.ClusterIP = ""
 19591  			newSvc.Spec.ClusterIPs = nil
 19592  		},
 19593  		numErrs: 1,
 19594  	}, {
 19595  		name: "change affinity",
 19596  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19597  			newSvc.Spec.SessionAffinity = "ClientIP"
 19598  			newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 19599  				ClientIP: &core.ClientIPConfig{
 19600  					TimeoutSeconds: utilpointer.Int32(90),
 19601  				},
 19602  			}
 19603  		},
 19604  		numErrs: 0,
 19605  	}, {
 19606  		name: "remove affinity",
 19607  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19608  			newSvc.Spec.SessionAffinity = ""
 19609  		},
 19610  		numErrs: 1,
 19611  	}, {
 19612  		name: "change type",
 19613  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19614  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19615  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19616  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19617  		},
 19618  		numErrs: 0,
 19619  	}, {
 19620  		name: "remove type",
 19621  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19622  			newSvc.Spec.Type = ""
 19623  		},
 19624  		numErrs: 1,
 19625  	}, {
 19626  		name: "change type -> nodeport",
 19627  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19628  			newSvc.Spec.Type = core.ServiceTypeNodePort
 19629  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19630  		},
 19631  		numErrs: 0,
 19632  	}, {
 19633  		name: "add loadBalancerSourceRanges",
 19634  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19635  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19636  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19637  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19638  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19639  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19640  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 19641  		},
 19642  		numErrs: 0,
 19643  	}, {
 19644  		name: "update loadBalancerSourceRanges",
 19645  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19646  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19647  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19648  			oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 19649  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19650  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19651  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19652  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"}
 19653  		},
 19654  		numErrs: 0,
 19655  	}, {
 19656  		name: "LoadBalancer type cannot have None ClusterIP",
 19657  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19658  			newSvc.Spec.ClusterIP = "None"
 19659  			newSvc.Spec.ClusterIPs = []string{"None"}
 19660  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19661  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19662  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19663  		},
 19664  		numErrs: 1,
 19665  	}, {
 19666  		name: "`None` ClusterIP can NOT be changed",
 19667  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19668  			oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19669  			newSvc.Spec.Type = core.ServiceTypeClusterIP
 19670  
 19671  			oldSvc.Spec.ClusterIP = "None"
 19672  			oldSvc.Spec.ClusterIPs = []string{"None"}
 19673  
 19674  			newSvc.Spec.ClusterIP = "1.2.3.4"
 19675  			newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19676  		},
 19677  		numErrs: 1,
 19678  	}, {
 19679  		name: "`None` ClusterIP can NOT be removed",
 19680  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19681  			oldSvc.Spec.ClusterIP = "None"
 19682  			oldSvc.Spec.ClusterIPs = []string{"None"}
 19683  
 19684  			newSvc.Spec.ClusterIP = ""
 19685  			newSvc.Spec.ClusterIPs = nil
 19686  		},
 19687  		numErrs: 1,
 19688  	}, {
 19689  		name: "ClusterIP can NOT be changed to None",
 19690  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19691  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19692  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19693  
 19694  			newSvc.Spec.ClusterIP = "None"
 19695  			newSvc.Spec.ClusterIPs = []string{"None"}
 19696  		},
 19697  		numErrs: 1,
 19698  	},
 19699  
 19700  		{
 19701  			name: "Service with ClusterIP type cannot change its set ClusterIP",
 19702  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19703  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19704  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19705  
 19706  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19707  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19708  
 19709  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19710  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19711  			},
 19712  			numErrs: 1,
 19713  		}, {
 19714  			name: "Service with ClusterIP type can change its empty ClusterIP",
 19715  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19716  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19717  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19718  
 19719  				oldSvc.Spec.ClusterIP = ""
 19720  				oldSvc.Spec.ClusterIPs = nil
 19721  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19722  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19723  			},
 19724  			numErrs: 0,
 19725  		}, {
 19726  			name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort",
 19727  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19728  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19729  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19730  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19731  
 19732  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19733  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19734  
 19735  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19736  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19737  			},
 19738  			numErrs: 1,
 19739  		}, {
 19740  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort",
 19741  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19742  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19743  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19744  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19745  
 19746  				oldSvc.Spec.ClusterIP = ""
 19747  				oldSvc.Spec.ClusterIPs = nil
 19748  
 19749  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19750  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19751  			},
 19752  			numErrs: 0,
 19753  		}, {
 19754  			name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer",
 19755  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19756  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19757  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19758  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19759  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19760  
 19761  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19762  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19763  
 19764  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19765  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19766  			},
 19767  			numErrs: 1,
 19768  		}, {
 19769  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer",
 19770  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19771  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19772  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19773  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19774  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19775  
 19776  				oldSvc.Spec.ClusterIP = ""
 19777  				oldSvc.Spec.ClusterIPs = nil
 19778  
 19779  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19780  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19781  			},
 19782  			numErrs: 0,
 19783  		}, {
 19784  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false",
 19785  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19786  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19787  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19788  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19789  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19790  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 19791  			},
 19792  			numErrs: 0,
 19793  		}, {
 19794  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true",
 19795  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19796  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19797  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 19798  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19799  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19800  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19801  			},
 19802  			numErrs: 0,
 19803  		}, {
 19804  			name: "Service with NodePort type cannot change its set ClusterIP",
 19805  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19806  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19807  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19808  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19809  
 19810  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19811  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19812  
 19813  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19814  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19815  			},
 19816  			numErrs: 1,
 19817  		}, {
 19818  			name: "Service with NodePort type can change its empty ClusterIP",
 19819  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19820  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19821  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19822  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19823  
 19824  				oldSvc.Spec.ClusterIP = ""
 19825  				oldSvc.Spec.ClusterIPs = nil
 19826  
 19827  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19828  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19829  			},
 19830  			numErrs: 0,
 19831  		}, {
 19832  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP",
 19833  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19834  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19835  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19836  
 19837  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19838  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19839  
 19840  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19841  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19842  			},
 19843  			numErrs: 1,
 19844  		}, {
 19845  			name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP",
 19846  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19847  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19848  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19849  
 19850  				oldSvc.Spec.ClusterIP = ""
 19851  				oldSvc.Spec.ClusterIPs = nil
 19852  
 19853  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19854  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19855  			},
 19856  			numErrs: 0,
 19857  		}, {
 19858  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer",
 19859  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19860  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19861  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19862  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19863  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19864  
 19865  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19866  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19867  
 19868  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19869  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19870  			},
 19871  			numErrs: 1,
 19872  		}, {
 19873  			name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer",
 19874  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19875  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19876  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19877  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19878  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19879  
 19880  				oldSvc.Spec.ClusterIP = ""
 19881  				oldSvc.Spec.ClusterIPs = nil
 19882  
 19883  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19884  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19885  			},
 19886  			numErrs: 0,
 19887  		}, {
 19888  			name: "Service with LoadBalancer type cannot change its set ClusterIP",
 19889  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19890  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19891  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19892  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19893  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19894  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19895  
 19896  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19897  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19898  
 19899  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19900  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19901  			},
 19902  			numErrs: 1,
 19903  		}, {
 19904  			name: "Service with LoadBalancer type can change its empty ClusterIP",
 19905  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19906  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19907  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19908  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19909  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19910  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19911  
 19912  				oldSvc.Spec.ClusterIP = ""
 19913  				oldSvc.Spec.ClusterIPs = nil
 19914  
 19915  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19916  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19917  			},
 19918  			numErrs: 0,
 19919  		}, {
 19920  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP",
 19921  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19922  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19923  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19924  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19925  
 19926  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19927  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19928  
 19929  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19930  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19931  			},
 19932  			numErrs: 1,
 19933  		}, {
 19934  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP",
 19935  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19936  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19937  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19938  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19939  
 19940  				oldSvc.Spec.ClusterIP = ""
 19941  				oldSvc.Spec.ClusterIPs = nil
 19942  
 19943  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19944  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19945  			},
 19946  			numErrs: 0,
 19947  		}, {
 19948  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort",
 19949  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19950  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19951  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19952  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19953  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19954  
 19955  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19956  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19957  
 19958  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19959  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19960  			},
 19961  			numErrs: 1,
 19962  		}, {
 19963  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort",
 19964  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19965  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19966  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19967  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19968  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19969  
 19970  				oldSvc.Spec.ClusterIP = ""
 19971  				oldSvc.Spec.ClusterIPs = nil
 19972  
 19973  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19974  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19975  			},
 19976  			numErrs: 0,
 19977  		}, {
 19978  			name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP",
 19979  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19980  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19981  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19982  
 19983  				oldSvc.Spec.ClusterIP = ""
 19984  				oldSvc.Spec.ClusterIPs = nil
 19985  
 19986  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19987  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19988  			},
 19989  			numErrs: 0,
 19990  		}, {
 19991  			name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP",
 19992  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19993  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19994  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19995  
 19996  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19997  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19998  
 19999  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20000  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20001  			},
 20002  			numErrs: 0,
 20003  		}, {
 20004  			name: "invalid node port with clusterIP None",
 20005  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20006  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 20007  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20008  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20009  
 20010  				oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 20011  				newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 20012  
 20013  				oldSvc.Spec.ClusterIP = ""
 20014  				oldSvc.Spec.ClusterIPs = nil
 20015  
 20016  				newSvc.Spec.ClusterIP = "None"
 20017  				newSvc.Spec.ClusterIPs = []string{"None"}
 20018  			},
 20019  			numErrs: 1,
 20020  		},
 20021  		/* Service IP Family */
 20022  		{
 20023  			name: "convert from ExternalName",
 20024  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20025  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20026  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20027  			},
 20028  			numErrs: 0,
 20029  		}, {
 20030  			name: "invalid: convert to ExternalName",
 20031  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20032  				singleStack := core.IPFamilyPolicySingleStack
 20033  
 20034  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20035  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 20036  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20037  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20038  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20039  
 20040  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20041  				newSvc.Spec.ExternalName = "foo"
 20042  				/*
 20043  					not removing these fields is a validation error
 20044  					strategy takes care of resetting Families & Policy if ClusterIPs
 20045  					were reset. But it does not get called in validation testing.
 20046  				*/
 20047  				newSvc.Spec.ClusterIP = "10.0.0.10"
 20048  				newSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20049  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20050  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20051  
 20052  			},
 20053  			numErrs: 3,
 20054  		}, {
 20055  			name: "valid: convert to ExternalName",
 20056  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20057  				singleStack := core.IPFamilyPolicySingleStack
 20058  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20059  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 20060  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20061  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20062  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20063  
 20064  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20065  				newSvc.Spec.ExternalName = "foo"
 20066  			},
 20067  			numErrs: 0,
 20068  		},
 20069  
 20070  		{
 20071  			name: "same ServiceIPFamily",
 20072  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20073  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20074  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20075  
 20076  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20077  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20078  			},
 20079  			numErrs: 0,
 20080  		}, {
 20081  			name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack",
 20082  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20083  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20084  				oldSvc.Spec.IPFamilyPolicy = nil
 20085  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20086  
 20087  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20088  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20089  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20090  			},
 20091  			numErrs: 0,
 20092  		}, {
 20093  			name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack",
 20094  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20095  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20096  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20097  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20098  
 20099  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20100  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20101  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20102  			},
 20103  			numErrs: 0,
 20104  		},
 20105  
 20106  		{
 20107  			name: "add a new ServiceIPFamily",
 20108  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20109  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20110  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20111  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20112  
 20113  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20114  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20115  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20116  			},
 20117  			numErrs: 0,
 20118  		},
 20119  
 20120  		{
 20121  			name: "ExternalName while changing Service IPFamily",
 20122  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20123  				oldSvc.Spec.ExternalName = "somename"
 20124  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20125  
 20126  				newSvc.Spec.ExternalName = "somename"
 20127  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20128  			},
 20129  			numErrs: 0,
 20130  		}, {
 20131  			name: "setting ipfamily from nil to v4",
 20132  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20133  				oldSvc.Spec.IPFamilies = nil
 20134  
 20135  				newSvc.Spec.ExternalName = "somename"
 20136  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20137  			},
 20138  			numErrs: 0,
 20139  		}, {
 20140  			name: "setting ipfamily from nil to v6",
 20141  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20142  				oldSvc.Spec.IPFamilies = nil
 20143  
 20144  				newSvc.Spec.ExternalName = "somename"
 20145  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20146  			},
 20147  			numErrs: 0,
 20148  		}, {
 20149  			name: "change primary ServiceIPFamily",
 20150  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20151  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20152  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20153  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20154  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20155  
 20156  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20157  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20158  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20159  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20160  			},
 20161  			numErrs: 2,
 20162  		},
 20163  		/* upgrade + downgrade from/to dualstack tests */
 20164  		{
 20165  			name: "valid: upgrade to dual stack with requiredDualStack",
 20166  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20167  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20168  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20169  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20170  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20171  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20172  
 20173  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20174  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20175  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20176  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20177  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20178  			},
 20179  			numErrs: 0,
 20180  		}, {
 20181  			name: "valid: upgrade to dual stack with preferDualStack",
 20182  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20183  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20184  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20185  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20186  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20187  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20188  
 20189  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20190  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20191  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20192  				newSvc.Spec.IPFamilyPolicy = &preferDualStack
 20193  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20194  			},
 20195  			numErrs: 0,
 20196  		},
 20197  
 20198  		{
 20199  			name: "valid: upgrade to dual stack, no specific secondary ip",
 20200  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20201  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20202  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20203  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20204  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20205  
 20206  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20207  
 20208  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20209  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20210  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20211  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20212  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20213  			},
 20214  			numErrs: 0,
 20215  		}, {
 20216  			name: "valid: upgrade to dual stack, with specific secondary ip",
 20217  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20218  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20219  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20220  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20221  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20222  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20223  
 20224  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20225  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20226  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20227  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20228  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20229  			},
 20230  			numErrs: 0,
 20231  		}, {
 20232  			name: "valid: downgrade from dual to single",
 20233  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20234  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20235  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20236  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20237  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20238  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20239  
 20240  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20241  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20242  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20243  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20244  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20245  			},
 20246  			numErrs: 0,
 20247  		}, {
 20248  			name: "valid: change families for a headless service",
 20249  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20250  				oldSvc.Spec.ClusterIP = "None"
 20251  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20252  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20253  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20254  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20255  
 20256  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20257  				newSvc.Spec.ClusterIP = "None"
 20258  				newSvc.Spec.ClusterIPs = []string{"None"}
 20259  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20260  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20261  			},
 20262  			numErrs: 0,
 20263  		}, {
 20264  			name: "valid: upgrade a headless service",
 20265  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20266  				oldSvc.Spec.ClusterIP = "None"
 20267  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20268  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20269  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20270  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20271  
 20272  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20273  				newSvc.Spec.ClusterIP = "None"
 20274  				newSvc.Spec.ClusterIPs = []string{"None"}
 20275  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20276  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20277  			},
 20278  			numErrs: 0,
 20279  		}, {
 20280  			name: "valid: downgrade a headless service",
 20281  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20282  				oldSvc.Spec.ClusterIP = "None"
 20283  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20284  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20285  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20286  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20287  
 20288  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20289  				newSvc.Spec.ClusterIP = "None"
 20290  				newSvc.Spec.ClusterIPs = []string{"None"}
 20291  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20292  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20293  			},
 20294  			numErrs: 0,
 20295  		},
 20296  
 20297  		{
 20298  			name: "invalid flip families",
 20299  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20300  				oldSvc.Spec.ClusterIP = "1.2.3.40"
 20301  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20302  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20303  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20304  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20305  
 20306  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20307  				newSvc.Spec.ClusterIP = "2001::1"
 20308  				newSvc.Spec.ClusterIPs = []string{"2001::1", "1.2.3.5"}
 20309  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20310  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20311  			},
 20312  			numErrs: 4,
 20313  		}, {
 20314  			name: "invalid change first ip, in dualstack service",
 20315  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20316  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20317  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20318  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20319  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20320  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20321  
 20322  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20323  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20324  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5", "2001::1"}
 20325  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20326  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20327  			},
 20328  			numErrs: 1,
 20329  		}, {
 20330  			name: "invalid, change second ip in dualstack service",
 20331  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20332  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20333  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20334  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20335  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20336  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20337  
 20338  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20339  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20340  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2002::1"}
 20341  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20342  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20343  			},
 20344  			numErrs: 1,
 20345  		}, {
 20346  			name: "downgrade keeping the families",
 20347  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20348  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20349  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20350  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20351  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20352  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20353  
 20354  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20355  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20356  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20357  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20358  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20359  			},
 20360  			numErrs: 0, // families and ips are trimmed in strategy
 20361  		}, {
 20362  			name: "invalid, downgrade without changing to singleStack",
 20363  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20364  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20365  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20366  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20367  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20368  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20369  
 20370  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20371  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20372  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20373  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20374  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20375  			},
 20376  			numErrs: 2,
 20377  		}, {
 20378  			name: "invalid, downgrade and change primary ip",
 20379  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20380  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20381  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20382  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20383  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20384  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20385  
 20386  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20387  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20388  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20389  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20390  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20391  			},
 20392  			numErrs: 1,
 20393  		}, {
 20394  			name: "invalid: upgrade to dual stack and change primary",
 20395  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20396  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20397  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20398  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20399  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20400  
 20401  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20402  
 20403  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20404  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20405  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20406  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20407  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20408  			},
 20409  			numErrs: 1,
 20410  		}, {
 20411  			name: "update to valid app protocol",
 20412  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20413  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 20414  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 20415  			},
 20416  			numErrs: 0,
 20417  		}, {
 20418  			name: "update to invalid app protocol",
 20419  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20420  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 20421  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 20422  			},
 20423  			numErrs: 1,
 20424  		}, {
 20425  			name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 20426  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20427  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20428  			},
 20429  			numErrs: 1,
 20430  		}, {
 20431  			name: "update LoadBalancer type of service without change LoadBalancerClass",
 20432  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20433  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20434  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20435  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20436  
 20437  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20438  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20439  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20440  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20441  			},
 20442  			numErrs: 0,
 20443  		}, {
 20444  			name: "invalid: change LoadBalancerClass when update service",
 20445  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20446  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20447  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20448  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20449  
 20450  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20451  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20452  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20453  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 20454  			},
 20455  			numErrs: 1,
 20456  		}, {
 20457  			name: "invalid: unset LoadBalancerClass when update service",
 20458  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20459  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20460  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20461  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20462  
 20463  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20464  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20465  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20466  				newSvc.Spec.LoadBalancerClass = nil
 20467  			},
 20468  			numErrs: 1,
 20469  		}, {
 20470  			name: "invalid: set LoadBalancerClass when update service",
 20471  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20472  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20473  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20474  				oldSvc.Spec.LoadBalancerClass = nil
 20475  
 20476  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20477  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20478  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20479  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 20480  			},
 20481  			numErrs: 1,
 20482  		}, {
 20483  			name: "update to LoadBalancer type of service with valid LoadBalancerClass",
 20484  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20485  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20486  
 20487  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20488  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20489  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20490  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20491  			},
 20492  			numErrs: 0,
 20493  		}, {
 20494  			name: "update to LoadBalancer type of service without LoadBalancerClass",
 20495  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20496  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20497  
 20498  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20499  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20500  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20501  				newSvc.Spec.LoadBalancerClass = nil
 20502  			},
 20503  			numErrs: 0,
 20504  		}, {
 20505  			name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer",
 20506  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20507  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20508  
 20509  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20510  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20511  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20512  				newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass")
 20513  			},
 20514  			numErrs: 2,
 20515  		}, {
 20516  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20517  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20518  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20519  
 20520  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20521  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20522  			},
 20523  			numErrs: 2,
 20524  		}, {
 20525  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20526  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20527  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20528  
 20529  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20530  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20531  			},
 20532  			numErrs: 3,
 20533  		}, {
 20534  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20535  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20536  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 20537  
 20538  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20539  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20540  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20541  			},
 20542  			numErrs: 2,
 20543  		}, {
 20544  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20545  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20546  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20547  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20548  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20549  
 20550  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20551  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20552  			},
 20553  			numErrs: 2,
 20554  		}, {
 20555  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20556  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20557  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20558  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20559  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20560  
 20561  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20562  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20563  			},
 20564  			numErrs: 3,
 20565  		}, {
 20566  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20567  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20568  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20569  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20570  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20571  
 20572  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20573  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20574  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20575  			},
 20576  			numErrs: 2,
 20577  		}, {
 20578  			name: "update internalTrafficPolicy from Cluster to Local",
 20579  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20580  				cluster := core.ServiceInternalTrafficPolicyCluster
 20581  				oldSvc.Spec.InternalTrafficPolicy = &cluster
 20582  
 20583  				local := core.ServiceInternalTrafficPolicyLocal
 20584  				newSvc.Spec.InternalTrafficPolicy = &local
 20585  			},
 20586  			numErrs: 0,
 20587  		}, {
 20588  			name: "update internalTrafficPolicy from Local to Cluster",
 20589  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20590  				local := core.ServiceInternalTrafficPolicyLocal
 20591  				oldSvc.Spec.InternalTrafficPolicy = &local
 20592  
 20593  				cluster := core.ServiceInternalTrafficPolicyCluster
 20594  				newSvc.Spec.InternalTrafficPolicy = &cluster
 20595  			},
 20596  			numErrs: 0,
 20597  		}, {
 20598  			name: "topology annotations are mismatched",
 20599  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20600  				newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 20601  				newSvc.Annotations[core.AnnotationTopologyMode] = "different"
 20602  			},
 20603  			numErrs: 1,
 20604  		},
 20605  	}
 20606  
 20607  	for _, tc := range testCases {
 20608  		t.Run(tc.name, func(t *testing.T) {
 20609  			oldSvc := makeValidService()
 20610  			newSvc := makeValidService()
 20611  			tc.tweakSvc(&oldSvc, &newSvc)
 20612  			errs := ValidateServiceUpdate(&newSvc, &oldSvc)
 20613  			if len(errs) != tc.numErrs {
 20614  				t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate())
 20615  			}
 20616  		})
 20617  	}
 20618  }
 20619  
 20620  func TestValidateResourceNames(t *testing.T) {
 20621  	table := []struct {
 20622  		input   core.ResourceName
 20623  		success bool
 20624  		expect  string
 20625  	}{
 20626  		{"memory", true, ""},
 20627  		{"cpu", true, ""},
 20628  		{"storage", true, ""},
 20629  		{"requests.cpu", true, ""},
 20630  		{"requests.memory", true, ""},
 20631  		{"requests.storage", true, ""},
 20632  		{"limits.cpu", true, ""},
 20633  		{"limits.memory", true, ""},
 20634  		{"network", false, ""},
 20635  		{"disk", false, ""},
 20636  		{"", false, ""},
 20637  		{".", false, ""},
 20638  		{"..", false, ""},
 20639  		{"my.favorite.app.co/12345", true, ""},
 20640  		{"my.favorite.app.co/_12345", false, ""},
 20641  		{"my.favorite.app.co/12345_", false, ""},
 20642  		{"kubernetes.io/..", false, ""},
 20643  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)), true, ""},
 20644  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 64)), false, ""},
 20645  		{"kubernetes.io//", false, ""},
 20646  		{"kubernetes.io", false, ""},
 20647  		{"kubernetes.io/will/not/work/", false, ""},
 20648  	}
 20649  	for k, item := range table {
 20650  		err := validateResourceName(item.input, field.NewPath("field"))
 20651  		if len(err) != 0 && item.success {
 20652  			t.Errorf("expected no failure for input %q", item.input)
 20653  		} else if len(err) == 0 && !item.success {
 20654  			t.Errorf("expected failure for input %q", item.input)
 20655  			for i := range err {
 20656  				detail := err[i].Detail
 20657  				if detail != "" && !strings.Contains(detail, item.expect) {
 20658  					t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail)
 20659  				}
 20660  			}
 20661  		}
 20662  	}
 20663  }
 20664  
 20665  func getResourceList(cpu, memory string) core.ResourceList {
 20666  	res := core.ResourceList{}
 20667  	if cpu != "" {
 20668  		res[core.ResourceCPU] = resource.MustParse(cpu)
 20669  	}
 20670  	if memory != "" {
 20671  		res[core.ResourceMemory] = resource.MustParse(memory)
 20672  	}
 20673  	return res
 20674  }
 20675  
 20676  func getStorageResourceList(storage string) core.ResourceList {
 20677  	res := core.ResourceList{}
 20678  	if storage != "" {
 20679  		res[core.ResourceStorage] = resource.MustParse(storage)
 20680  	}
 20681  	return res
 20682  }
 20683  
 20684  func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList {
 20685  	res := core.ResourceList{}
 20686  	if ephemeralStorage != "" {
 20687  		res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
 20688  	}
 20689  	return res
 20690  }
 20691  
 20692  func TestValidateLimitRangeForLocalStorage(t *testing.T) {
 20693  	testCases := []struct {
 20694  		name string
 20695  		spec core.LimitRangeSpec
 20696  	}{{
 20697  		name: "all-fields-valid",
 20698  		spec: core.LimitRangeSpec{
 20699  			Limits: []core.LimitRangeItem{{
 20700  				Type:                 core.LimitTypePod,
 20701  				Max:                  getLocalStorageResourceList("10000Mi"),
 20702  				Min:                  getLocalStorageResourceList("100Mi"),
 20703  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 20704  			}, {
 20705  				Type:                 core.LimitTypeContainer,
 20706  				Max:                  getLocalStorageResourceList("10000Mi"),
 20707  				Min:                  getLocalStorageResourceList("100Mi"),
 20708  				Default:              getLocalStorageResourceList("500Mi"),
 20709  				DefaultRequest:       getLocalStorageResourceList("200Mi"),
 20710  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 20711  			}},
 20712  		},
 20713  	},
 20714  	}
 20715  
 20716  	for _, testCase := range testCases {
 20717  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec}
 20718  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 20719  			t.Errorf("Case %v, unexpected error: %v", testCase.name, errs)
 20720  		}
 20721  	}
 20722  }
 20723  
 20724  func TestValidateLimitRange(t *testing.T) {
 20725  	successCases := []struct {
 20726  		name string
 20727  		spec core.LimitRangeSpec
 20728  	}{{
 20729  		name: "all-fields-valid",
 20730  		spec: core.LimitRangeSpec{
 20731  			Limits: []core.LimitRangeItem{{
 20732  				Type:                 core.LimitTypePod,
 20733  				Max:                  getResourceList("100m", "10000Mi"),
 20734  				Min:                  getResourceList("5m", "100Mi"),
 20735  				MaxLimitRequestRatio: getResourceList("10", ""),
 20736  			}, {
 20737  				Type:                 core.LimitTypeContainer,
 20738  				Max:                  getResourceList("100m", "10000Mi"),
 20739  				Min:                  getResourceList("5m", "100Mi"),
 20740  				Default:              getResourceList("50m", "500Mi"),
 20741  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20742  				MaxLimitRequestRatio: getResourceList("10", ""),
 20743  			}, {
 20744  				Type: core.LimitTypePersistentVolumeClaim,
 20745  				Max:  getStorageResourceList("10Gi"),
 20746  				Min:  getStorageResourceList("5Gi"),
 20747  			}},
 20748  		},
 20749  	}, {
 20750  		name: "pvc-min-only",
 20751  		spec: core.LimitRangeSpec{
 20752  			Limits: []core.LimitRangeItem{{
 20753  				Type: core.LimitTypePersistentVolumeClaim,
 20754  				Min:  getStorageResourceList("5Gi"),
 20755  			}},
 20756  		},
 20757  	}, {
 20758  		name: "pvc-max-only",
 20759  		spec: core.LimitRangeSpec{
 20760  			Limits: []core.LimitRangeItem{{
 20761  				Type: core.LimitTypePersistentVolumeClaim,
 20762  				Max:  getStorageResourceList("10Gi"),
 20763  			}},
 20764  		},
 20765  	}, {
 20766  		name: "all-fields-valid-big-numbers",
 20767  		spec: core.LimitRangeSpec{
 20768  			Limits: []core.LimitRangeItem{{
 20769  				Type:                 core.LimitTypeContainer,
 20770  				Max:                  getResourceList("100m", "10000T"),
 20771  				Min:                  getResourceList("5m", "100Mi"),
 20772  				Default:              getResourceList("50m", "500Mi"),
 20773  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20774  				MaxLimitRequestRatio: getResourceList("10", ""),
 20775  			}},
 20776  		},
 20777  	}, {
 20778  		name: "thirdparty-fields-all-valid-standard-container-resources",
 20779  		spec: core.LimitRangeSpec{
 20780  			Limits: []core.LimitRangeItem{{
 20781  				Type:                 "thirdparty.com/foo",
 20782  				Max:                  getResourceList("100m", "10000T"),
 20783  				Min:                  getResourceList("5m", "100Mi"),
 20784  				Default:              getResourceList("50m", "500Mi"),
 20785  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20786  				MaxLimitRequestRatio: getResourceList("10", ""),
 20787  			}},
 20788  		},
 20789  	}, {
 20790  		name: "thirdparty-fields-all-valid-storage-resources",
 20791  		spec: core.LimitRangeSpec{
 20792  			Limits: []core.LimitRangeItem{{
 20793  				Type:                 "thirdparty.com/foo",
 20794  				Max:                  getStorageResourceList("10000T"),
 20795  				Min:                  getStorageResourceList("100Mi"),
 20796  				Default:              getStorageResourceList("500Mi"),
 20797  				DefaultRequest:       getStorageResourceList("200Mi"),
 20798  				MaxLimitRequestRatio: getStorageResourceList(""),
 20799  			}},
 20800  		},
 20801  	},
 20802  	}
 20803  
 20804  	for _, successCase := range successCases {
 20805  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec}
 20806  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 20807  			t.Errorf("Case %v, unexpected error: %v", successCase.name, errs)
 20808  		}
 20809  	}
 20810  
 20811  	errorCases := map[string]struct {
 20812  		R core.LimitRange
 20813  		D string
 20814  	}{
 20815  		"zero-length-name": {
 20816  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 20817  			"name or generateName is required",
 20818  		},
 20819  		"zero-length-namespace": {
 20820  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}},
 20821  			"",
 20822  		},
 20823  		"invalid-name": {
 20824  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 20825  			dnsSubdomainLabelErrMsg,
 20826  		},
 20827  		"invalid-namespace": {
 20828  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}},
 20829  			dnsLabelErrMsg,
 20830  		},
 20831  		"duplicate-limit-type": {
 20832  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20833  				Limits: []core.LimitRangeItem{{
 20834  					Type: core.LimitTypePod,
 20835  					Max:  getResourceList("100m", "10000m"),
 20836  					Min:  getResourceList("0m", "100m"),
 20837  				}, {
 20838  					Type: core.LimitTypePod,
 20839  					Min:  getResourceList("0m", "100m"),
 20840  				}},
 20841  			}},
 20842  			"",
 20843  		},
 20844  		"default-limit-type-pod": {
 20845  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20846  				Limits: []core.LimitRangeItem{{
 20847  					Type:    core.LimitTypePod,
 20848  					Max:     getResourceList("100m", "10000m"),
 20849  					Min:     getResourceList("0m", "100m"),
 20850  					Default: getResourceList("10m", "100m"),
 20851  				}},
 20852  			}},
 20853  			"may not be specified when `type` is 'Pod'",
 20854  		},
 20855  		"default-request-limit-type-pod": {
 20856  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20857  				Limits: []core.LimitRangeItem{{
 20858  					Type:           core.LimitTypePod,
 20859  					Max:            getResourceList("100m", "10000m"),
 20860  					Min:            getResourceList("0m", "100m"),
 20861  					DefaultRequest: getResourceList("10m", "100m"),
 20862  				}},
 20863  			}},
 20864  			"may not be specified when `type` is 'Pod'",
 20865  		},
 20866  		"min value 100m is greater than max value 10m": {
 20867  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20868  				Limits: []core.LimitRangeItem{{
 20869  					Type: core.LimitTypePod,
 20870  					Max:  getResourceList("10m", ""),
 20871  					Min:  getResourceList("100m", ""),
 20872  				}},
 20873  			}},
 20874  			"min value 100m is greater than max value 10m",
 20875  		},
 20876  		"invalid spec default outside range": {
 20877  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20878  				Limits: []core.LimitRangeItem{{
 20879  					Type:    core.LimitTypeContainer,
 20880  					Max:     getResourceList("1", ""),
 20881  					Min:     getResourceList("100m", ""),
 20882  					Default: getResourceList("2000m", ""),
 20883  				}},
 20884  			}},
 20885  			"default value 2 is greater than max value 1",
 20886  		},
 20887  		"invalid spec default request outside range": {
 20888  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20889  				Limits: []core.LimitRangeItem{{
 20890  					Type:           core.LimitTypeContainer,
 20891  					Max:            getResourceList("1", ""),
 20892  					Min:            getResourceList("100m", ""),
 20893  					DefaultRequest: getResourceList("2000m", ""),
 20894  				}},
 20895  			}},
 20896  			"default request value 2 is greater than max value 1",
 20897  		},
 20898  		"invalid spec default request more than default": {
 20899  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20900  				Limits: []core.LimitRangeItem{{
 20901  					Type:           core.LimitTypeContainer,
 20902  					Max:            getResourceList("2", ""),
 20903  					Min:            getResourceList("100m", ""),
 20904  					Default:        getResourceList("500m", ""),
 20905  					DefaultRequest: getResourceList("800m", ""),
 20906  				}},
 20907  			}},
 20908  			"default request value 800m is greater than default limit value 500m",
 20909  		},
 20910  		"invalid spec maxLimitRequestRatio less than 1": {
 20911  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20912  				Limits: []core.LimitRangeItem{{
 20913  					Type:                 core.LimitTypePod,
 20914  					MaxLimitRequestRatio: getResourceList("800m", ""),
 20915  				}},
 20916  			}},
 20917  			"ratio 800m is less than 1",
 20918  		},
 20919  		"invalid spec maxLimitRequestRatio greater than max/min": {
 20920  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20921  				Limits: []core.LimitRangeItem{{
 20922  					Type:                 core.LimitTypeContainer,
 20923  					Max:                  getResourceList("", "2Gi"),
 20924  					Min:                  getResourceList("", "512Mi"),
 20925  					MaxLimitRequestRatio: getResourceList("", "10"),
 20926  				}},
 20927  			}},
 20928  			"ratio 10 is greater than max/min = 4.000000",
 20929  		},
 20930  		"invalid non standard limit type": {
 20931  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20932  				Limits: []core.LimitRangeItem{{
 20933  					Type:                 "foo",
 20934  					Max:                  getStorageResourceList("10000T"),
 20935  					Min:                  getStorageResourceList("100Mi"),
 20936  					Default:              getStorageResourceList("500Mi"),
 20937  					DefaultRequest:       getStorageResourceList("200Mi"),
 20938  					MaxLimitRequestRatio: getStorageResourceList(""),
 20939  				}},
 20940  			}},
 20941  			"must be a standard limit type or fully qualified",
 20942  		},
 20943  		"min and max values missing, one required": {
 20944  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20945  				Limits: []core.LimitRangeItem{{
 20946  					Type: core.LimitTypePersistentVolumeClaim,
 20947  				}},
 20948  			}},
 20949  			"either minimum or maximum storage value is required, but neither was provided",
 20950  		},
 20951  		"invalid min greater than max": {
 20952  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20953  				Limits: []core.LimitRangeItem{{
 20954  					Type: core.LimitTypePersistentVolumeClaim,
 20955  					Min:  getStorageResourceList("10Gi"),
 20956  					Max:  getStorageResourceList("1Gi"),
 20957  				}},
 20958  			}},
 20959  			"min value 10Gi is greater than max value 1Gi",
 20960  		},
 20961  	}
 20962  
 20963  	for k, v := range errorCases {
 20964  		errs := ValidateLimitRange(&v.R)
 20965  		if len(errs) == 0 {
 20966  			t.Errorf("expected failure for %s", k)
 20967  		}
 20968  		for i := range errs {
 20969  			detail := errs[i].Detail
 20970  			if !strings.Contains(detail, v.D) {
 20971  				t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
 20972  			}
 20973  		}
 20974  	}
 20975  
 20976  }
 20977  
 20978  func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
 20979  	validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
 20980  		AccessModes: []core.PersistentVolumeAccessMode{
 20981  			core.ReadWriteOnce,
 20982  			core.ReadOnlyMany,
 20983  		},
 20984  		Resources: core.VolumeResourceRequirements{
 20985  			Requests: core.ResourceList{
 20986  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20987  			},
 20988  		},
 20989  	})
 20990  	validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20991  		AccessModes: []core.PersistentVolumeAccessMode{
 20992  			core.ReadWriteOnce,
 20993  			core.ReadOnlyMany,
 20994  		},
 20995  		Resources: core.VolumeResourceRequirements{
 20996  			Requests: core.ResourceList{
 20997  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20998  			},
 20999  		},
 21000  	}, core.PersistentVolumeClaimStatus{
 21001  		Phase: core.ClaimPending,
 21002  		Conditions: []core.PersistentVolumeClaimCondition{
 21003  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21004  		},
 21005  	})
 21006  	validAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21007  		AccessModes: []core.PersistentVolumeAccessMode{
 21008  			core.ReadWriteOnce,
 21009  			core.ReadOnlyMany,
 21010  		},
 21011  		Resources: core.VolumeResourceRequirements{
 21012  			Requests: core.ResourceList{
 21013  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21014  			},
 21015  		},
 21016  	}, core.PersistentVolumeClaimStatus{
 21017  		Phase: core.ClaimPending,
 21018  		Conditions: []core.PersistentVolumeClaimCondition{
 21019  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21020  		},
 21021  		AllocatedResources: core.ResourceList{
 21022  			core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21023  		},
 21024  	})
 21025  
 21026  	invalidAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21027  		AccessModes: []core.PersistentVolumeAccessMode{
 21028  			core.ReadWriteOnce,
 21029  			core.ReadOnlyMany,
 21030  		},
 21031  		Resources: core.VolumeResourceRequirements{
 21032  			Requests: core.ResourceList{
 21033  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21034  			},
 21035  		},
 21036  	}, core.PersistentVolumeClaimStatus{
 21037  		Phase: core.ClaimPending,
 21038  		Conditions: []core.PersistentVolumeClaimCondition{
 21039  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21040  		},
 21041  		AllocatedResources: core.ResourceList{
 21042  			core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
 21043  		},
 21044  	})
 21045  
 21046  	noStoraegeClaimStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21047  		AccessModes: []core.PersistentVolumeAccessMode{
 21048  			core.ReadWriteOnce,
 21049  		},
 21050  		Resources: core.VolumeResourceRequirements{
 21051  			Requests: core.ResourceList{
 21052  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21053  			},
 21054  		},
 21055  	}, core.PersistentVolumeClaimStatus{
 21056  		Phase: core.ClaimPending,
 21057  		AllocatedResources: core.ResourceList{
 21058  			core.ResourceName(core.ResourceCPU): resource.MustParse("10G"),
 21059  		},
 21060  	})
 21061  	progressResizeStatus := core.PersistentVolumeClaimControllerResizeInProgress
 21062  
 21063  	invalidResizeStatus := core.ClaimResourceStatus("foo")
 21064  	validResizeKeyCustom := core.ResourceName("example.com/foo")
 21065  	invalidNativeResizeKey := core.ResourceName("kubernetes.io/foo")
 21066  
 21067  	validResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21068  		AccessModes: []core.PersistentVolumeAccessMode{
 21069  			core.ReadWriteOnce,
 21070  		},
 21071  	}, core.PersistentVolumeClaimStatus{
 21072  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21073  			core.ResourceStorage: progressResizeStatus,
 21074  		},
 21075  	})
 21076  
 21077  	validResizeStatusControllerResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21078  		AccessModes: []core.PersistentVolumeAccessMode{
 21079  			core.ReadWriteOnce,
 21080  		},
 21081  	}, core.PersistentVolumeClaimStatus{
 21082  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21083  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 21084  		},
 21085  	})
 21086  
 21087  	validNodeResizePending := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21088  		AccessModes: []core.PersistentVolumeAccessMode{
 21089  			core.ReadWriteOnce,
 21090  		},
 21091  	}, core.PersistentVolumeClaimStatus{
 21092  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21093  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizePending,
 21094  		},
 21095  	})
 21096  
 21097  	validNodeResizeInProgress := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21098  		AccessModes: []core.PersistentVolumeAccessMode{
 21099  			core.ReadWriteOnce,
 21100  		},
 21101  	}, core.PersistentVolumeClaimStatus{
 21102  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21103  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInProgress,
 21104  		},
 21105  	})
 21106  
 21107  	validNodeResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21108  		AccessModes: []core.PersistentVolumeAccessMode{
 21109  			core.ReadWriteOnce,
 21110  		},
 21111  	}, core.PersistentVolumeClaimStatus{
 21112  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21113  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed,
 21114  		},
 21115  	})
 21116  
 21117  	invalidResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21118  		AccessModes: []core.PersistentVolumeAccessMode{
 21119  			core.ReadWriteOnce,
 21120  		},
 21121  	}, core.PersistentVolumeClaimStatus{
 21122  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21123  			core.ResourceStorage: invalidResizeStatus,
 21124  		},
 21125  	})
 21126  
 21127  	invalidNativeResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21128  		AccessModes: []core.PersistentVolumeAccessMode{
 21129  			core.ReadWriteOnce,
 21130  		},
 21131  	}, core.PersistentVolumeClaimStatus{
 21132  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21133  			invalidNativeResizeKey: core.PersistentVolumeClaimNodeResizePending,
 21134  		},
 21135  	})
 21136  
 21137  	validExternalResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21138  		AccessModes: []core.PersistentVolumeAccessMode{
 21139  			core.ReadWriteOnce,
 21140  		},
 21141  	}, core.PersistentVolumeClaimStatus{
 21142  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21143  			validResizeKeyCustom: core.PersistentVolumeClaimNodeResizePending,
 21144  		},
 21145  	})
 21146  
 21147  	multipleResourceStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21148  		AccessModes: []core.PersistentVolumeAccessMode{
 21149  			core.ReadWriteOnce,
 21150  		},
 21151  	}, core.PersistentVolumeClaimStatus{
 21152  		AllocatedResources: core.ResourceList{
 21153  			core.ResourceStorage: resource.MustParse("5Gi"),
 21154  			validResizeKeyCustom: resource.MustParse("10Gi"),
 21155  		},
 21156  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21157  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 21158  			validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
 21159  		},
 21160  	})
 21161  
 21162  	invalidNativeResourceAllocatedKey := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21163  		AccessModes: []core.PersistentVolumeAccessMode{
 21164  			core.ReadWriteOnce,
 21165  			core.ReadOnlyMany,
 21166  		},
 21167  		Resources: core.VolumeResourceRequirements{
 21168  			Requests: core.ResourceList{
 21169  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21170  			},
 21171  		},
 21172  	}, core.PersistentVolumeClaimStatus{
 21173  		Phase: core.ClaimPending,
 21174  		Conditions: []core.PersistentVolumeClaimCondition{
 21175  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21176  		},
 21177  		AllocatedResources: core.ResourceList{
 21178  			invalidNativeResizeKey: resource.MustParse("14G"),
 21179  		},
 21180  	})
 21181  
 21182  	validExternalAllocatedResource := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21183  		AccessModes: []core.PersistentVolumeAccessMode{
 21184  			core.ReadWriteOnce,
 21185  			core.ReadOnlyMany,
 21186  		},
 21187  		Resources: core.VolumeResourceRequirements{
 21188  			Requests: core.ResourceList{
 21189  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21190  			},
 21191  		},
 21192  	}, core.PersistentVolumeClaimStatus{
 21193  		Phase: core.ClaimPending,
 21194  		Conditions: []core.PersistentVolumeClaimCondition{
 21195  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21196  		},
 21197  		AllocatedResources: core.ResourceList{
 21198  			validResizeKeyCustom: resource.MustParse("14G"),
 21199  		},
 21200  	})
 21201  
 21202  	scenarios := map[string]struct {
 21203  		isExpectedFailure          bool
 21204  		oldClaim                   *core.PersistentVolumeClaim
 21205  		newClaim                   *core.PersistentVolumeClaim
 21206  		enableRecoverFromExpansion bool
 21207  	}{
 21208  		"condition-update-with-enabled-feature-gate": {
 21209  			isExpectedFailure: false,
 21210  			oldClaim:          validClaim,
 21211  			newClaim:          validConditionUpdate,
 21212  		},
 21213  		"status-update-with-valid-allocatedResources-feature-enabled": {
 21214  			isExpectedFailure:          false,
 21215  			oldClaim:                   validClaim,
 21216  			newClaim:                   validAllocatedResources,
 21217  			enableRecoverFromExpansion: true,
 21218  		},
 21219  		"status-update-with-invalid-allocatedResources-native-key-feature-enabled": {
 21220  			isExpectedFailure:          true,
 21221  			oldClaim:                   validClaim,
 21222  			newClaim:                   invalidNativeResourceAllocatedKey,
 21223  			enableRecoverFromExpansion: true,
 21224  		},
 21225  		"status-update-with-valid-allocatedResources-external-key-feature-enabled": {
 21226  			isExpectedFailure:          false,
 21227  			oldClaim:                   validClaim,
 21228  			newClaim:                   validExternalAllocatedResource,
 21229  			enableRecoverFromExpansion: true,
 21230  		},
 21231  
 21232  		"status-update-with-invalid-allocatedResources-feature-enabled": {
 21233  			isExpectedFailure:          true,
 21234  			oldClaim:                   validClaim,
 21235  			newClaim:                   invalidAllocatedResources,
 21236  			enableRecoverFromExpansion: true,
 21237  		},
 21238  		"status-update-with-no-storage-update": {
 21239  			isExpectedFailure:          true,
 21240  			oldClaim:                   validClaim,
 21241  			newClaim:                   noStoraegeClaimStatus,
 21242  			enableRecoverFromExpansion: true,
 21243  		},
 21244  		"staus-update-with-controller-resize-failed": {
 21245  			isExpectedFailure:          false,
 21246  			oldClaim:                   validClaim,
 21247  			newClaim:                   validResizeStatusControllerResizeFailed,
 21248  			enableRecoverFromExpansion: true,
 21249  		},
 21250  		"staus-update-with-node-resize-pending": {
 21251  			isExpectedFailure:          false,
 21252  			oldClaim:                   validClaim,
 21253  			newClaim:                   validNodeResizePending,
 21254  			enableRecoverFromExpansion: true,
 21255  		},
 21256  		"staus-update-with-node-resize-inprogress": {
 21257  			isExpectedFailure:          false,
 21258  			oldClaim:                   validClaim,
 21259  			newClaim:                   validNodeResizeInProgress,
 21260  			enableRecoverFromExpansion: true,
 21261  		},
 21262  		"staus-update-with-node-resize-failed": {
 21263  			isExpectedFailure:          false,
 21264  			oldClaim:                   validClaim,
 21265  			newClaim:                   validNodeResizeFailed,
 21266  			enableRecoverFromExpansion: true,
 21267  		},
 21268  		"staus-update-with-invalid-native-resource-status-key": {
 21269  			isExpectedFailure:          true,
 21270  			oldClaim:                   validClaim,
 21271  			newClaim:                   invalidNativeResizeStatusPVC,
 21272  			enableRecoverFromExpansion: true,
 21273  		},
 21274  		"staus-update-with-valid-external-resource-status-key": {
 21275  			isExpectedFailure:          false,
 21276  			oldClaim:                   validClaim,
 21277  			newClaim:                   validExternalResizeStatusPVC,
 21278  			enableRecoverFromExpansion: true,
 21279  		},
 21280  		"status-update-with-multiple-resources-key": {
 21281  			isExpectedFailure:          false,
 21282  			oldClaim:                   validClaim,
 21283  			newClaim:                   multipleResourceStatusPVC,
 21284  			enableRecoverFromExpansion: true,
 21285  		},
 21286  		"status-update-with-valid-pvc-resize-status": {
 21287  			isExpectedFailure:          false,
 21288  			oldClaim:                   validClaim,
 21289  			newClaim:                   validResizeStatusPVC,
 21290  			enableRecoverFromExpansion: true,
 21291  		},
 21292  		"status-update-with-invalid-pvc-resize-status": {
 21293  			isExpectedFailure:          true,
 21294  			oldClaim:                   validClaim,
 21295  			newClaim:                   invalidResizeStatusPVC,
 21296  			enableRecoverFromExpansion: true,
 21297  		},
 21298  		"status-update-with-old-pvc-valid-resourcestatus-newpvc-invalid-recovery-disabled": {
 21299  			isExpectedFailure:          true,
 21300  			oldClaim:                   validResizeStatusPVC,
 21301  			newClaim:                   invalidResizeStatusPVC,
 21302  			enableRecoverFromExpansion: false,
 21303  		},
 21304  		"status-update-with-old-pvc-valid-allocatedResource-newpvc-invalid-recovery-disabled": {
 21305  			isExpectedFailure:          true,
 21306  			oldClaim:                   validExternalAllocatedResource,
 21307  			newClaim:                   invalidNativeResourceAllocatedKey,
 21308  			enableRecoverFromExpansion: false,
 21309  		},
 21310  	}
 21311  	for name, scenario := range scenarios {
 21312  		t.Run(name, func(t *testing.T) {
 21313  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)
 21314  
 21315  			validateOpts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
 21316  
 21317  			// ensure we have a resource version specified for updates
 21318  			scenario.oldClaim.ResourceVersion = "1"
 21319  			scenario.newClaim.ResourceVersion = "1"
 21320  			errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim, validateOpts)
 21321  			if len(errs) == 0 && scenario.isExpectedFailure {
 21322  				t.Errorf("Unexpected success for scenario: %s", name)
 21323  			}
 21324  			if len(errs) > 0 && !scenario.isExpectedFailure {
 21325  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
 21326  			}
 21327  		})
 21328  	}
 21329  }
 21330  
 21331  func TestValidateResourceQuota(t *testing.T) {
 21332  	spec := core.ResourceQuotaSpec{
 21333  		Hard: core.ResourceList{
 21334  			core.ResourceCPU:                    resource.MustParse("100"),
 21335  			core.ResourceMemory:                 resource.MustParse("10000"),
 21336  			core.ResourceRequestsCPU:            resource.MustParse("100"),
 21337  			core.ResourceRequestsMemory:         resource.MustParse("10000"),
 21338  			core.ResourceLimitsCPU:              resource.MustParse("100"),
 21339  			core.ResourceLimitsMemory:           resource.MustParse("10000"),
 21340  			core.ResourcePods:                   resource.MustParse("10"),
 21341  			core.ResourceServices:               resource.MustParse("0"),
 21342  			core.ResourceReplicationControllers: resource.MustParse("10"),
 21343  			core.ResourceQuotas:                 resource.MustParse("10"),
 21344  			core.ResourceConfigMaps:             resource.MustParse("10"),
 21345  			core.ResourceSecrets:                resource.MustParse("10"),
 21346  		},
 21347  	}
 21348  
 21349  	terminatingSpec := core.ResourceQuotaSpec{
 21350  		Hard: core.ResourceList{
 21351  			core.ResourceCPU:       resource.MustParse("100"),
 21352  			core.ResourceLimitsCPU: resource.MustParse("200"),
 21353  		},
 21354  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating},
 21355  	}
 21356  
 21357  	nonTerminatingSpec := core.ResourceQuotaSpec{
 21358  		Hard: core.ResourceList{
 21359  			core.ResourceCPU: resource.MustParse("100"),
 21360  		},
 21361  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating},
 21362  	}
 21363  
 21364  	bestEffortSpec := core.ResourceQuotaSpec{
 21365  		Hard: core.ResourceList{
 21366  			core.ResourcePods: resource.MustParse("100"),
 21367  		},
 21368  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort},
 21369  	}
 21370  
 21371  	nonBestEffortSpec := core.ResourceQuotaSpec{
 21372  		Hard: core.ResourceList{
 21373  			core.ResourceCPU: resource.MustParse("100"),
 21374  		},
 21375  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
 21376  	}
 21377  
 21378  	crossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 21379  		Hard: core.ResourceList{
 21380  			core.ResourceCPU:       resource.MustParse("100"),
 21381  			core.ResourceLimitsCPU: resource.MustParse("200"),
 21382  		},
 21383  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeCrossNamespacePodAffinity},
 21384  	}
 21385  
 21386  	scopeSelectorSpec := core.ResourceQuotaSpec{
 21387  		ScopeSelector: &core.ScopeSelector{
 21388  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 21389  				ScopeName: core.ResourceQuotaScopePriorityClass,
 21390  				Operator:  core.ScopeSelectorOpIn,
 21391  				Values:    []string{"cluster-services"},
 21392  			}},
 21393  		},
 21394  	}
 21395  
 21396  	// storage is not yet supported as a quota tracked resource
 21397  	invalidQuotaResourceSpec := core.ResourceQuotaSpec{
 21398  		Hard: core.ResourceList{
 21399  			core.ResourceStorage: resource.MustParse("10"),
 21400  		},
 21401  	}
 21402  
 21403  	negativeSpec := core.ResourceQuotaSpec{
 21404  		Hard: core.ResourceList{
 21405  			core.ResourceCPU:                    resource.MustParse("-100"),
 21406  			core.ResourceMemory:                 resource.MustParse("-10000"),
 21407  			core.ResourcePods:                   resource.MustParse("-10"),
 21408  			core.ResourceServices:               resource.MustParse("-10"),
 21409  			core.ResourceReplicationControllers: resource.MustParse("-10"),
 21410  			core.ResourceQuotas:                 resource.MustParse("-10"),
 21411  			core.ResourceConfigMaps:             resource.MustParse("-10"),
 21412  			core.ResourceSecrets:                resource.MustParse("-10"),
 21413  		},
 21414  	}
 21415  
 21416  	fractionalComputeSpec := core.ResourceQuotaSpec{
 21417  		Hard: core.ResourceList{
 21418  			core.ResourceCPU: resource.MustParse("100m"),
 21419  		},
 21420  	}
 21421  
 21422  	fractionalPodSpec := core.ResourceQuotaSpec{
 21423  		Hard: core.ResourceList{
 21424  			core.ResourcePods:                   resource.MustParse(".1"),
 21425  			core.ResourceServices:               resource.MustParse(".5"),
 21426  			core.ResourceReplicationControllers: resource.MustParse("1.25"),
 21427  			core.ResourceQuotas:                 resource.MustParse("2.5"),
 21428  		},
 21429  	}
 21430  
 21431  	invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{
 21432  		Hard: core.ResourceList{
 21433  			core.ResourceCPU: resource.MustParse("100"),
 21434  		},
 21435  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating},
 21436  	}
 21437  
 21438  	invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{
 21439  		Hard: core.ResourceList{
 21440  			core.ResourcePods: resource.MustParse("100"),
 21441  		},
 21442  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort},
 21443  	}
 21444  
 21445  	invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 21446  		ScopeSelector: &core.ScopeSelector{
 21447  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 21448  				ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity,
 21449  				Operator:  core.ScopeSelectorOpIn,
 21450  				Values:    []string{"cluster-services"},
 21451  			}},
 21452  		},
 21453  	}
 21454  
 21455  	invalidScopeNameSpec := core.ResourceQuotaSpec{
 21456  		Hard: core.ResourceList{
 21457  			core.ResourceCPU: resource.MustParse("100"),
 21458  		},
 21459  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")},
 21460  	}
 21461  
 21462  	testCases := map[string]struct {
 21463  		rq        core.ResourceQuota
 21464  		errDetail string
 21465  		errField  string
 21466  	}{
 21467  		"no-scope": {
 21468  			rq: core.ResourceQuota{
 21469  				ObjectMeta: metav1.ObjectMeta{
 21470  					Name:      "abc",
 21471  					Namespace: "foo",
 21472  				},
 21473  				Spec: spec,
 21474  			},
 21475  		},
 21476  		"fractional-compute-spec": {
 21477  			rq: core.ResourceQuota{
 21478  				ObjectMeta: metav1.ObjectMeta{
 21479  					Name:      "abc",
 21480  					Namespace: "foo",
 21481  				},
 21482  				Spec: fractionalComputeSpec,
 21483  			},
 21484  		},
 21485  		"terminating-spec": {
 21486  			rq: core.ResourceQuota{
 21487  				ObjectMeta: metav1.ObjectMeta{
 21488  					Name:      "abc",
 21489  					Namespace: "foo",
 21490  				},
 21491  				Spec: terminatingSpec,
 21492  			},
 21493  		},
 21494  		"non-terminating-spec": {
 21495  			rq: core.ResourceQuota{
 21496  				ObjectMeta: metav1.ObjectMeta{
 21497  					Name:      "abc",
 21498  					Namespace: "foo",
 21499  				},
 21500  				Spec: nonTerminatingSpec,
 21501  			},
 21502  		},
 21503  		"best-effort-spec": {
 21504  			rq: core.ResourceQuota{
 21505  				ObjectMeta: metav1.ObjectMeta{
 21506  					Name:      "abc",
 21507  					Namespace: "foo",
 21508  				},
 21509  				Spec: bestEffortSpec,
 21510  			},
 21511  		},
 21512  		"cross-namespace-affinity-spec": {
 21513  			rq: core.ResourceQuota{
 21514  				ObjectMeta: metav1.ObjectMeta{
 21515  					Name:      "abc",
 21516  					Namespace: "foo",
 21517  				},
 21518  				Spec: crossNamespaceAffinitySpec,
 21519  			},
 21520  		},
 21521  		"scope-selector-spec": {
 21522  			rq: core.ResourceQuota{
 21523  				ObjectMeta: metav1.ObjectMeta{
 21524  					Name:      "abc",
 21525  					Namespace: "foo",
 21526  				},
 21527  				Spec: scopeSelectorSpec,
 21528  			},
 21529  		},
 21530  		"non-best-effort-spec": {
 21531  			rq: core.ResourceQuota{
 21532  				ObjectMeta: metav1.ObjectMeta{
 21533  					Name:      "abc",
 21534  					Namespace: "foo",
 21535  				},
 21536  				Spec: nonBestEffortSpec,
 21537  			},
 21538  		},
 21539  		"zero-length Name": {
 21540  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
 21541  			errDetail: "name or generateName is required",
 21542  		},
 21543  		"zero-length Namespace": {
 21544  			rq:       core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
 21545  			errField: "metadata.namespace",
 21546  		},
 21547  		"invalid Name": {
 21548  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
 21549  			errDetail: dnsSubdomainLabelErrMsg,
 21550  		},
 21551  		"invalid Namespace": {
 21552  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
 21553  			errDetail: dnsLabelErrMsg,
 21554  		},
 21555  		"negative-limits": {
 21556  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec},
 21557  			errDetail: isNegativeErrorMsg,
 21558  		},
 21559  		"fractional-api-resource": {
 21560  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
 21561  			errDetail: isNotIntegerErrorMsg,
 21562  		},
 21563  		"invalid-quota-resource": {
 21564  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
 21565  			errDetail: isInvalidQuotaResource,
 21566  		},
 21567  		"invalid-quota-terminating-pair": {
 21568  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
 21569  			errDetail: "conflicting scopes",
 21570  		},
 21571  		"invalid-quota-besteffort-pair": {
 21572  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
 21573  			errDetail: "conflicting scopes",
 21574  		},
 21575  		"invalid-quota-scope-name": {
 21576  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
 21577  			errDetail: "unsupported scope",
 21578  		},
 21579  		"invalid-cross-namespace-affinity": {
 21580  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec},
 21581  			errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity",
 21582  		},
 21583  	}
 21584  	for name, tc := range testCases {
 21585  		t.Run(name, func(t *testing.T) {
 21586  			errs := ValidateResourceQuota(&tc.rq)
 21587  			if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
 21588  				t.Errorf("expected success: %v", errs)
 21589  			} else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {
 21590  				t.Errorf("expected failure")
 21591  			} else {
 21592  				for i := range errs {
 21593  					if !strings.Contains(errs[i].Detail, tc.errDetail) {
 21594  						t.Errorf("expected error detail either empty or %s, got %s", tc.errDetail, errs[i].Detail)
 21595  					}
 21596  				}
 21597  			}
 21598  		})
 21599  	}
 21600  }
 21601  
 21602  func TestValidateNamespace(t *testing.T) {
 21603  	validLabels := map[string]string{"a": "b"}
 21604  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 21605  	successCases := []core.Namespace{{
 21606  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels},
 21607  	}, {
 21608  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 21609  		Spec: core.NamespaceSpec{
 21610  			Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"},
 21611  		},
 21612  	},
 21613  	}
 21614  	for _, successCase := range successCases {
 21615  		if errs := ValidateNamespace(&successCase); len(errs) != 0 {
 21616  			t.Errorf("expected success: %v", errs)
 21617  		}
 21618  	}
 21619  	errorCases := map[string]struct {
 21620  		R core.Namespace
 21621  		D string
 21622  	}{
 21623  		"zero-length name": {
 21624  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}},
 21625  			"",
 21626  		},
 21627  		"defined-namespace": {
 21628  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
 21629  			"",
 21630  		},
 21631  		"invalid-labels": {
 21632  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}},
 21633  			"",
 21634  		},
 21635  	}
 21636  	for k, v := range errorCases {
 21637  		errs := ValidateNamespace(&v.R)
 21638  		if len(errs) == 0 {
 21639  			t.Errorf("expected failure for %s", k)
 21640  		}
 21641  	}
 21642  }
 21643  
 21644  func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
 21645  	tests := []struct {
 21646  		oldNamespace core.Namespace
 21647  		namespace    core.Namespace
 21648  		valid        bool
 21649  	}{
 21650  		{core.Namespace{}, core.Namespace{}, true},
 21651  		{core.Namespace{
 21652  			ObjectMeta: metav1.ObjectMeta{
 21653  				Name: "foo"}},
 21654  			core.Namespace{
 21655  				ObjectMeta: metav1.ObjectMeta{
 21656  					Name: "foo"},
 21657  				Spec: core.NamespaceSpec{
 21658  					Finalizers: []core.FinalizerName{"Foo"},
 21659  				},
 21660  			}, false},
 21661  		{core.Namespace{
 21662  			ObjectMeta: metav1.ObjectMeta{
 21663  				Name: "foo"},
 21664  			Spec: core.NamespaceSpec{
 21665  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 21666  			},
 21667  		},
 21668  			core.Namespace{
 21669  				ObjectMeta: metav1.ObjectMeta{
 21670  					Name: "foo"},
 21671  				Spec: core.NamespaceSpec{
 21672  					Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"},
 21673  				},
 21674  			}, true},
 21675  		{core.Namespace{
 21676  			ObjectMeta: metav1.ObjectMeta{
 21677  				Name: "fooemptyfinalizer"},
 21678  			Spec: core.NamespaceSpec{
 21679  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 21680  			},
 21681  		},
 21682  			core.Namespace{
 21683  				ObjectMeta: metav1.ObjectMeta{
 21684  					Name: "fooemptyfinalizer"},
 21685  				Spec: core.NamespaceSpec{
 21686  					Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"},
 21687  				},
 21688  			}, false},
 21689  	}
 21690  	for i, test := range tests {
 21691  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21692  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21693  		errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
 21694  		if test.valid && len(errs) > 0 {
 21695  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21696  			t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
 21697  		}
 21698  		if !test.valid && len(errs) == 0 {
 21699  			t.Errorf("%d: Unexpected non-error", i)
 21700  		}
 21701  	}
 21702  }
 21703  
 21704  func TestValidateNamespaceStatusUpdate(t *testing.T) {
 21705  	now := metav1.Now()
 21706  
 21707  	tests := []struct {
 21708  		oldNamespace core.Namespace
 21709  		namespace    core.Namespace
 21710  		valid        bool
 21711  	}{
 21712  		{core.Namespace{}, core.Namespace{
 21713  			Status: core.NamespaceStatus{
 21714  				Phase: core.NamespaceActive,
 21715  			},
 21716  		}, true},
 21717  		// Cannot set deletionTimestamp via status update
 21718  		{core.Namespace{
 21719  			ObjectMeta: metav1.ObjectMeta{
 21720  				Name: "foo"}},
 21721  			core.Namespace{
 21722  				ObjectMeta: metav1.ObjectMeta{
 21723  					Name:              "foo",
 21724  					DeletionTimestamp: &now},
 21725  				Status: core.NamespaceStatus{
 21726  					Phase: core.NamespaceTerminating,
 21727  				},
 21728  			}, false},
 21729  		// Can update phase via status update
 21730  		{core.Namespace{
 21731  			ObjectMeta: metav1.ObjectMeta{
 21732  				Name:              "foo",
 21733  				DeletionTimestamp: &now}},
 21734  			core.Namespace{
 21735  				ObjectMeta: metav1.ObjectMeta{
 21736  					Name:              "foo",
 21737  					DeletionTimestamp: &now},
 21738  				Status: core.NamespaceStatus{
 21739  					Phase: core.NamespaceTerminating,
 21740  				},
 21741  			}, true},
 21742  		{core.Namespace{
 21743  			ObjectMeta: metav1.ObjectMeta{
 21744  				Name: "foo"}},
 21745  			core.Namespace{
 21746  				ObjectMeta: metav1.ObjectMeta{
 21747  					Name: "foo"},
 21748  				Status: core.NamespaceStatus{
 21749  					Phase: core.NamespaceTerminating,
 21750  				},
 21751  			}, false},
 21752  		{core.Namespace{
 21753  			ObjectMeta: metav1.ObjectMeta{
 21754  				Name: "foo"}},
 21755  			core.Namespace{
 21756  				ObjectMeta: metav1.ObjectMeta{
 21757  					Name: "bar"},
 21758  				Status: core.NamespaceStatus{
 21759  					Phase: core.NamespaceTerminating,
 21760  				},
 21761  			}, false},
 21762  	}
 21763  	for i, test := range tests {
 21764  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21765  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21766  		errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace)
 21767  		if test.valid && len(errs) > 0 {
 21768  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21769  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 21770  		}
 21771  		if !test.valid && len(errs) == 0 {
 21772  			t.Errorf("%d: Unexpected non-error", i)
 21773  		}
 21774  	}
 21775  }
 21776  
 21777  func TestValidateNamespaceUpdate(t *testing.T) {
 21778  	tests := []struct {
 21779  		oldNamespace core.Namespace
 21780  		namespace    core.Namespace
 21781  		valid        bool
 21782  	}{
 21783  		{core.Namespace{}, core.Namespace{}, true},
 21784  		{core.Namespace{
 21785  			ObjectMeta: metav1.ObjectMeta{
 21786  				Name: "foo1"}},
 21787  			core.Namespace{
 21788  				ObjectMeta: metav1.ObjectMeta{
 21789  					Name: "bar1"},
 21790  			}, false},
 21791  		{core.Namespace{
 21792  			ObjectMeta: metav1.ObjectMeta{
 21793  				Name:   "foo2",
 21794  				Labels: map[string]string{"foo": "bar"},
 21795  			},
 21796  		}, core.Namespace{
 21797  			ObjectMeta: metav1.ObjectMeta{
 21798  				Name:   "foo2",
 21799  				Labels: map[string]string{"foo": "baz"},
 21800  			},
 21801  		}, true},
 21802  		{core.Namespace{
 21803  			ObjectMeta: metav1.ObjectMeta{
 21804  				Name: "foo3",
 21805  			},
 21806  		}, core.Namespace{
 21807  			ObjectMeta: metav1.ObjectMeta{
 21808  				Name:   "foo3",
 21809  				Labels: map[string]string{"foo": "baz"},
 21810  			},
 21811  		}, true},
 21812  		{core.Namespace{
 21813  			ObjectMeta: metav1.ObjectMeta{
 21814  				Name:   "foo4",
 21815  				Labels: map[string]string{"bar": "foo"},
 21816  			},
 21817  		}, core.Namespace{
 21818  			ObjectMeta: metav1.ObjectMeta{
 21819  				Name:   "foo4",
 21820  				Labels: map[string]string{"foo": "baz"},
 21821  			},
 21822  		}, true},
 21823  		{core.Namespace{
 21824  			ObjectMeta: metav1.ObjectMeta{
 21825  				Name:   "foo5",
 21826  				Labels: map[string]string{"foo": "baz"},
 21827  			},
 21828  		}, core.Namespace{
 21829  			ObjectMeta: metav1.ObjectMeta{
 21830  				Name:   "foo5",
 21831  				Labels: map[string]string{"Foo": "baz"},
 21832  			},
 21833  		}, true},
 21834  		{core.Namespace{
 21835  			ObjectMeta: metav1.ObjectMeta{
 21836  				Name:   "foo6",
 21837  				Labels: map[string]string{"foo": "baz"},
 21838  			},
 21839  		}, core.Namespace{
 21840  			ObjectMeta: metav1.ObjectMeta{
 21841  				Name:   "foo6",
 21842  				Labels: map[string]string{"Foo": "baz"},
 21843  			},
 21844  			Spec: core.NamespaceSpec{
 21845  				Finalizers: []core.FinalizerName{"kubernetes"},
 21846  			},
 21847  			Status: core.NamespaceStatus{
 21848  				Phase: core.NamespaceTerminating,
 21849  			},
 21850  		}, true},
 21851  	}
 21852  	for i, test := range tests {
 21853  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21854  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21855  		errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace)
 21856  		if test.valid && len(errs) > 0 {
 21857  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21858  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 21859  		}
 21860  		if !test.valid && len(errs) == 0 {
 21861  			t.Errorf("%d: Unexpected non-error", i)
 21862  		}
 21863  	}
 21864  }
 21865  
 21866  func TestValidateSecret(t *testing.T) {
 21867  	// Opaque secret validation
 21868  	validSecret := func() core.Secret {
 21869  		return core.Secret{
 21870  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21871  			Data: map[string][]byte{
 21872  				"data-1": []byte("bar"),
 21873  			},
 21874  		}
 21875  	}
 21876  
 21877  	var (
 21878  		emptyName     = validSecret()
 21879  		invalidName   = validSecret()
 21880  		emptyNs       = validSecret()
 21881  		invalidNs     = validSecret()
 21882  		overMaxSize   = validSecret()
 21883  		invalidKey    = validSecret()
 21884  		leadingDotKey = validSecret()
 21885  		dotKey        = validSecret()
 21886  		doubleDotKey  = validSecret()
 21887  	)
 21888  
 21889  	emptyName.Name = ""
 21890  	invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals"
 21891  	emptyNs.Namespace = ""
 21892  	invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals"
 21893  	overMaxSize.Data = map[string][]byte{
 21894  		"over": make([]byte, core.MaxSecretSize+1),
 21895  	}
 21896  	invalidKey.Data["a*b"] = []byte("whoops")
 21897  	leadingDotKey.Data[".key"] = []byte("bar")
 21898  	dotKey.Data["."] = []byte("bar")
 21899  	doubleDotKey.Data[".."] = []byte("bar")
 21900  
 21901  	// kubernetes.io/service-account-token secret validation
 21902  	validServiceAccountTokenSecret := func() core.Secret {
 21903  		return core.Secret{
 21904  			ObjectMeta: metav1.ObjectMeta{
 21905  				Name:      "foo",
 21906  				Namespace: "bar",
 21907  				Annotations: map[string]string{
 21908  					core.ServiceAccountNameKey: "foo",
 21909  				},
 21910  			},
 21911  			Type: core.SecretTypeServiceAccountToken,
 21912  			Data: map[string][]byte{
 21913  				"data-1": []byte("bar"),
 21914  			},
 21915  		}
 21916  	}
 21917  
 21918  	var (
 21919  		emptyTokenAnnotation    = validServiceAccountTokenSecret()
 21920  		missingTokenAnnotation  = validServiceAccountTokenSecret()
 21921  		missingTokenAnnotations = validServiceAccountTokenSecret()
 21922  	)
 21923  	emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = ""
 21924  	delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey)
 21925  	missingTokenAnnotations.Annotations = nil
 21926  
 21927  	tests := map[string]struct {
 21928  		secret core.Secret
 21929  		valid  bool
 21930  	}{
 21931  		"valid":                                     {validSecret(), true},
 21932  		"empty name":                                {emptyName, false},
 21933  		"invalid name":                              {invalidName, false},
 21934  		"empty namespace":                           {emptyNs, false},
 21935  		"invalid namespace":                         {invalidNs, false},
 21936  		"over max size":                             {overMaxSize, false},
 21937  		"invalid key":                               {invalidKey, false},
 21938  		"valid service-account-token secret":        {validServiceAccountTokenSecret(), true},
 21939  		"empty service-account-token annotation":    {emptyTokenAnnotation, false},
 21940  		"missing service-account-token annotation":  {missingTokenAnnotation, false},
 21941  		"missing service-account-token annotations": {missingTokenAnnotations, false},
 21942  		"leading dot key":                           {leadingDotKey, true},
 21943  		"dot key":                                   {dotKey, false},
 21944  		"double dot key":                            {doubleDotKey, false},
 21945  	}
 21946  
 21947  	for name, tc := range tests {
 21948  		errs := ValidateSecret(&tc.secret)
 21949  		if tc.valid && len(errs) > 0 {
 21950  			t.Errorf("%v: Unexpected error: %v", name, errs)
 21951  		}
 21952  		if !tc.valid && len(errs) == 0 {
 21953  			t.Errorf("%v: Unexpected non-error", name)
 21954  		}
 21955  	}
 21956  }
 21957  
 21958  func TestValidateSecretUpdate(t *testing.T) {
 21959  	validSecret := func() core.Secret {
 21960  		return core.Secret{
 21961  			ObjectMeta: metav1.ObjectMeta{
 21962  				Name:            "foo",
 21963  				Namespace:       "bar",
 21964  				ResourceVersion: "20",
 21965  			},
 21966  			Data: map[string][]byte{
 21967  				"data-1": []byte("bar"),
 21968  			},
 21969  		}
 21970  	}
 21971  
 21972  	falseVal := false
 21973  	trueVal := true
 21974  
 21975  	secret := validSecret()
 21976  	immutableSecret := validSecret()
 21977  	immutableSecret.Immutable = &trueVal
 21978  	mutableSecret := validSecret()
 21979  	mutableSecret.Immutable = &falseVal
 21980  
 21981  	secretWithData := validSecret()
 21982  	secretWithData.Data["data-2"] = []byte("baz")
 21983  	immutableSecretWithData := validSecret()
 21984  	immutableSecretWithData.Immutable = &trueVal
 21985  	immutableSecretWithData.Data["data-2"] = []byte("baz")
 21986  
 21987  	secretWithChangedData := validSecret()
 21988  	secretWithChangedData.Data["data-1"] = []byte("foo")
 21989  	immutableSecretWithChangedData := validSecret()
 21990  	immutableSecretWithChangedData.Immutable = &trueVal
 21991  	immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
 21992  
 21993  	tests := []struct {
 21994  		name      string
 21995  		oldSecret core.Secret
 21996  		newSecret core.Secret
 21997  		valid     bool
 21998  	}{{
 21999  		name:      "mark secret immutable",
 22000  		oldSecret: secret,
 22001  		newSecret: immutableSecret,
 22002  		valid:     true,
 22003  	}, {
 22004  		name:      "revert immutable secret",
 22005  		oldSecret: immutableSecret,
 22006  		newSecret: secret,
 22007  		valid:     false,
 22008  	}, {
 22009  		name:      "makr immutable secret mutable",
 22010  		oldSecret: immutableSecret,
 22011  		newSecret: mutableSecret,
 22012  		valid:     false,
 22013  	}, {
 22014  		name:      "add data in secret",
 22015  		oldSecret: secret,
 22016  		newSecret: secretWithData,
 22017  		valid:     true,
 22018  	}, {
 22019  		name:      "add data in immutable secret",
 22020  		oldSecret: immutableSecret,
 22021  		newSecret: immutableSecretWithData,
 22022  		valid:     false,
 22023  	}, {
 22024  		name:      "change data in secret",
 22025  		oldSecret: secret,
 22026  		newSecret: secretWithChangedData,
 22027  		valid:     true,
 22028  	}, {
 22029  		name:      "change data in immutable secret",
 22030  		oldSecret: immutableSecret,
 22031  		newSecret: immutableSecretWithChangedData,
 22032  		valid:     false,
 22033  	},
 22034  	}
 22035  
 22036  	for _, tc := range tests {
 22037  		t.Run(tc.name, func(t *testing.T) {
 22038  			errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
 22039  			if tc.valid && len(errs) > 0 {
 22040  				t.Errorf("Unexpected error: %v", errs)
 22041  			}
 22042  			if !tc.valid && len(errs) == 0 {
 22043  				t.Errorf("Unexpected lack of error")
 22044  			}
 22045  		})
 22046  	}
 22047  }
 22048  
 22049  func TestValidateDockerConfigSecret(t *testing.T) {
 22050  	validDockerSecret := func() core.Secret {
 22051  		return core.Secret{
 22052  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22053  			Type:       core.SecretTypeDockercfg,
 22054  			Data: map[string][]byte{
 22055  				core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`),
 22056  			},
 22057  		}
 22058  	}
 22059  	validDockerSecret2 := func() core.Secret {
 22060  		return core.Secret{
 22061  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22062  			Type:       core.SecretTypeDockerConfigJSON,
 22063  			Data: map[string][]byte{
 22064  				core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`),
 22065  			},
 22066  		}
 22067  	}
 22068  
 22069  	var (
 22070  		missingDockerConfigKey  = validDockerSecret()
 22071  		emptyDockerConfigKey    = validDockerSecret()
 22072  		invalidDockerConfigKey  = validDockerSecret()
 22073  		missingDockerConfigKey2 = validDockerSecret2()
 22074  		emptyDockerConfigKey2   = validDockerSecret2()
 22075  		invalidDockerConfigKey2 = validDockerSecret2()
 22076  	)
 22077  
 22078  	delete(missingDockerConfigKey.Data, core.DockerConfigKey)
 22079  	emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("")
 22080  	invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad")
 22081  	delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey)
 22082  	emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("")
 22083  	invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad")
 22084  
 22085  	tests := map[string]struct {
 22086  		secret core.Secret
 22087  		valid  bool
 22088  	}{
 22089  		"valid dockercfg":     {validDockerSecret(), true},
 22090  		"missing dockercfg":   {missingDockerConfigKey, false},
 22091  		"empty dockercfg":     {emptyDockerConfigKey, false},
 22092  		"invalid dockercfg":   {invalidDockerConfigKey, false},
 22093  		"valid config.json":   {validDockerSecret2(), true},
 22094  		"missing config.json": {missingDockerConfigKey2, false},
 22095  		"empty config.json":   {emptyDockerConfigKey2, false},
 22096  		"invalid config.json": {invalidDockerConfigKey2, false},
 22097  	}
 22098  
 22099  	for name, tc := range tests {
 22100  		errs := ValidateSecret(&tc.secret)
 22101  		if tc.valid && len(errs) > 0 {
 22102  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22103  		}
 22104  		if !tc.valid && len(errs) == 0 {
 22105  			t.Errorf("%v: Unexpected non-error", name)
 22106  		}
 22107  	}
 22108  }
 22109  
 22110  func TestValidateBasicAuthSecret(t *testing.T) {
 22111  	validBasicAuthSecret := func() core.Secret {
 22112  		return core.Secret{
 22113  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22114  			Type:       core.SecretTypeBasicAuth,
 22115  			Data: map[string][]byte{
 22116  				core.BasicAuthUsernameKey: []byte("username"),
 22117  				core.BasicAuthPasswordKey: []byte("password"),
 22118  			},
 22119  		}
 22120  	}
 22121  
 22122  	var (
 22123  		missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret()
 22124  	)
 22125  
 22126  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey)
 22127  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey)
 22128  
 22129  	tests := map[string]struct {
 22130  		secret core.Secret
 22131  		valid  bool
 22132  	}{
 22133  		"valid":                         {validBasicAuthSecret(), true},
 22134  		"missing username and password": {missingBasicAuthUsernamePasswordKeys, false},
 22135  	}
 22136  
 22137  	for name, tc := range tests {
 22138  		errs := ValidateSecret(&tc.secret)
 22139  		if tc.valid && len(errs) > 0 {
 22140  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22141  		}
 22142  		if !tc.valid && len(errs) == 0 {
 22143  			t.Errorf("%v: Unexpected non-error", name)
 22144  		}
 22145  	}
 22146  }
 22147  
 22148  func TestValidateSSHAuthSecret(t *testing.T) {
 22149  	validSSHAuthSecret := func() core.Secret {
 22150  		return core.Secret{
 22151  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22152  			Type:       core.SecretTypeSSHAuth,
 22153  			Data: map[string][]byte{
 22154  				core.SSHAuthPrivateKey: []byte("foo-bar-baz"),
 22155  			},
 22156  		}
 22157  	}
 22158  
 22159  	missingSSHAuthPrivateKey := validSSHAuthSecret()
 22160  
 22161  	delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey)
 22162  
 22163  	tests := map[string]struct {
 22164  		secret core.Secret
 22165  		valid  bool
 22166  	}{
 22167  		"valid":               {validSSHAuthSecret(), true},
 22168  		"missing private key": {missingSSHAuthPrivateKey, false},
 22169  	}
 22170  
 22171  	for name, tc := range tests {
 22172  		errs := ValidateSecret(&tc.secret)
 22173  		if tc.valid && len(errs) > 0 {
 22174  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22175  		}
 22176  		if !tc.valid && len(errs) == 0 {
 22177  			t.Errorf("%v: Unexpected non-error", name)
 22178  		}
 22179  	}
 22180  }
 22181  
 22182  func TestValidateEndpointsCreate(t *testing.T) {
 22183  	successCases := map[string]struct {
 22184  		endpoints core.Endpoints
 22185  	}{
 22186  		"simple endpoint": {
 22187  			endpoints: core.Endpoints{
 22188  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22189  				Subsets: []core.EndpointSubset{{
 22190  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
 22191  					Ports:     []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 22192  				}, {
 22193  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 22194  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
 22195  				}},
 22196  			},
 22197  		},
 22198  		"empty subsets": {
 22199  			endpoints: core.Endpoints{
 22200  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22201  			},
 22202  		},
 22203  		"no name required for singleton port": {
 22204  			endpoints: core.Endpoints{
 22205  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22206  				Subsets: []core.EndpointSubset{{
 22207  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22208  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
 22209  				}},
 22210  			},
 22211  		},
 22212  		"valid appProtocol": {
 22213  			endpoints: core.Endpoints{
 22214  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22215  				Subsets: []core.EndpointSubset{{
 22216  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22217  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}},
 22218  				}},
 22219  			},
 22220  		},
 22221  		"empty ports": {
 22222  			endpoints: core.Endpoints{
 22223  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22224  				Subsets: []core.EndpointSubset{{
 22225  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 22226  				}},
 22227  			},
 22228  		},
 22229  	}
 22230  
 22231  	for name, tc := range successCases {
 22232  		t.Run(name, func(t *testing.T) {
 22233  			errs := ValidateEndpointsCreate(&tc.endpoints)
 22234  			if len(errs) != 0 {
 22235  				t.Errorf("Expected no validation errors, got %v", errs)
 22236  			}
 22237  
 22238  		})
 22239  	}
 22240  
 22241  	errorCases := map[string]struct {
 22242  		endpoints   core.Endpoints
 22243  		errorType   field.ErrorType
 22244  		errorDetail string
 22245  	}{
 22246  		"missing namespace": {
 22247  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
 22248  			errorType: "FieldValueRequired",
 22249  		},
 22250  		"missing name": {
 22251  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}},
 22252  			errorType: "FieldValueRequired",
 22253  		},
 22254  		"invalid namespace": {
 22255  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}},
 22256  			errorType:   "FieldValueInvalid",
 22257  			errorDetail: dnsLabelErrMsg,
 22258  		},
 22259  		"invalid name": {
 22260  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}},
 22261  			errorType:   "FieldValueInvalid",
 22262  			errorDetail: dnsSubdomainLabelErrMsg,
 22263  		},
 22264  		"empty addresses": {
 22265  			endpoints: core.Endpoints{
 22266  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22267  				Subsets: []core.EndpointSubset{{
 22268  					Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22269  				}},
 22270  			},
 22271  			errorType: "FieldValueRequired",
 22272  		},
 22273  		"invalid IP": {
 22274  			endpoints: core.Endpoints{
 22275  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22276  				Subsets: []core.EndpointSubset{{
 22277  					Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}},
 22278  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22279  				}},
 22280  			},
 22281  			errorType:   "FieldValueInvalid",
 22282  			errorDetail: "must be a valid IP address",
 22283  		},
 22284  		"Multiple ports, one without name": {
 22285  			endpoints: core.Endpoints{
 22286  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22287  				Subsets: []core.EndpointSubset{{
 22288  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22289  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 22290  				}},
 22291  			},
 22292  			errorType: "FieldValueRequired",
 22293  		},
 22294  		"Invalid port number": {
 22295  			endpoints: core.Endpoints{
 22296  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22297  				Subsets: []core.EndpointSubset{{
 22298  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22299  					Ports:     []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}},
 22300  				}},
 22301  			},
 22302  			errorType:   "FieldValueInvalid",
 22303  			errorDetail: "between",
 22304  		},
 22305  		"Invalid protocol": {
 22306  			endpoints: core.Endpoints{
 22307  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22308  				Subsets: []core.EndpointSubset{{
 22309  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22310  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}},
 22311  				}},
 22312  			},
 22313  			errorType: "FieldValueNotSupported",
 22314  		},
 22315  		"Address missing IP": {
 22316  			endpoints: core.Endpoints{
 22317  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22318  				Subsets: []core.EndpointSubset{{
 22319  					Addresses: []core.EndpointAddress{{}},
 22320  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22321  				}},
 22322  			},
 22323  			errorType:   "FieldValueInvalid",
 22324  			errorDetail: "must be a valid IP address",
 22325  		},
 22326  		"Port missing number": {
 22327  			endpoints: core.Endpoints{
 22328  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22329  				Subsets: []core.EndpointSubset{{
 22330  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22331  					Ports:     []core.EndpointPort{{Name: "a", Protocol: "TCP"}},
 22332  				}},
 22333  			},
 22334  			errorType:   "FieldValueInvalid",
 22335  			errorDetail: "between",
 22336  		},
 22337  		"Port missing protocol": {
 22338  			endpoints: core.Endpoints{
 22339  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22340  				Subsets: []core.EndpointSubset{{
 22341  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22342  					Ports:     []core.EndpointPort{{Name: "a", Port: 93}},
 22343  				}},
 22344  			},
 22345  			errorType: "FieldValueRequired",
 22346  		},
 22347  		"Address is loopback": {
 22348  			endpoints: core.Endpoints{
 22349  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22350  				Subsets: []core.EndpointSubset{{
 22351  					Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}},
 22352  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22353  				}},
 22354  			},
 22355  			errorType:   "FieldValueInvalid",
 22356  			errorDetail: "loopback",
 22357  		},
 22358  		"Address is link-local": {
 22359  			endpoints: core.Endpoints{
 22360  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22361  				Subsets: []core.EndpointSubset{{
 22362  					Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}},
 22363  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22364  				}},
 22365  			},
 22366  			errorType:   "FieldValueInvalid",
 22367  			errorDetail: "link-local",
 22368  		},
 22369  		"Address is link-local multicast": {
 22370  			endpoints: core.Endpoints{
 22371  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22372  				Subsets: []core.EndpointSubset{{
 22373  					Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}},
 22374  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22375  				}},
 22376  			},
 22377  			errorType:   "FieldValueInvalid",
 22378  			errorDetail: "link-local multicast",
 22379  		},
 22380  		"Invalid AppProtocol": {
 22381  			endpoints: core.Endpoints{
 22382  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22383  				Subsets: []core.EndpointSubset{{
 22384  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22385  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}},
 22386  				}},
 22387  			},
 22388  			errorType:   "FieldValueInvalid",
 22389  			errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character",
 22390  		},
 22391  	}
 22392  
 22393  	for k, v := range errorCases {
 22394  		t.Run(k, func(t *testing.T) {
 22395  			if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22396  				t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs)
 22397  			}
 22398  		})
 22399  	}
 22400  }
 22401  
 22402  func TestValidateEndpointsUpdate(t *testing.T) {
 22403  	baseEndpoints := core.Endpoints{
 22404  		ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"},
 22405  		Subsets: []core.EndpointSubset{{
 22406  			Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}},
 22407  		}},
 22408  	}
 22409  
 22410  	testCases := map[string]struct {
 22411  		tweakOldEndpoints func(ep *core.Endpoints)
 22412  		tweakNewEndpoints func(ep *core.Endpoints)
 22413  		numExpectedErrors int
 22414  	}{
 22415  		"update to valid app protocol": {
 22416  			tweakOldEndpoints: func(ep *core.Endpoints) {
 22417  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 22418  			},
 22419  			tweakNewEndpoints: func(ep *core.Endpoints) {
 22420  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 22421  			},
 22422  			numExpectedErrors: 0,
 22423  		},
 22424  		"update to invalid app protocol": {
 22425  			tweakOldEndpoints: func(ep *core.Endpoints) {
 22426  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 22427  			},
 22428  			tweakNewEndpoints: func(ep *core.Endpoints) {
 22429  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 22430  			},
 22431  			numExpectedErrors: 1,
 22432  		},
 22433  	}
 22434  
 22435  	for name, tc := range testCases {
 22436  		t.Run(name, func(t *testing.T) {
 22437  			oldEndpoints := baseEndpoints.DeepCopy()
 22438  			tc.tweakOldEndpoints(oldEndpoints)
 22439  			newEndpoints := baseEndpoints.DeepCopy()
 22440  			tc.tweakNewEndpoints(newEndpoints)
 22441  
 22442  			errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints)
 22443  			if len(errs) != tc.numExpectedErrors {
 22444  				t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs)
 22445  			}
 22446  
 22447  		})
 22448  	}
 22449  }
 22450  
 22451  func TestValidateWindowsSecurityContext(t *testing.T) {
 22452  	tests := []struct {
 22453  		name        string
 22454  		sc          *core.PodSpec
 22455  		expectError bool
 22456  		errorMsg    string
 22457  		errorType   field.ErrorType
 22458  	}{{
 22459  		name:        "pod with SELinux Options",
 22460  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}},
 22461  		expectError: true,
 22462  		errorMsg:    "cannot be set for a windows pod",
 22463  		errorType:   "FieldValueForbidden",
 22464  	}, {
 22465  		name:        "pod with SeccompProfile",
 22466  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}},
 22467  		expectError: true,
 22468  		errorMsg:    "cannot be set for a windows pod",
 22469  		errorType:   "FieldValueForbidden",
 22470  	}, {
 22471  		name:        "pod with AppArmorProfile",
 22472  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{AppArmorProfile: &core.AppArmorProfile{Type: core.AppArmorProfileTypeRuntimeDefault}}}}},
 22473  		expectError: true,
 22474  		errorMsg:    "cannot be set for a windows pod",
 22475  		errorType:   "FieldValueForbidden",
 22476  	}, {
 22477  		name:        "pod with WindowsOptions, no error",
 22478  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}},
 22479  		expectError: false,
 22480  	},
 22481  	}
 22482  	for _, test := range tests {
 22483  		t.Run(test.name, func(t *testing.T) {
 22484  			errs := validateWindows(test.sc, field.NewPath("field"))
 22485  			if test.expectError && len(errs) > 0 {
 22486  				if errs[0].Type != test.errorType {
 22487  					t.Errorf("expected error type %q got %q", test.errorType, errs[0].Type)
 22488  				}
 22489  				if errs[0].Detail != test.errorMsg {
 22490  					t.Errorf("expected error detail %q, got %q", test.errorMsg, errs[0].Detail)
 22491  				}
 22492  			} else if test.expectError && len(errs) == 0 {
 22493  				t.Error("Unexpected success")
 22494  			}
 22495  			if !test.expectError && len(errs) != 0 {
 22496  				t.Errorf("Unexpected error(s): %v", errs)
 22497  			}
 22498  		})
 22499  	}
 22500  }
 22501  
 22502  func TestValidateOSFields(t *testing.T) {
 22503  	// Contains the list of OS specific fields within pod spec.
 22504  	// All the fields in pod spec should be either osSpecific or osNeutral field
 22505  	// To make a field OS specific:
 22506  	// - Add documentation to the os specific field indicating which os it can/cannot be set for
 22507  	// - Add documentation to the os field in the api
 22508  	// - Add validation logic validateLinux, validateWindows functions to make sure the field is only set for eligible OSes
 22509  	osSpecificFields := sets.NewString(
 22510  		"Containers[*].SecurityContext.AppArmorProfile",
 22511  		"Containers[*].SecurityContext.AllowPrivilegeEscalation",
 22512  		"Containers[*].SecurityContext.Capabilities",
 22513  		"Containers[*].SecurityContext.Privileged",
 22514  		"Containers[*].SecurityContext.ProcMount",
 22515  		"Containers[*].SecurityContext.ReadOnlyRootFilesystem",
 22516  		"Containers[*].SecurityContext.RunAsGroup",
 22517  		"Containers[*].SecurityContext.RunAsUser",
 22518  		"Containers[*].SecurityContext.SELinuxOptions",
 22519  		"Containers[*].SecurityContext.SeccompProfile",
 22520  		"Containers[*].SecurityContext.WindowsOptions",
 22521  		"InitContainers[*].SecurityContext.AppArmorProfile",
 22522  		"InitContainers[*].SecurityContext.AllowPrivilegeEscalation",
 22523  		"InitContainers[*].SecurityContext.Capabilities",
 22524  		"InitContainers[*].SecurityContext.Privileged",
 22525  		"InitContainers[*].SecurityContext.ProcMount",
 22526  		"InitContainers[*].SecurityContext.ReadOnlyRootFilesystem",
 22527  		"InitContainers[*].SecurityContext.RunAsGroup",
 22528  		"InitContainers[*].SecurityContext.RunAsUser",
 22529  		"InitContainers[*].SecurityContext.SELinuxOptions",
 22530  		"InitContainers[*].SecurityContext.SeccompProfile",
 22531  		"InitContainers[*].SecurityContext.WindowsOptions",
 22532  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AppArmorProfile",
 22533  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation",
 22534  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities",
 22535  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged",
 22536  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ProcMount",
 22537  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ReadOnlyRootFilesystem",
 22538  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsGroup",
 22539  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsUser",
 22540  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SELinuxOptions",
 22541  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
 22542  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
 22543  		"OS",
 22544  		"SecurityContext.AppArmorProfile",
 22545  		"SecurityContext.FSGroup",
 22546  		"SecurityContext.FSGroupChangePolicy",
 22547  		"SecurityContext.HostIPC",
 22548  		"SecurityContext.HostNetwork",
 22549  		"SecurityContext.HostPID",
 22550  		"SecurityContext.HostUsers",
 22551  		"SecurityContext.RunAsGroup",
 22552  		"SecurityContext.RunAsUser",
 22553  		"SecurityContext.SELinuxOptions",
 22554  		"SecurityContext.SeccompProfile",
 22555  		"SecurityContext.ShareProcessNamespace",
 22556  		"SecurityContext.SupplementalGroups",
 22557  		"SecurityContext.Sysctls",
 22558  		"SecurityContext.WindowsOptions",
 22559  	)
 22560  	osNeutralFields := sets.NewString(
 22561  		"ActiveDeadlineSeconds",
 22562  		"Affinity",
 22563  		"AutomountServiceAccountToken",
 22564  		"Containers[*].Args",
 22565  		"Containers[*].Command",
 22566  		"Containers[*].Env",
 22567  		"Containers[*].EnvFrom",
 22568  		"Containers[*].Image",
 22569  		"Containers[*].ImagePullPolicy",
 22570  		"Containers[*].Lifecycle",
 22571  		"Containers[*].LivenessProbe",
 22572  		"Containers[*].Name",
 22573  		"Containers[*].Ports",
 22574  		"Containers[*].ReadinessProbe",
 22575  		"Containers[*].Resources",
 22576  		"Containers[*].ResizePolicy[*].RestartPolicy",
 22577  		"Containers[*].ResizePolicy[*].ResourceName",
 22578  		"Containers[*].RestartPolicy",
 22579  		"Containers[*].SecurityContext.RunAsNonRoot",
 22580  		"Containers[*].Stdin",
 22581  		"Containers[*].StdinOnce",
 22582  		"Containers[*].StartupProbe",
 22583  		"Containers[*].VolumeDevices[*]",
 22584  		"Containers[*].VolumeMounts[*]",
 22585  		"Containers[*].TTY",
 22586  		"Containers[*].TerminationMessagePath",
 22587  		"Containers[*].TerminationMessagePolicy",
 22588  		"Containers[*].WorkingDir",
 22589  		"DNSPolicy",
 22590  		"EnableServiceLinks",
 22591  		"EphemeralContainers[*].EphemeralContainerCommon.Args",
 22592  		"EphemeralContainers[*].EphemeralContainerCommon.Command",
 22593  		"EphemeralContainers[*].EphemeralContainerCommon.Env",
 22594  		"EphemeralContainers[*].EphemeralContainerCommon.EnvFrom",
 22595  		"EphemeralContainers[*].EphemeralContainerCommon.Image",
 22596  		"EphemeralContainers[*].EphemeralContainerCommon.ImagePullPolicy",
 22597  		"EphemeralContainers[*].EphemeralContainerCommon.Lifecycle",
 22598  		"EphemeralContainers[*].EphemeralContainerCommon.LivenessProbe",
 22599  		"EphemeralContainers[*].EphemeralContainerCommon.Name",
 22600  		"EphemeralContainers[*].EphemeralContainerCommon.Ports",
 22601  		"EphemeralContainers[*].EphemeralContainerCommon.ReadinessProbe",
 22602  		"EphemeralContainers[*].EphemeralContainerCommon.Resources",
 22603  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy",
 22604  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName",
 22605  		"EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy",
 22606  		"EphemeralContainers[*].EphemeralContainerCommon.Stdin",
 22607  		"EphemeralContainers[*].EphemeralContainerCommon.StdinOnce",
 22608  		"EphemeralContainers[*].EphemeralContainerCommon.TTY",
 22609  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePath",
 22610  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePolicy",
 22611  		"EphemeralContainers[*].EphemeralContainerCommon.WorkingDir",
 22612  		"EphemeralContainers[*].TargetContainerName",
 22613  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsNonRoot",
 22614  		"EphemeralContainers[*].EphemeralContainerCommon.StartupProbe",
 22615  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeDevices[*]",
 22616  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeMounts[*]",
 22617  		"HostAliases",
 22618  		"Hostname",
 22619  		"ImagePullSecrets",
 22620  		"InitContainers[*].Args",
 22621  		"InitContainers[*].Command",
 22622  		"InitContainers[*].Env",
 22623  		"InitContainers[*].EnvFrom",
 22624  		"InitContainers[*].Image",
 22625  		"InitContainers[*].ImagePullPolicy",
 22626  		"InitContainers[*].Lifecycle",
 22627  		"InitContainers[*].LivenessProbe",
 22628  		"InitContainers[*].Name",
 22629  		"InitContainers[*].Ports",
 22630  		"InitContainers[*].ReadinessProbe",
 22631  		"InitContainers[*].Resources",
 22632  		"InitContainers[*].ResizePolicy[*].RestartPolicy",
 22633  		"InitContainers[*].ResizePolicy[*].ResourceName",
 22634  		"InitContainers[*].RestartPolicy",
 22635  		"InitContainers[*].Stdin",
 22636  		"InitContainers[*].StdinOnce",
 22637  		"InitContainers[*].TTY",
 22638  		"InitContainers[*].TerminationMessagePath",
 22639  		"InitContainers[*].TerminationMessagePolicy",
 22640  		"InitContainers[*].WorkingDir",
 22641  		"InitContainers[*].SecurityContext.RunAsNonRoot",
 22642  		"InitContainers[*].StartupProbe",
 22643  		"InitContainers[*].VolumeDevices[*]",
 22644  		"InitContainers[*].VolumeMounts[*]",
 22645  		"NodeName",
 22646  		"NodeSelector",
 22647  		"PreemptionPolicy",
 22648  		"Priority",
 22649  		"PriorityClassName",
 22650  		"ReadinessGates",
 22651  		"ResourceClaims[*].Name",
 22652  		"ResourceClaims[*].Source.ResourceClaimName",
 22653  		"ResourceClaims[*].Source.ResourceClaimTemplateName",
 22654  		"RestartPolicy",
 22655  		"RuntimeClassName",
 22656  		"SchedulerName",
 22657  		"SchedulingGates[*].Name",
 22658  		"SecurityContext.RunAsNonRoot",
 22659  		"ServiceAccountName",
 22660  		"SetHostnameAsFQDN",
 22661  		"Subdomain",
 22662  		"TerminationGracePeriodSeconds",
 22663  		"Volumes",
 22664  		"DNSConfig",
 22665  		"Overhead",
 22666  		"Tolerations",
 22667  		"TopologySpreadConstraints",
 22668  	)
 22669  
 22670  	expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields)
 22671  
 22672  	result := collectResourcePaths(t, expect, reflect.TypeOf(&core.PodSpec{}), nil)
 22673  
 22674  	if !expect.Equal(result) {
 22675  		// expected fields missing from result
 22676  		missing := expect.Difference(result)
 22677  		// unexpected fields in result but not specified in expect
 22678  		unexpected := result.Difference(expect)
 22679  		if len(missing) > 0 {
 22680  			t.Errorf("the following fields were expected, but missing from the result. "+
 22681  				"If the field has been removed, please remove it from the osNeutralFields set "+
 22682  				"or remove it from the osSpecificFields set, as appropriate:\n%s",
 22683  				strings.Join(missing.List(), "\n"))
 22684  		}
 22685  		if len(unexpected) > 0 {
 22686  			t.Errorf("the following fields were in the result, but unexpected. "+
 22687  				"If the field is new, please add it to the osNeutralFields set "+
 22688  				"or add it to the osSpecificFields set, as appropriate:\n%s",
 22689  				strings.Join(unexpected.List(), "\n"))
 22690  		}
 22691  	}
 22692  }
 22693  
 22694  func TestValidateSchedulingGates(t *testing.T) {
 22695  	fieldPath := field.NewPath("field")
 22696  
 22697  	tests := []struct {
 22698  		name            string
 22699  		schedulingGates []core.PodSchedulingGate
 22700  		wantFieldErrors field.ErrorList
 22701  	}{{
 22702  		name:            "nil gates",
 22703  		schedulingGates: nil,
 22704  		wantFieldErrors: field.ErrorList{},
 22705  	}, {
 22706  		name: "empty string in gates",
 22707  		schedulingGates: []core.PodSchedulingGate{
 22708  			{Name: "foo"},
 22709  			{Name: ""},
 22710  		},
 22711  		wantFieldErrors: field.ErrorList{
 22712  			field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"),
 22713  			field.Invalid(fieldPath.Index(1), "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"),
 22714  		},
 22715  	}, {
 22716  		name: "legal gates",
 22717  		schedulingGates: []core.PodSchedulingGate{
 22718  			{Name: "foo"},
 22719  			{Name: "bar"},
 22720  		},
 22721  		wantFieldErrors: field.ErrorList{},
 22722  	}, {
 22723  		name: "illegal gates",
 22724  		schedulingGates: []core.PodSchedulingGate{
 22725  			{Name: "foo"},
 22726  			{Name: "\nbar"},
 22727  		},
 22728  		wantFieldErrors: []*field.Error{field.Invalid(fieldPath.Index(1), "\nbar", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")},
 22729  	}, {
 22730  		name: "duplicated gates (single duplication)",
 22731  		schedulingGates: []core.PodSchedulingGate{
 22732  			{Name: "foo"},
 22733  			{Name: "bar"},
 22734  			{Name: "bar"},
 22735  		},
 22736  		wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
 22737  	}, {
 22738  		name: "duplicated gates (multiple duplications)",
 22739  		schedulingGates: []core.PodSchedulingGate{
 22740  			{Name: "foo"},
 22741  			{Name: "bar"},
 22742  			{Name: "foo"},
 22743  			{Name: "baz"},
 22744  			{Name: "foo"},
 22745  			{Name: "bar"},
 22746  		},
 22747  		wantFieldErrors: field.ErrorList{
 22748  			field.Duplicate(fieldPath.Index(2), "foo"),
 22749  			field.Duplicate(fieldPath.Index(4), "foo"),
 22750  			field.Duplicate(fieldPath.Index(5), "bar"),
 22751  		},
 22752  	},
 22753  	}
 22754  	for _, tt := range tests {
 22755  		t.Run(tt.name, func(t *testing.T) {
 22756  			errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
 22757  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 22758  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 22759  			}
 22760  		})
 22761  	}
 22762  }
 22763  
 22764  // collectResourcePaths traverses the object, computing all the struct paths.
 22765  func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
 22766  	if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
 22767  		return sets.NewString(pathStr)
 22768  	}
 22769  
 22770  	paths := sets.NewString()
 22771  	switch tp.Kind() {
 22772  	case reflect.Pointer:
 22773  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path).List()...)
 22774  	case reflect.Struct:
 22775  		for i := 0; i < tp.NumField(); i++ {
 22776  			field := tp.Field(i)
 22777  			paths.Insert(collectResourcePaths(t, skipRecurseList, field.Type, path.Child(field.Name)).List()...)
 22778  		}
 22779  	case reflect.Map, reflect.Slice:
 22780  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path.Key("*")).List()...)
 22781  	case reflect.Interface:
 22782  		t.Fatalf("unexpected interface{} field %s", path.String())
 22783  	default:
 22784  		// if we hit a primitive type, we're at a leaf
 22785  		paths.Insert(path.String())
 22786  	}
 22787  	return paths
 22788  }
 22789  
 22790  func TestValidateTLSSecret(t *testing.T) {
 22791  	successCases := map[string]core.Secret{
 22792  		"empty certificate chain": {
 22793  			ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"},
 22794  			Data: map[string][]byte{
 22795  				core.TLSCertKey:       []byte("public key"),
 22796  				core.TLSPrivateKeyKey: []byte("private key"),
 22797  			},
 22798  		},
 22799  	}
 22800  	for k, v := range successCases {
 22801  		if errs := ValidateSecret(&v); len(errs) != 0 {
 22802  			t.Errorf("Expected success for %s, got %v", k, errs)
 22803  		}
 22804  	}
 22805  	errorCases := map[string]struct {
 22806  		secrets     core.Secret
 22807  		errorType   field.ErrorType
 22808  		errorDetail string
 22809  	}{
 22810  		"missing public key": {
 22811  			secrets: core.Secret{
 22812  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 22813  				Data: map[string][]byte{
 22814  					core.TLSCertKey: []byte("public key"),
 22815  				},
 22816  			},
 22817  			errorType: "FieldValueRequired",
 22818  		},
 22819  		"missing private key": {
 22820  			secrets: core.Secret{
 22821  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 22822  				Data: map[string][]byte{
 22823  					core.TLSCertKey: []byte("public key"),
 22824  				},
 22825  			},
 22826  			errorType: "FieldValueRequired",
 22827  		},
 22828  	}
 22829  	for k, v := range errorCases {
 22830  		if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22831  			t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22832  		}
 22833  	}
 22834  }
 22835  
 22836  func TestValidateLinuxSecurityContext(t *testing.T) {
 22837  	runAsUser := int64(1)
 22838  	validLinuxSC := &core.SecurityContext{
 22839  		Privileged: utilpointer.Bool(false),
 22840  		Capabilities: &core.Capabilities{
 22841  			Add:  []core.Capability{"foo"},
 22842  			Drop: []core.Capability{"bar"},
 22843  		},
 22844  		SELinuxOptions: &core.SELinuxOptions{
 22845  			User:  "user",
 22846  			Role:  "role",
 22847  			Type:  "type",
 22848  			Level: "level",
 22849  		},
 22850  		RunAsUser: &runAsUser,
 22851  	}
 22852  	invalidLinuxSC := &core.SecurityContext{
 22853  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
 22854  	}
 22855  	cases := map[string]struct {
 22856  		sc          *core.PodSpec
 22857  		expectErr   bool
 22858  		errorType   field.ErrorType
 22859  		errorDetail string
 22860  	}{
 22861  		"valid SC, linux, no error": {
 22862  			sc:        &core.PodSpec{Containers: []core.Container{{SecurityContext: validLinuxSC}}},
 22863  			expectErr: false,
 22864  		},
 22865  		"invalid SC, linux, error": {
 22866  			sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: invalidLinuxSC}}},
 22867  			errorType:   "FieldValueForbidden",
 22868  			errorDetail: "windows options cannot be set for a linux pod",
 22869  			expectErr:   true,
 22870  		},
 22871  	}
 22872  	for k, v := range cases {
 22873  		t.Run(k, func(t *testing.T) {
 22874  			errs := validateLinux(v.sc, field.NewPath("field"))
 22875  			if v.expectErr && len(errs) > 0 {
 22876  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22877  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22878  				}
 22879  			} else if v.expectErr && len(errs) == 0 {
 22880  				t.Errorf("Unexpected success")
 22881  			}
 22882  			if !v.expectErr && len(errs) != 0 {
 22883  				t.Errorf("Unexpected error(s): %v", errs)
 22884  			}
 22885  		})
 22886  	}
 22887  }
 22888  
 22889  func TestValidateSecurityContext(t *testing.T) {
 22890  	runAsUser := int64(1)
 22891  	fullValidSC := func() *core.SecurityContext {
 22892  		return &core.SecurityContext{
 22893  			Privileged: utilpointer.Bool(false),
 22894  			Capabilities: &core.Capabilities{
 22895  				Add:  []core.Capability{"foo"},
 22896  				Drop: []core.Capability{"bar"},
 22897  			},
 22898  			SELinuxOptions: &core.SELinuxOptions{
 22899  				User:  "user",
 22900  				Role:  "role",
 22901  				Type:  "type",
 22902  				Level: "level",
 22903  			},
 22904  			RunAsUser: &runAsUser,
 22905  		}
 22906  	}
 22907  
 22908  	// setup data
 22909  	allSettings := fullValidSC()
 22910  	noCaps := fullValidSC()
 22911  	noCaps.Capabilities = nil
 22912  
 22913  	noSELinux := fullValidSC()
 22914  	noSELinux.SELinuxOptions = nil
 22915  
 22916  	noPrivRequest := fullValidSC()
 22917  	noPrivRequest.Privileged = nil
 22918  
 22919  	noRunAsUser := fullValidSC()
 22920  	noRunAsUser.RunAsUser = nil
 22921  
 22922  	procMountSet := fullValidSC()
 22923  	defPmt := core.DefaultProcMount
 22924  	procMountSet.ProcMount = &defPmt
 22925  
 22926  	umPmt := core.UnmaskedProcMount
 22927  	procMountUnmasked := fullValidSC()
 22928  	procMountUnmasked.ProcMount = &umPmt
 22929  
 22930  	successCases := map[string]struct {
 22931  		sc        *core.SecurityContext
 22932  		hostUsers bool
 22933  	}{
 22934  		"all settings":        {allSettings, false},
 22935  		"no capabilities":     {noCaps, false},
 22936  		"no selinux":          {noSELinux, false},
 22937  		"no priv request":     {noPrivRequest, false},
 22938  		"no run as user":      {noRunAsUser, false},
 22939  		"proc mount set":      {procMountSet, true},
 22940  		"proc mount unmasked": {procMountUnmasked, false},
 22941  	}
 22942  	for k, v := range successCases {
 22943  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) != 0 {
 22944  			t.Errorf("[%s] Expected success, got %v", k, errs)
 22945  		}
 22946  	}
 22947  
 22948  	privRequestWithGlobalDeny := fullValidSC()
 22949  	privRequestWithGlobalDeny.Privileged = utilpointer.Bool(true)
 22950  
 22951  	negativeRunAsUser := fullValidSC()
 22952  	negativeUser := int64(-1)
 22953  	negativeRunAsUser.RunAsUser = &negativeUser
 22954  
 22955  	privWithoutEscalation := fullValidSC()
 22956  	privWithoutEscalation.Privileged = utilpointer.Bool(true)
 22957  	privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 22958  
 22959  	capSysAdminWithoutEscalation := fullValidSC()
 22960  	capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
 22961  	capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 22962  
 22963  	errorCases := map[string]struct {
 22964  		sc           *core.SecurityContext
 22965  		errorType    field.ErrorType
 22966  		errorDetail  string
 22967  		capAllowPriv bool
 22968  	}{
 22969  		"request privileged when capabilities forbids": {
 22970  			sc:          privRequestWithGlobalDeny,
 22971  			errorType:   "FieldValueForbidden",
 22972  			errorDetail: "disallowed by cluster policy",
 22973  		},
 22974  		"negative RunAsUser": {
 22975  			sc:          negativeRunAsUser,
 22976  			errorType:   "FieldValueInvalid",
 22977  			errorDetail: "must be between",
 22978  		},
 22979  		"with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
 22980  			sc:          capSysAdminWithoutEscalation,
 22981  			errorType:   "FieldValueInvalid",
 22982  			errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
 22983  		},
 22984  		"with privileged and allowPrivilegeEscalation false": {
 22985  			sc:           privWithoutEscalation,
 22986  			errorType:    "FieldValueInvalid",
 22987  			errorDetail:  "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
 22988  			capAllowPriv: true,
 22989  		},
 22990  		"with unmasked proc mount type and no user namespace": {
 22991  			sc:          procMountUnmasked,
 22992  			errorType:   "FieldValueInvalid",
 22993  			errorDetail: "`hostUsers` must be false to use `Unmasked`",
 22994  		},
 22995  	}
 22996  	for k, v := range errorCases {
 22997  		capabilities.SetForTests(capabilities.Capabilities{
 22998  			AllowPrivileged: v.capAllowPriv,
 22999  		})
 23000  		// note the unconditional `true` here for hostUsers. The failure case to test for ProcMount only includes it being true,
 23001  		// and the field is ignored if ProcMount isn't set. Thus, we can unconditionally set to `true` and simplify the test matrix setup.
 23002  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), true); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 23003  			t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 23004  		}
 23005  	}
 23006  }
 23007  
 23008  func fakeValidSecurityContext(priv bool) *core.SecurityContext {
 23009  	return &core.SecurityContext{
 23010  		Privileged: &priv,
 23011  	}
 23012  }
 23013  
 23014  func TestValidPodLogOptions(t *testing.T) {
 23015  	now := metav1.Now()
 23016  	negative := int64(-1)
 23017  	zero := int64(0)
 23018  	positive := int64(1)
 23019  	tests := []struct {
 23020  		opt  core.PodLogOptions
 23021  		errs int
 23022  	}{
 23023  		{core.PodLogOptions{}, 0},
 23024  		{core.PodLogOptions{Previous: true}, 0},
 23025  		{core.PodLogOptions{Follow: true}, 0},
 23026  		{core.PodLogOptions{TailLines: &zero}, 0},
 23027  		{core.PodLogOptions{TailLines: &negative}, 1},
 23028  		{core.PodLogOptions{TailLines: &positive}, 0},
 23029  		{core.PodLogOptions{LimitBytes: &zero}, 1},
 23030  		{core.PodLogOptions{LimitBytes: &negative}, 1},
 23031  		{core.PodLogOptions{LimitBytes: &positive}, 0},
 23032  		{core.PodLogOptions{SinceSeconds: &negative}, 1},
 23033  		{core.PodLogOptions{SinceSeconds: &positive}, 0},
 23034  		{core.PodLogOptions{SinceSeconds: &zero}, 1},
 23035  		{core.PodLogOptions{SinceTime: &now}, 0},
 23036  	}
 23037  	for i, test := range tests {
 23038  		errs := ValidatePodLogOptions(&test.opt)
 23039  		if test.errs != len(errs) {
 23040  			t.Errorf("%d: Unexpected errors: %v", i, errs)
 23041  		}
 23042  	}
 23043  }
 23044  
 23045  func TestValidateConfigMap(t *testing.T) {
 23046  	newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap {
 23047  		return core.ConfigMap{
 23048  			ObjectMeta: metav1.ObjectMeta{
 23049  				Name:      name,
 23050  				Namespace: namespace,
 23051  			},
 23052  			Data:       data,
 23053  			BinaryData: binaryData,
 23054  		}
 23055  	}
 23056  
 23057  	var (
 23058  		validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")})
 23059  		maxKeyLength   = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil)
 23060  
 23061  		emptyName               = newConfigMap("", "validns", nil, nil)
 23062  		invalidName             = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil)
 23063  		emptyNs                 = newConfigMap("validname", "", nil, nil)
 23064  		invalidNs               = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil)
 23065  		invalidKey              = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil)
 23066  		leadingDotKey           = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil)
 23067  		dotKey                  = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil)
 23068  		doubleDotKey            = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil)
 23069  		overMaxKeyLength        = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil)
 23070  		overMaxSize             = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil)
 23071  		duplicatedKey           = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")})
 23072  		binDataInvalidKey       = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")})
 23073  		binDataLeadingDotKey    = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")})
 23074  		binDataDotKey           = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")})
 23075  		binDataDoubleDotKey     = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")})
 23076  		binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")})
 23077  		binDataOverMaxSize      = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)})
 23078  		binNonUtf8Value         = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}})
 23079  	)
 23080  
 23081  	tests := map[string]struct {
 23082  		cfg     core.ConfigMap
 23083  		isValid bool
 23084  	}{
 23085  		"valid":                           {validConfigMap, true},
 23086  		"max key length":                  {maxKeyLength, true},
 23087  		"leading dot key":                 {leadingDotKey, true},
 23088  		"empty name":                      {emptyName, false},
 23089  		"invalid name":                    {invalidName, false},
 23090  		"invalid key":                     {invalidKey, false},
 23091  		"empty namespace":                 {emptyNs, false},
 23092  		"invalid namespace":               {invalidNs, false},
 23093  		"dot key":                         {dotKey, false},
 23094  		"double dot key":                  {doubleDotKey, false},
 23095  		"over max key length":             {overMaxKeyLength, false},
 23096  		"over max size":                   {overMaxSize, false},
 23097  		"duplicated key":                  {duplicatedKey, false},
 23098  		"binary data invalid key":         {binDataInvalidKey, false},
 23099  		"binary data leading dot key":     {binDataLeadingDotKey, true},
 23100  		"binary data dot key":             {binDataDotKey, false},
 23101  		"binary data double dot key":      {binDataDoubleDotKey, false},
 23102  		"binary data over max key length": {binDataOverMaxKeyLength, false},
 23103  		"binary data max size":            {binDataOverMaxSize, false},
 23104  		"binary data non utf-8 bytes":     {binNonUtf8Value, true},
 23105  	}
 23106  
 23107  	for name, tc := range tests {
 23108  		errs := ValidateConfigMap(&tc.cfg)
 23109  		if tc.isValid && len(errs) > 0 {
 23110  			t.Errorf("%v: unexpected error: %v", name, errs)
 23111  		}
 23112  		if !tc.isValid && len(errs) == 0 {
 23113  			t.Errorf("%v: unexpected non-error", name)
 23114  		}
 23115  	}
 23116  }
 23117  
 23118  func TestValidateConfigMapUpdate(t *testing.T) {
 23119  	newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap {
 23120  		return core.ConfigMap{
 23121  			ObjectMeta: metav1.ObjectMeta{
 23122  				Name:            name,
 23123  				Namespace:       namespace,
 23124  				ResourceVersion: version,
 23125  			},
 23126  			Data: data,
 23127  		}
 23128  	}
 23129  	validConfigMap := func() core.ConfigMap {
 23130  		return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
 23131  	}
 23132  
 23133  	falseVal := false
 23134  	trueVal := true
 23135  
 23136  	configMap := validConfigMap()
 23137  	immutableConfigMap := validConfigMap()
 23138  	immutableConfigMap.Immutable = &trueVal
 23139  	mutableConfigMap := validConfigMap()
 23140  	mutableConfigMap.Immutable = &falseVal
 23141  
 23142  	configMapWithData := validConfigMap()
 23143  	configMapWithData.Data["key-2"] = "value-2"
 23144  	immutableConfigMapWithData := validConfigMap()
 23145  	immutableConfigMapWithData.Immutable = &trueVal
 23146  	immutableConfigMapWithData.Data["key-2"] = "value-2"
 23147  
 23148  	configMapWithChangedData := validConfigMap()
 23149  	configMapWithChangedData.Data["key"] = "foo"
 23150  	immutableConfigMapWithChangedData := validConfigMap()
 23151  	immutableConfigMapWithChangedData.Immutable = &trueVal
 23152  	immutableConfigMapWithChangedData.Data["key"] = "foo"
 23153  
 23154  	noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
 23155  
 23156  	cases := []struct {
 23157  		name   string
 23158  		newCfg core.ConfigMap
 23159  		oldCfg core.ConfigMap
 23160  		valid  bool
 23161  	}{{
 23162  		name:   "valid",
 23163  		newCfg: configMap,
 23164  		oldCfg: configMap,
 23165  		valid:  true,
 23166  	}, {
 23167  		name:   "invalid",
 23168  		newCfg: noVersion,
 23169  		oldCfg: configMap,
 23170  		valid:  false,
 23171  	}, {
 23172  		name:   "mark configmap immutable",
 23173  		oldCfg: configMap,
 23174  		newCfg: immutableConfigMap,
 23175  		valid:  true,
 23176  	}, {
 23177  		name:   "revert immutable configmap",
 23178  		oldCfg: immutableConfigMap,
 23179  		newCfg: configMap,
 23180  		valid:  false,
 23181  	}, {
 23182  		name:   "mark immutable configmap mutable",
 23183  		oldCfg: immutableConfigMap,
 23184  		newCfg: mutableConfigMap,
 23185  		valid:  false,
 23186  	}, {
 23187  		name:   "add data in configmap",
 23188  		oldCfg: configMap,
 23189  		newCfg: configMapWithData,
 23190  		valid:  true,
 23191  	}, {
 23192  		name:   "add data in immutable configmap",
 23193  		oldCfg: immutableConfigMap,
 23194  		newCfg: immutableConfigMapWithData,
 23195  		valid:  false,
 23196  	}, {
 23197  		name:   "change data in configmap",
 23198  		oldCfg: configMap,
 23199  		newCfg: configMapWithChangedData,
 23200  		valid:  true,
 23201  	}, {
 23202  		name:   "change data in immutable configmap",
 23203  		oldCfg: immutableConfigMap,
 23204  		newCfg: immutableConfigMapWithChangedData,
 23205  		valid:  false,
 23206  	},
 23207  	}
 23208  
 23209  	for _, tc := range cases {
 23210  		t.Run(tc.name, func(t *testing.T) {
 23211  			errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
 23212  			if tc.valid && len(errs) > 0 {
 23213  				t.Errorf("Unexpected error: %v", errs)
 23214  			}
 23215  			if !tc.valid && len(errs) == 0 {
 23216  				t.Errorf("Unexpected lack of error")
 23217  			}
 23218  		})
 23219  	}
 23220  }
 23221  
 23222  func TestValidateHasLabel(t *testing.T) {
 23223  	successCase := metav1.ObjectMeta{
 23224  		Name:      "123",
 23225  		Namespace: "ns",
 23226  		Labels: map[string]string{
 23227  			"other": "blah",
 23228  			"foo":   "bar",
 23229  		},
 23230  	}
 23231  	if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 {
 23232  		t.Errorf("expected success: %v", errs)
 23233  	}
 23234  
 23235  	missingCase := metav1.ObjectMeta{
 23236  		Name:      "123",
 23237  		Namespace: "ns",
 23238  		Labels: map[string]string{
 23239  			"other": "blah",
 23240  		},
 23241  	}
 23242  	if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 23243  		t.Errorf("expected failure")
 23244  	}
 23245  
 23246  	wrongValueCase := metav1.ObjectMeta{
 23247  		Name:      "123",
 23248  		Namespace: "ns",
 23249  		Labels: map[string]string{
 23250  			"other": "blah",
 23251  			"foo":   "notbar",
 23252  		},
 23253  	}
 23254  	if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 23255  		t.Errorf("expected failure")
 23256  	}
 23257  }
 23258  
 23259  func TestIsValidSysctlName(t *testing.T) {
 23260  	valid := []string{
 23261  		"a.b.c.d",
 23262  		"a",
 23263  		"a_b",
 23264  		"a-b",
 23265  		"abc",
 23266  		"abc.def",
 23267  		"a/b/c/d",
 23268  		"a/b.c",
 23269  	}
 23270  	invalid := []string{
 23271  		"",
 23272  		"*",
 23273  		"ä",
 23274  		"a_",
 23275  		"_",
 23276  		"__",
 23277  		"_a",
 23278  		"_a._b",
 23279  		"-",
 23280  		".",
 23281  		"a.",
 23282  		".a",
 23283  		"a.b.",
 23284  		"a*.b",
 23285  		"a*b",
 23286  		"*a",
 23287  		"a.*",
 23288  		"*",
 23289  		"abc*",
 23290  		"a.abc*",
 23291  		"a.b.*",
 23292  		"Abc",
 23293  		"/",
 23294  		"/a",
 23295  		"a/abc*",
 23296  		"a/b/*",
 23297  		func(n int) string {
 23298  			x := make([]byte, n)
 23299  			for i := range x {
 23300  				x[i] = byte('a')
 23301  			}
 23302  			return string(x)
 23303  		}(256),
 23304  	}
 23305  
 23306  	for _, s := range valid {
 23307  		if !IsValidSysctlName(s) {
 23308  			t.Errorf("%q expected to be a valid sysctl name", s)
 23309  		}
 23310  	}
 23311  	for _, s := range invalid {
 23312  		if IsValidSysctlName(s) {
 23313  			t.Errorf("%q expected to be an invalid sysctl name", s)
 23314  		}
 23315  	}
 23316  }
 23317  
 23318  func TestValidateSysctls(t *testing.T) {
 23319  	valid := []string{
 23320  		"net.foo.bar",
 23321  		"kernel.shmmax",
 23322  		"net.ipv4.conf.enp3s0/200.forwarding",
 23323  		"net/ipv4/conf/enp3s0.200/forwarding",
 23324  	}
 23325  	invalid := []string{
 23326  		"i..nvalid",
 23327  		"_invalid",
 23328  	}
 23329  
 23330  	invalidWithHostNet := []string{
 23331  		"net.ipv4.conf.enp3s0/200.forwarding",
 23332  		"net/ipv4/conf/enp3s0.200/forwarding",
 23333  	}
 23334  
 23335  	invalidWithHostIPC := []string{
 23336  		"kernel.shmmax",
 23337  		"kernel.msgmax",
 23338  	}
 23339  
 23340  	duplicates := []string{
 23341  		"kernel.shmmax",
 23342  		"kernel.shmmax",
 23343  	}
 23344  	opts := PodValidationOptions{
 23345  		AllowNamespacedSysctlsForHostNetAndHostIPC: false,
 23346  	}
 23347  
 23348  	sysctls := make([]core.Sysctl, len(valid))
 23349  	validSecurityContext := &core.PodSecurityContext{
 23350  		Sysctls: sysctls,
 23351  	}
 23352  	for i, sysctl := range valid {
 23353  		sysctls[i].Name = sysctl
 23354  	}
 23355  	errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts)
 23356  	if len(errs) != 0 {
 23357  		t.Errorf("unexpected validation errors: %v", errs)
 23358  	}
 23359  
 23360  	sysctls = make([]core.Sysctl, len(invalid))
 23361  	for i, sysctl := range invalid {
 23362  		sysctls[i].Name = sysctl
 23363  	}
 23364  	inValidSecurityContext := &core.PodSecurityContext{
 23365  		Sysctls: sysctls,
 23366  	}
 23367  	errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts)
 23368  	if len(errs) != 2 {
 23369  		t.Errorf("expected 2 validation errors. Got: %v", errs)
 23370  	} else {
 23371  		if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) {
 23372  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 23373  		}
 23374  		if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) {
 23375  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 23376  		}
 23377  	}
 23378  
 23379  	sysctls = make([]core.Sysctl, len(duplicates))
 23380  	for i, sysctl := range duplicates {
 23381  		sysctls[i].Name = sysctl
 23382  	}
 23383  	securityContextWithDup := &core.PodSecurityContext{
 23384  		Sysctls: sysctls,
 23385  	}
 23386  	errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts)
 23387  	if len(errs) != 1 {
 23388  		t.Errorf("unexpected validation errors: %v", errs)
 23389  	} else if errs[0].Type != field.ErrorTypeDuplicate {
 23390  		t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
 23391  	}
 23392  
 23393  	sysctls = make([]core.Sysctl, len(invalidWithHostNet))
 23394  	for i, sysctl := range invalidWithHostNet {
 23395  		sysctls[i].Name = sysctl
 23396  	}
 23397  	invalidSecurityContextWithHostNet := &core.PodSecurityContext{
 23398  		Sysctls:     sysctls,
 23399  		HostIPC:     false,
 23400  		HostNetwork: true,
 23401  	}
 23402  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 23403  	if len(errs) != 2 {
 23404  		t.Errorf("unexpected validation errors: %v", errs)
 23405  	}
 23406  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 23407  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 23408  	if len(errs) != 0 {
 23409  		t.Errorf("unexpected validation errors: %v", errs)
 23410  	}
 23411  
 23412  	sysctls = make([]core.Sysctl, len(invalidWithHostIPC))
 23413  	for i, sysctl := range invalidWithHostIPC {
 23414  		sysctls[i].Name = sysctl
 23415  	}
 23416  	invalidSecurityContextWithHostIPC := &core.PodSecurityContext{
 23417  		Sysctls:     sysctls,
 23418  		HostIPC:     true,
 23419  		HostNetwork: false,
 23420  	}
 23421  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false
 23422  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 23423  	if len(errs) != 2 {
 23424  		t.Errorf("unexpected validation errors: %v", errs)
 23425  	}
 23426  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 23427  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 23428  	if len(errs) != 0 {
 23429  		t.Errorf("unexpected validation errors: %v", errs)
 23430  	}
 23431  }
 23432  
 23433  func newNodeNameEndpoint(nodeName string) *core.Endpoints {
 23434  	ep := &core.Endpoints{
 23435  		ObjectMeta: metav1.ObjectMeta{
 23436  			Name:            "foo",
 23437  			Namespace:       metav1.NamespaceDefault,
 23438  			ResourceVersion: "1",
 23439  		},
 23440  		Subsets: []core.EndpointSubset{{
 23441  			NotReadyAddresses: []core.EndpointAddress{},
 23442  			Ports:             []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}},
 23443  			Addresses: []core.EndpointAddress{{
 23444  				IP:       "8.8.8.8",
 23445  				Hostname: "zookeeper1",
 23446  				NodeName: &nodeName}}}}}
 23447  	return ep
 23448  }
 23449  
 23450  func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
 23451  	oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend")
 23452  	updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
 23453  	// Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused.
 23454  	// The same ip will now have a different nodeName.
 23455  	errList := ValidateEndpoints(updatedEndpoint)
 23456  	errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
 23457  	if len(errList) != 0 {
 23458  		t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
 23459  	}
 23460  }
 23461  
 23462  func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
 23463  	// Check NodeName DNS validation
 23464  	endpoint := newNodeNameEndpoint("illegal*.nodename")
 23465  	errList := ValidateEndpoints(endpoint)
 23466  	if len(errList) == 0 {
 23467  		t.Error("Endpoint should reject invalid NodeName")
 23468  	}
 23469  }
 23470  
 23471  func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
 23472  	endpoint := newNodeNameEndpoint("10.10.1.1")
 23473  	errList := ValidateEndpoints(endpoint)
 23474  	if len(errList) != 0 {
 23475  		t.Error("Endpoint should accept a NodeName that is an IP address")
 23476  	}
 23477  }
 23478  
 23479  func TestValidateFlexVolumeSource(t *testing.T) {
 23480  	testcases := map[string]struct {
 23481  		source       *core.FlexVolumeSource
 23482  		expectedErrs map[string]string
 23483  	}{
 23484  		"valid": {
 23485  			source:       &core.FlexVolumeSource{Driver: "foo"},
 23486  			expectedErrs: map[string]string{},
 23487  		},
 23488  		"valid with options": {
 23489  			source:       &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
 23490  			expectedErrs: map[string]string{},
 23491  		},
 23492  		"no driver": {
 23493  			source:       &core.FlexVolumeSource{Driver: ""},
 23494  			expectedErrs: map[string]string{"driver": "Required value"},
 23495  		},
 23496  		"reserved option keys": {
 23497  			source: &core.FlexVolumeSource{
 23498  				Driver: "foo",
 23499  				Options: map[string]string{
 23500  					// valid options
 23501  					"myns.io":               "A",
 23502  					"myns.io/bar":           "A",
 23503  					"myns.io/kubernetes.io": "A",
 23504  
 23505  					// invalid options
 23506  					"KUBERNETES.IO":     "A",
 23507  					"kubernetes.io":     "A",
 23508  					"kubernetes.io/":    "A",
 23509  					"kubernetes.io/foo": "A",
 23510  
 23511  					"alpha.kubernetes.io":     "A",
 23512  					"alpha.kubernetes.io/":    "A",
 23513  					"alpha.kubernetes.io/foo": "A",
 23514  
 23515  					"k8s.io":     "A",
 23516  					"k8s.io/":    "A",
 23517  					"k8s.io/foo": "A",
 23518  
 23519  					"alpha.k8s.io":     "A",
 23520  					"alpha.k8s.io/":    "A",
 23521  					"alpha.k8s.io/foo": "A",
 23522  				},
 23523  			},
 23524  			expectedErrs: map[string]string{
 23525  				"options[KUBERNETES.IO]":           "reserved",
 23526  				"options[kubernetes.io]":           "reserved",
 23527  				"options[kubernetes.io/]":          "reserved",
 23528  				"options[kubernetes.io/foo]":       "reserved",
 23529  				"options[alpha.kubernetes.io]":     "reserved",
 23530  				"options[alpha.kubernetes.io/]":    "reserved",
 23531  				"options[alpha.kubernetes.io/foo]": "reserved",
 23532  				"options[k8s.io]":                  "reserved",
 23533  				"options[k8s.io/]":                 "reserved",
 23534  				"options[k8s.io/foo]":              "reserved",
 23535  				"options[alpha.k8s.io]":            "reserved",
 23536  				"options[alpha.k8s.io/]":           "reserved",
 23537  				"options[alpha.k8s.io/foo]":        "reserved",
 23538  			},
 23539  		},
 23540  	}
 23541  
 23542  	for k, tc := range testcases {
 23543  		errs := validateFlexVolumeSource(tc.source, nil)
 23544  		for _, err := range errs {
 23545  			expectedErr, ok := tc.expectedErrs[err.Field]
 23546  			if !ok {
 23547  				t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
 23548  				continue
 23549  			}
 23550  			if !strings.Contains(err.Error(), expectedErr) {
 23551  				t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
 23552  				continue
 23553  			}
 23554  		}
 23555  		if len(errs) != len(tc.expectedErrs) {
 23556  			t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
 23557  			continue
 23558  		}
 23559  	}
 23560  }
 23561  
 23562  func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
 23563  	successCases := map[string]*core.SessionAffinityConfig{
 23564  		"non-empty config, valid timeout: 1": {
 23565  			ClientIP: &core.ClientIPConfig{
 23566  				TimeoutSeconds: utilpointer.Int32(1),
 23567  			},
 23568  		},
 23569  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": {
 23570  			ClientIP: &core.ClientIPConfig{
 23571  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds - 1),
 23572  			},
 23573  		},
 23574  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": {
 23575  			ClientIP: &core.ClientIPConfig{
 23576  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds),
 23577  			},
 23578  		},
 23579  	}
 23580  
 23581  	for name, test := range successCases {
 23582  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
 23583  			t.Errorf("case: %s, expected success: %v", name, errs)
 23584  		}
 23585  	}
 23586  
 23587  	errorCases := map[string]*core.SessionAffinityConfig{
 23588  		"empty session affinity config": nil,
 23589  		"empty client IP config": {
 23590  			ClientIP: nil,
 23591  		},
 23592  		"empty timeoutSeconds": {
 23593  			ClientIP: &core.ClientIPConfig{
 23594  				TimeoutSeconds: nil,
 23595  			},
 23596  		},
 23597  		"non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": {
 23598  			ClientIP: &core.ClientIPConfig{
 23599  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds + 1),
 23600  			},
 23601  		},
 23602  		"non-empty config, invalid timeout: -1": {
 23603  			ClientIP: &core.ClientIPConfig{
 23604  				TimeoutSeconds: utilpointer.Int32(-1),
 23605  			},
 23606  		},
 23607  		"non-empty config, invalid timeout: 0": {
 23608  			ClientIP: &core.ClientIPConfig{
 23609  				TimeoutSeconds: utilpointer.Int32(0),
 23610  			},
 23611  		},
 23612  	}
 23613  
 23614  	for name, test := range errorCases {
 23615  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
 23616  			t.Errorf("case: %v, expected failures: %v", name, errs)
 23617  		}
 23618  	}
 23619  }
 23620  
 23621  func TestValidateWindowsSecurityContextOptions(t *testing.T) {
 23622  	toPtr := func(s string) *string {
 23623  		return &s
 23624  	}
 23625  
 23626  	testCases := []struct {
 23627  		testName string
 23628  
 23629  		windowsOptions         *core.WindowsSecurityContextOptions
 23630  		expectedErrorSubstring string
 23631  	}{{
 23632  		testName: "a nil pointer",
 23633  	}, {
 23634  		testName:       "an empty struct",
 23635  		windowsOptions: &core.WindowsSecurityContextOptions{},
 23636  	}, {
 23637  		testName: "a valid input",
 23638  		windowsOptions: &core.WindowsSecurityContextOptions{
 23639  			GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"),
 23640  			GMSACredentialSpec:     toPtr("dummy-gmsa-crep-spec-contents"),
 23641  		},
 23642  	}, {
 23643  		testName: "a GMSA cred spec name that is not a valid resource name",
 23644  		windowsOptions: &core.WindowsSecurityContextOptions{
 23645  			// invalid because of the underscore
 23646  			GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"),
 23647  		},
 23648  		expectedErrorSubstring: dnsSubdomainLabelErrMsg,
 23649  	}, {
 23650  		testName: "empty GMSA cred spec contents",
 23651  		windowsOptions: &core.WindowsSecurityContextOptions{
 23652  			GMSACredentialSpec: toPtr(""),
 23653  		},
 23654  		expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string",
 23655  	}, {
 23656  		testName: "GMSA cred spec contents that are too long",
 23657  		windowsOptions: &core.WindowsSecurityContextOptions{
 23658  			GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)),
 23659  		},
 23660  		expectedErrorSubstring: "gmsaCredentialSpec size must be under",
 23661  	}, {
 23662  		testName: "RunAsUserName is nil",
 23663  		windowsOptions: &core.WindowsSecurityContextOptions{
 23664  			RunAsUserName: nil,
 23665  		},
 23666  	}, {
 23667  		testName: "a valid RunAsUserName",
 23668  		windowsOptions: &core.WindowsSecurityContextOptions{
 23669  			RunAsUserName: toPtr("Container. User"),
 23670  		},
 23671  	}, {
 23672  		testName: "a valid RunAsUserName with NetBios Domain",
 23673  		windowsOptions: &core.WindowsSecurityContextOptions{
 23674  			RunAsUserName: toPtr("Network Service\\Container. User"),
 23675  		},
 23676  	}, {
 23677  		testName: "a valid RunAsUserName with DNS Domain",
 23678  		windowsOptions: &core.WindowsSecurityContextOptions{
 23679  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"),
 23680  		},
 23681  	}, {
 23682  		testName: "a valid RunAsUserName with DNS Domain with a single character segment",
 23683  		windowsOptions: &core.WindowsSecurityContextOptions{
 23684  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"),
 23685  		},
 23686  	}, {
 23687  		testName: "a valid RunAsUserName with a long single segment DNS Domain",
 23688  		windowsOptions: &core.WindowsSecurityContextOptions{
 23689  			RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"),
 23690  		},
 23691  	}, {
 23692  		testName: "an empty RunAsUserName",
 23693  		windowsOptions: &core.WindowsSecurityContextOptions{
 23694  			RunAsUserName: toPtr(""),
 23695  		},
 23696  		expectedErrorSubstring: "runAsUserName cannot be an empty string",
 23697  	}, {
 23698  		testName: "RunAsUserName containing a control character",
 23699  		windowsOptions: &core.WindowsSecurityContextOptions{
 23700  			RunAsUserName: toPtr("Container\tUser"),
 23701  		},
 23702  		expectedErrorSubstring: "runAsUserName cannot contain control characters",
 23703  	}, {
 23704  		testName: "RunAsUserName containing too many backslashes",
 23705  		windowsOptions: &core.WindowsSecurityContextOptions{
 23706  			RunAsUserName: toPtr("Container\\Foo\\Lish"),
 23707  		},
 23708  		expectedErrorSubstring: "runAsUserName cannot contain more than one backslash",
 23709  	}, {
 23710  		testName: "RunAsUserName containing backslash but empty Domain",
 23711  		windowsOptions: &core.WindowsSecurityContextOptions{
 23712  			RunAsUserName: toPtr("\\User"),
 23713  		},
 23714  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 23715  	}, {
 23716  		testName: "RunAsUserName containing backslash but empty User",
 23717  		windowsOptions: &core.WindowsSecurityContextOptions{
 23718  			RunAsUserName: toPtr("Container\\"),
 23719  		},
 23720  		expectedErrorSubstring: "runAsUserName's User cannot be empty",
 23721  	}, {
 23722  		testName: "RunAsUserName's NetBios Domain is too long",
 23723  		windowsOptions: &core.WindowsSecurityContextOptions{
 23724  			RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"),
 23725  		},
 23726  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23727  	}, {
 23728  		testName: "RunAsUserName's DNS Domain is too long",
 23729  		windowsOptions: &core.WindowsSecurityContextOptions{
 23730  			// even if this tests the max Domain length, the Domain should still be "valid".
 23731  			RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"),
 23732  		},
 23733  		expectedErrorSubstring: "runAsUserName's Domain length must be under",
 23734  	}, {
 23735  		testName: "RunAsUserName's User is too long",
 23736  		windowsOptions: &core.WindowsSecurityContextOptions{
 23737  			RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)),
 23738  		},
 23739  		expectedErrorSubstring: "runAsUserName's User length must not be longer than",
 23740  	}, {
 23741  		testName: "RunAsUserName's User cannot contain only spaces or periods",
 23742  		windowsOptions: &core.WindowsSecurityContextOptions{
 23743  			RunAsUserName: toPtr("... ..."),
 23744  		},
 23745  		expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces",
 23746  	}, {
 23747  		testName: "RunAsUserName's NetBios Domain cannot start with a dot",
 23748  		windowsOptions: &core.WindowsSecurityContextOptions{
 23749  			RunAsUserName: toPtr(".FooLish\\User"),
 23750  		},
 23751  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23752  	}, {
 23753  		testName: "RunAsUserName's NetBios Domain cannot contain invalid characters",
 23754  		windowsOptions: &core.WindowsSecurityContextOptions{
 23755  			RunAsUserName: toPtr("Foo? Lish?\\User"),
 23756  		},
 23757  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23758  	}, {
 23759  		testName: "RunAsUserName's DNS Domain cannot contain invalid characters",
 23760  		windowsOptions: &core.WindowsSecurityContextOptions{
 23761  			RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"),
 23762  		},
 23763  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 23764  	}, {
 23765  		testName: "RunAsUserName's User cannot contain invalid characters",
 23766  		windowsOptions: &core.WindowsSecurityContextOptions{
 23767  			RunAsUserName: toPtr("Container/User"),
 23768  		},
 23769  		expectedErrorSubstring: "runAsUserName's User cannot contain the following characters",
 23770  	},
 23771  	}
 23772  
 23773  	for _, testCase := range testCases {
 23774  		t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) {
 23775  			errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field"))
 23776  
 23777  			switch len(errs) {
 23778  			case 0:
 23779  				if testCase.expectedErrorSubstring != "" {
 23780  					t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring)
 23781  				}
 23782  			case 1:
 23783  				if testCase.expectedErrorSubstring == "" {
 23784  					t.Errorf("didn't expect a failure, got: %q", errs[0].Error())
 23785  				} else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) {
 23786  					t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error())
 23787  				}
 23788  			default:
 23789  				t.Errorf("got %d failures", len(errs))
 23790  				for i, err := range errs {
 23791  					t.Errorf("error %d: %q", i, err.Error())
 23792  				}
 23793  			}
 23794  		})
 23795  	}
 23796  }
 23797  
 23798  func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec {
 23799  	scName := "csi-plugin"
 23800  	dataSourceInSpec := core.PersistentVolumeClaimSpec{
 23801  		AccessModes: []core.PersistentVolumeAccessMode{
 23802  			core.ReadOnlyMany,
 23803  		},
 23804  		Resources: core.VolumeResourceRequirements{
 23805  			Requests: core.ResourceList{
 23806  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 23807  			},
 23808  		},
 23809  		StorageClassName: &scName,
 23810  		DataSource: &core.TypedLocalObjectReference{
 23811  			APIGroup: &apiGroup,
 23812  			Kind:     kind,
 23813  			Name:     name,
 23814  		},
 23815  	}
 23816  
 23817  	return &dataSourceInSpec
 23818  }
 23819  
 23820  func TestAlphaVolumePVCDataSource(t *testing.T) {
 23821  	testCases := []struct {
 23822  		testName     string
 23823  		claimSpec    core.PersistentVolumeClaimSpec
 23824  		expectedFail bool
 23825  	}{{
 23826  		testName:  "test create from valid snapshot source",
 23827  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23828  	}, {
 23829  		testName:  "test create from valid pvc source",
 23830  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 23831  	}, {
 23832  		testName:     "test missing name in snapshot datasource should fail",
 23833  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23834  		expectedFail: true,
 23835  	}, {
 23836  		testName:     "test missing kind in snapshot datasource should fail",
 23837  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 23838  		expectedFail: true,
 23839  	}, {
 23840  		testName:  "test create from valid generic custom resource source",
 23841  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 23842  	}, {
 23843  		testName:     "test invalid datasource should fail",
 23844  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 23845  		expectedFail: true,
 23846  	},
 23847  	}
 23848  
 23849  	for _, tc := range testCases {
 23850  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23851  		if tc.expectedFail {
 23852  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23853  				t.Errorf("expected failure: %v", errs)
 23854  			}
 23855  
 23856  		} else {
 23857  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23858  				t.Errorf("expected success: %v", errs)
 23859  			}
 23860  		}
 23861  	}
 23862  }
 23863  
 23864  func testAnyDataSource(t *testing.T, ds, dsRef bool) {
 23865  	testCases := []struct {
 23866  		testName     string
 23867  		claimSpec    core.PersistentVolumeClaimSpec
 23868  		expectedFail bool
 23869  	}{{
 23870  		testName:  "test create from valid snapshot source",
 23871  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23872  	}, {
 23873  		testName:  "test create from valid pvc source",
 23874  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 23875  	}, {
 23876  		testName:     "test missing name in snapshot datasource should fail",
 23877  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23878  		expectedFail: true,
 23879  	}, {
 23880  		testName:     "test missing kind in snapshot datasource should fail",
 23881  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 23882  		expectedFail: true,
 23883  	}, {
 23884  		testName:  "test create from valid generic custom resource source",
 23885  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 23886  	}, {
 23887  		testName:     "test invalid datasource should fail",
 23888  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 23889  		expectedFail: true,
 23890  	},
 23891  	}
 23892  
 23893  	for _, tc := range testCases {
 23894  		if dsRef {
 23895  			tc.claimSpec.DataSourceRef = &core.TypedObjectReference{
 23896  				APIGroup: tc.claimSpec.DataSource.APIGroup,
 23897  				Kind:     tc.claimSpec.DataSource.Kind,
 23898  				Name:     tc.claimSpec.DataSource.Name,
 23899  			}
 23900  		}
 23901  		if !ds {
 23902  			tc.claimSpec.DataSource = nil
 23903  		}
 23904  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23905  		if tc.expectedFail {
 23906  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23907  				t.Errorf("expected failure: %v", errs)
 23908  			}
 23909  		} else {
 23910  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23911  				t.Errorf("expected success: %v", errs)
 23912  			}
 23913  		}
 23914  	}
 23915  }
 23916  
 23917  func TestAnyDataSource(t *testing.T) {
 23918  	testAnyDataSource(t, true, false)
 23919  	testAnyDataSource(t, false, true)
 23920  	testAnyDataSource(t, true, false)
 23921  }
 23922  
 23923  func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec {
 23924  	scName := "csi-plugin"
 23925  	spec := core.PersistentVolumeClaimSpec{
 23926  		AccessModes: []core.PersistentVolumeAccessMode{
 23927  			core.ReadOnlyMany,
 23928  		},
 23929  		Resources: core.VolumeResourceRequirements{
 23930  			Requests: core.ResourceList{
 23931  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 23932  			},
 23933  		},
 23934  		StorageClassName: &scName,
 23935  		DataSourceRef: &core.TypedObjectReference{
 23936  			APIGroup:  apiGroup,
 23937  			Kind:      kind,
 23938  			Namespace: namespace,
 23939  			Name:      name,
 23940  		},
 23941  	}
 23942  
 23943  	if isDataSourceSet {
 23944  		spec.DataSource = &core.TypedLocalObjectReference{
 23945  			APIGroup: apiGroup,
 23946  			Kind:     kind,
 23947  			Name:     name,
 23948  		}
 23949  	}
 23950  	return &spec
 23951  }
 23952  
 23953  func TestCrossNamespaceSource(t *testing.T) {
 23954  	snapAPIGroup := "snapshot.storage.k8s.io"
 23955  	coreAPIGroup := ""
 23956  	unsupportedAPIGroup := "unsupported.example.com"
 23957  	snapKind := "VolumeSnapshot"
 23958  	pvcKind := "PersistentVolumeClaim"
 23959  	goodNS := "ns1"
 23960  	badNS := "a*b"
 23961  	emptyNS := ""
 23962  	goodName := "snapshot1"
 23963  
 23964  	testCases := []struct {
 23965  		testName     string
 23966  		expectedFail bool
 23967  		claimSpec    *core.PersistentVolumeClaimSpec
 23968  	}{{
 23969  		testName:     "Feature gate enabled and valid xns DataSourceRef specified",
 23970  		expectedFail: false,
 23971  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false),
 23972  	}, {
 23973  		testName:     "Feature gate enabled and xns DataSourceRef with PVC source specified",
 23974  		expectedFail: false,
 23975  		claimSpec:    pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false),
 23976  	}, {
 23977  		testName:     "Feature gate enabled and xns DataSourceRef with unsupported source specified",
 23978  		expectedFail: false,
 23979  		claimSpec:    pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false),
 23980  	}, {
 23981  		testName:     "Feature gate enabled and xns DataSourceRef with nil apiGroup",
 23982  		expectedFail: true,
 23983  		claimSpec:    pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false),
 23984  	}, {
 23985  		testName:     "Feature gate enabled and xns DataSourceRef with invalid namspace specified",
 23986  		expectedFail: true,
 23987  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false),
 23988  	}, {
 23989  		testName:     "Feature gate enabled and xns DataSourceRef with nil namspace specified",
 23990  		expectedFail: false,
 23991  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false),
 23992  	}, {
 23993  		testName:     "Feature gate enabled and xns DataSourceRef with empty namspace specified",
 23994  		expectedFail: false,
 23995  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false),
 23996  	}, {
 23997  		testName:     "Feature gate enabled and both xns DataSourceRef and DataSource specified",
 23998  		expectedFail: true,
 23999  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true),
 24000  	},
 24001  	}
 24002  
 24003  	for _, tc := range testCases {
 24004  		featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)
 24005  		featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)
 24006  		opts := PersistentVolumeClaimSpecValidationOptions{}
 24007  		if tc.expectedFail {
 24008  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 24009  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 24010  			}
 24011  		} else {
 24012  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 24013  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 24014  			}
 24015  		}
 24016  	}
 24017  }
 24018  
 24019  func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec {
 24020  	scName := "csi-plugin"
 24021  	spec := core.PersistentVolumeClaimSpec{
 24022  		AccessModes: []core.PersistentVolumeAccessMode{
 24023  			core.ReadOnlyMany,
 24024  		},
 24025  		Resources: core.VolumeResourceRequirements{
 24026  			Requests: core.ResourceList{
 24027  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 24028  			},
 24029  		},
 24030  		StorageClassName:          &scName,
 24031  		VolumeAttributesClassName: vacName,
 24032  	}
 24033  	return &spec
 24034  }
 24035  
 24036  func TestVolumeAttributesClass(t *testing.T) {
 24037  	testCases := []struct {
 24038  		testName                    string
 24039  		expectedFail                bool
 24040  		enableVolumeAttributesClass bool
 24041  		claimSpec                   *core.PersistentVolumeClaimSpec
 24042  	}{
 24043  		{
 24044  			testName:                    "Feature gate enabled and valid no volumeAttributesClassName specified",
 24045  			expectedFail:                false,
 24046  			enableVolumeAttributesClass: true,
 24047  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(nil),
 24048  		},
 24049  		{
 24050  			testName:                    "Feature gate enabled and an empty volumeAttributesClassName specified",
 24051  			expectedFail:                false,
 24052  			enableVolumeAttributesClass: true,
 24053  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("")),
 24054  		},
 24055  		{
 24056  			testName:                    "Feature gate enabled and valid volumeAttributesClassName specified",
 24057  			expectedFail:                false,
 24058  			enableVolumeAttributesClass: true,
 24059  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")),
 24060  		},
 24061  		{
 24062  			testName:                    "Feature gate enabled and invalid volumeAttributesClassName specified",
 24063  			expectedFail:                true,
 24064  			enableVolumeAttributesClass: true,
 24065  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")),
 24066  		},
 24067  	}
 24068  	for _, tc := range testCases {
 24069  		opts := PersistentVolumeClaimSpecValidationOptions{
 24070  			EnableVolumeAttributesClass: tc.enableVolumeAttributesClass,
 24071  		}
 24072  		if tc.expectedFail {
 24073  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 24074  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 24075  			}
 24076  		} else {
 24077  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 24078  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 24079  			}
 24080  		}
 24081  	}
 24082  }
 24083  
 24084  func TestValidateTopologySpreadConstraints(t *testing.T) {
 24085  	fieldPath := field.NewPath("field")
 24086  	subFldPath0 := fieldPath.Index(0)
 24087  	fieldPathMinDomains := subFldPath0.Child("minDomains")
 24088  	fieldPathMaxSkew := subFldPath0.Child("maxSkew")
 24089  	fieldPathTopologyKey := subFldPath0.Child("topologyKey")
 24090  	fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable")
 24091  	fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}")
 24092  	fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys")
 24093  	nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy")
 24094  	nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy")
 24095  	labelSelectorField := subFldPath0.Child("labelSelector")
 24096  	unknown := core.NodeInclusionPolicy("Unknown")
 24097  	ignore := core.NodeInclusionPolicyIgnore
 24098  	honor := core.NodeInclusionPolicyHonor
 24099  
 24100  	testCases := []struct {
 24101  		name            string
 24102  		constraints     []core.TopologySpreadConstraint
 24103  		wantFieldErrors field.ErrorList
 24104  		opts            PodValidationOptions
 24105  	}{{
 24106  		name: "all required fields ok",
 24107  		constraints: []core.TopologySpreadConstraint{{
 24108  			MaxSkew:           1,
 24109  			TopologyKey:       "k8s.io/zone",
 24110  			WhenUnsatisfiable: core.DoNotSchedule,
 24111  			MinDomains:        utilpointer.Int32(3),
 24112  		}},
 24113  		wantFieldErrors: field.ErrorList{},
 24114  	}, {
 24115  		name: "missing MaxSkew",
 24116  		constraints: []core.TopologySpreadConstraint{
 24117  			{TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24118  		},
 24119  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)},
 24120  	}, {
 24121  		name: "negative MaxSkew",
 24122  		constraints: []core.TopologySpreadConstraint{
 24123  			{MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24124  		},
 24125  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)},
 24126  	}, {
 24127  		name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil",
 24128  		constraints: []core.TopologySpreadConstraint{{
 24129  			MaxSkew:           1,
 24130  			TopologyKey:       "k8s.io/zone",
 24131  			WhenUnsatisfiable: core.ScheduleAnyway,
 24132  			MinDomains:        nil,
 24133  		}},
 24134  		wantFieldErrors: field.ErrorList{},
 24135  	}, {
 24136  		name: "negative minDomains is invalid",
 24137  		constraints: []core.TopologySpreadConstraint{{
 24138  			MaxSkew:           1,
 24139  			TopologyKey:       "k8s.io/zone",
 24140  			WhenUnsatisfiable: core.DoNotSchedule,
 24141  			MinDomains:        utilpointer.Int32(-1),
 24142  		}},
 24143  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)},
 24144  	}, {
 24145  		name: "cannot use non-nil MinDomains with ScheduleAnyway",
 24146  		constraints: []core.TopologySpreadConstraint{{
 24147  			MaxSkew:           1,
 24148  			TopologyKey:       "k8s.io/zone",
 24149  			WhenUnsatisfiable: core.ScheduleAnyway,
 24150  			MinDomains:        utilpointer.Int32(10),
 24151  		}},
 24152  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(10), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway)))},
 24153  	}, {
 24154  		name: "use negative MinDomains with ScheduleAnyway(invalid)",
 24155  		constraints: []core.TopologySpreadConstraint{{
 24156  			MaxSkew:           1,
 24157  			TopologyKey:       "k8s.io/zone",
 24158  			WhenUnsatisfiable: core.ScheduleAnyway,
 24159  			MinDomains:        utilpointer.Int32(-1),
 24160  		}},
 24161  		wantFieldErrors: []*field.Error{
 24162  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg),
 24163  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))),
 24164  		},
 24165  	}, {
 24166  		name: "missing TopologyKey",
 24167  		constraints: []core.TopologySpreadConstraint{
 24168  			{MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule},
 24169  		},
 24170  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 24171  	}, {
 24172  		name: "missing scheduling mode",
 24173  		constraints: []core.TopologySpreadConstraint{
 24174  			{MaxSkew: 1, TopologyKey: "k8s.io/zone"},
 24175  		},
 24176  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), sets.List(supportedScheduleActions))},
 24177  	}, {
 24178  		name: "unsupported scheduling mode",
 24179  		constraints: []core.TopologySpreadConstraint{
 24180  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")},
 24181  		},
 24182  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), sets.List(supportedScheduleActions))},
 24183  	}, {
 24184  		name: "multiple constraints ok with all required fields",
 24185  		constraints: []core.TopologySpreadConstraint{
 24186  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24187  			{MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway},
 24188  		},
 24189  		wantFieldErrors: field.ErrorList{},
 24190  	}, {
 24191  		name: "multiple constraints missing TopologyKey on partial ones",
 24192  		constraints: []core.TopologySpreadConstraint{
 24193  			{MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway},
 24194  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24195  		},
 24196  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 24197  	}, {
 24198  		name: "duplicate constraints",
 24199  		constraints: []core.TopologySpreadConstraint{
 24200  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24201  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24202  		},
 24203  		wantFieldErrors: []*field.Error{
 24204  			field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)),
 24205  		},
 24206  	}, {
 24207  		name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy",
 24208  		constraints: []core.TopologySpreadConstraint{{
 24209  			MaxSkew:            1,
 24210  			TopologyKey:        "k8s.io/zone",
 24211  			WhenUnsatisfiable:  core.DoNotSchedule,
 24212  			NodeAffinityPolicy: &honor,
 24213  			NodeTaintsPolicy:   &ignore,
 24214  		}},
 24215  		wantFieldErrors: []*field.Error{},
 24216  	}, {
 24217  		name: "unsupported policy name set on NodeAffinityPolicy",
 24218  		constraints: []core.TopologySpreadConstraint{{
 24219  			MaxSkew:            1,
 24220  			TopologyKey:        "k8s.io/zone",
 24221  			WhenUnsatisfiable:  core.DoNotSchedule,
 24222  			NodeAffinityPolicy: &unknown,
 24223  			NodeTaintsPolicy:   &ignore,
 24224  		}},
 24225  		wantFieldErrors: []*field.Error{
 24226  			field.NotSupported(nodeAffinityField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 24227  		},
 24228  	}, {
 24229  		name: "unsupported policy name set on NodeTaintsPolicy",
 24230  		constraints: []core.TopologySpreadConstraint{{
 24231  			MaxSkew:            1,
 24232  			TopologyKey:        "k8s.io/zone",
 24233  			WhenUnsatisfiable:  core.DoNotSchedule,
 24234  			NodeAffinityPolicy: &honor,
 24235  			NodeTaintsPolicy:   &unknown,
 24236  		}},
 24237  		wantFieldErrors: []*field.Error{
 24238  			field.NotSupported(nodeTaintsField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 24239  		},
 24240  	}, {
 24241  		name: "key in MatchLabelKeys isn't correctly defined",
 24242  		constraints: []core.TopologySpreadConstraint{{
 24243  			MaxSkew:           1,
 24244  			TopologyKey:       "k8s.io/zone",
 24245  			LabelSelector:     &metav1.LabelSelector{},
 24246  			WhenUnsatisfiable: core.DoNotSchedule,
 24247  			MatchLabelKeys:    []string{"/simple"},
 24248  		}},
 24249  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")},
 24250  	}, {
 24251  		name: "key exists in both matchLabelKeys and labelSelector",
 24252  		constraints: []core.TopologySpreadConstraint{{
 24253  			MaxSkew:           1,
 24254  			TopologyKey:       "k8s.io/zone",
 24255  			WhenUnsatisfiable: core.DoNotSchedule,
 24256  			MatchLabelKeys:    []string{"foo"},
 24257  			LabelSelector: &metav1.LabelSelector{
 24258  				MatchExpressions: []metav1.LabelSelectorRequirement{
 24259  					{
 24260  						Key:      "foo",
 24261  						Operator: metav1.LabelSelectorOpNotIn,
 24262  						Values:   []string{"value1", "value2"},
 24263  					},
 24264  				},
 24265  			},
 24266  		}},
 24267  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},
 24268  	}, {
 24269  		name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set",
 24270  		constraints: []core.TopologySpreadConstraint{{
 24271  			MaxSkew:           1,
 24272  			TopologyKey:       "k8s.io/zone",
 24273  			WhenUnsatisfiable: core.DoNotSchedule,
 24274  			MatchLabelKeys:    []string{"foo"},
 24275  		}},
 24276  		wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")},
 24277  	}, {
 24278  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 24279  		constraints: []core.TopologySpreadConstraint{{
 24280  			MaxSkew:           1,
 24281  			TopologyKey:       "k8s.io/zone",
 24282  			WhenUnsatisfiable: core.DoNotSchedule,
 24283  			MinDomains:        nil,
 24284  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 24285  		}},
 24286  		wantFieldErrors: []*field.Error{field.Invalid(labelSelectorField.Child("matchLabels"), "NoUppercaseOrSpecialCharsLike=Equals", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")},
 24287  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 24288  	}, {
 24289  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true",
 24290  		constraints: []core.TopologySpreadConstraint{{
 24291  			MaxSkew:           1,
 24292  			TopologyKey:       "k8s.io/zone",
 24293  			WhenUnsatisfiable: core.DoNotSchedule,
 24294  			MinDomains:        nil,
 24295  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 24296  		}},
 24297  		wantFieldErrors: []*field.Error{},
 24298  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true},
 24299  	}, {
 24300  		name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 24301  		constraints: []core.TopologySpreadConstraint{{
 24302  			MaxSkew:           1,
 24303  			TopologyKey:       "k8s.io/zone",
 24304  			WhenUnsatisfiable: core.DoNotSchedule,
 24305  			MinDomains:        nil,
 24306  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}},
 24307  		}},
 24308  		wantFieldErrors: []*field.Error{},
 24309  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 24310  	},
 24311  	}
 24312  
 24313  	for _, tc := range testCases {
 24314  		t.Run(tc.name, func(t *testing.T) {
 24315  			errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
 24316  			if diff := cmp.Diff(tc.wantFieldErrors, errs); diff != "" {
 24317  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 24318  			}
 24319  		})
 24320  	}
 24321  }
 24322  
 24323  func TestValidateOverhead(t *testing.T) {
 24324  	successCase := []struct {
 24325  		Name     string
 24326  		overhead core.ResourceList
 24327  	}{{
 24328  		Name: "Valid Overhead for CPU + Memory",
 24329  		overhead: core.ResourceList{
 24330  			core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 24331  			core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 24332  		},
 24333  	},
 24334  	}
 24335  	for _, tc := range successCase {
 24336  		if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 {
 24337  			t.Errorf("%q unexpected error: %v", tc.Name, errs)
 24338  		}
 24339  	}
 24340  
 24341  	errorCase := []struct {
 24342  		Name     string
 24343  		overhead core.ResourceList
 24344  	}{{
 24345  		Name: "Invalid Overhead Resources",
 24346  		overhead: core.ResourceList{
 24347  			core.ResourceName("my.org"): resource.MustParse("10m"),
 24348  		},
 24349  	},
 24350  	}
 24351  	for _, tc := range errorCase {
 24352  		if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 {
 24353  			t.Errorf("%q expected error", tc.Name)
 24354  		}
 24355  	}
 24356  }
 24357  
 24358  // helper creates a pod with name, namespace and IPs
 24359  func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod {
 24360  	return core.Pod{
 24361  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 24362  		Spec: core.PodSpec{
 24363  			Containers: []core.Container{{
 24364  				Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 24365  			}},
 24366  			RestartPolicy: core.RestartPolicyAlways,
 24367  			DNSPolicy:     core.DNSClusterFirst,
 24368  		},
 24369  		Status: core.PodStatus{
 24370  			PodIPs: podIPs,
 24371  		},
 24372  	}
 24373  }
 24374  func TestPodIPsValidation(t *testing.T) {
 24375  	testCases := []struct {
 24376  		pod         core.Pod
 24377  		expectError bool
 24378  	}{{
 24379  		expectError: false,
 24380  		pod:         makePod("nil-ips", "ns", nil),
 24381  	}, {
 24382  		expectError: false,
 24383  		pod:         makePod("empty-podips-list", "ns", []core.PodIP{}),
 24384  	}, {
 24385  		expectError: false,
 24386  		pod:         makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}),
 24387  	}, {
 24388  		expectError: false,
 24389  		pod:         makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}),
 24390  	}, {
 24391  		expectError: false,
 24392  		pod:         makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 24393  	}, {
 24394  		expectError: false,
 24395  		pod:         makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 24396  	},
 24397  		/* failure cases start here */
 24398  		{
 24399  			expectError: true,
 24400  			pod:         makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}),
 24401  		}, {
 24402  			expectError: true,
 24403  			pod:         makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}),
 24404  		}, {
 24405  			expectError: true,
 24406  			pod:         makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 24407  		}, {
 24408  			expectError: true,
 24409  			pod:         makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 24410  		}, {
 24411  			expectError: true,
 24412  			pod:         makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 24413  		},
 24414  
 24415  		{
 24416  			expectError: true,
 24417  			pod:         makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 24418  		}, {
 24419  			expectError: true,
 24420  			pod:         makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 24421  		},
 24422  	}
 24423  
 24424  	for _, testCase := range testCases {
 24425  		t.Run(testCase.pod.Name, func(t *testing.T) {
 24426  			for _, oldTestCase := range testCases {
 24427  				newPod := testCase.pod.DeepCopy()
 24428  				newPod.ResourceVersion = "1"
 24429  
 24430  				oldPod := oldTestCase.pod.DeepCopy()
 24431  				oldPod.ResourceVersion = "1"
 24432  				oldPod.Name = newPod.Name
 24433  
 24434  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 24435  
 24436  				if len(errs) == 0 && testCase.expectError {
 24437  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 24438  				}
 24439  				if len(errs) != 0 && !testCase.expectError {
 24440  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 24441  				}
 24442  			}
 24443  		})
 24444  	}
 24445  }
 24446  
 24447  func makePodWithHostIPs(podName string, podNamespace string, hostIPs []core.HostIP) core.Pod {
 24448  	hostIP := ""
 24449  	if len(hostIPs) > 0 {
 24450  		hostIP = hostIPs[0].IP
 24451  	}
 24452  	return core.Pod{
 24453  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 24454  		Spec: core.PodSpec{
 24455  			Containers: []core.Container{
 24456  				{
 24457  					Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 24458  				},
 24459  			},
 24460  			RestartPolicy: core.RestartPolicyAlways,
 24461  			DNSPolicy:     core.DNSClusterFirst,
 24462  		},
 24463  		Status: core.PodStatus{
 24464  			HostIP:  hostIP,
 24465  			HostIPs: hostIPs,
 24466  		},
 24467  	}
 24468  }
 24469  
 24470  func TestHostIPsValidation(t *testing.T) {
 24471  	testCases := []struct {
 24472  		pod         core.Pod
 24473  		expectError bool
 24474  	}{
 24475  		{
 24476  			expectError: false,
 24477  			pod:         makePodWithHostIPs("nil-ips", "ns", nil),
 24478  		},
 24479  		{
 24480  			expectError: false,
 24481  			pod:         makePodWithHostIPs("empty-HostIPs-list", "ns", []core.HostIP{}),
 24482  		},
 24483  		{
 24484  			expectError: false,
 24485  			pod:         makePodWithHostIPs("single-ip-family-6", "ns", []core.HostIP{{IP: "::1"}}),
 24486  		},
 24487  		{
 24488  			expectError: false,
 24489  			pod:         makePodWithHostIPs("single-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}}),
 24490  		},
 24491  		{
 24492  			expectError: false,
 24493  			pod:         makePodWithHostIPs("dual-stack-4-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 24494  		},
 24495  		{
 24496  			expectError: false,
 24497  			pod:         makePodWithHostIPs("dual-stack-6-4", "ns", []core.HostIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 24498  		},
 24499  		/* failure cases start here */
 24500  		{
 24501  			expectError: true,
 24502  			pod:         makePodWithHostIPs("invalid-pod-ip", "ns", []core.HostIP{{IP: "this-is-not-an-ip"}}),
 24503  		},
 24504  		{
 24505  			expectError: true,
 24506  			pod:         makePodWithHostIPs("dualstack-same-ip-family-6", "ns", []core.HostIP{{IP: "::1"}, {IP: "::2"}}),
 24507  		},
 24508  		{
 24509  			expectError: true,
 24510  			pod:         makePodWithHostIPs("dualstack-same-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 24511  		},
 24512  		{
 24513  			expectError: true,
 24514  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 24515  		},
 24516  		{
 24517  			expectError: true,
 24518  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 24519  		},
 24520  
 24521  		{
 24522  			expectError: true,
 24523  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 24524  		},
 24525  		{
 24526  			expectError: true,
 24527  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 24528  		},
 24529  	}
 24530  
 24531  	for _, testCase := range testCases {
 24532  		t.Run(testCase.pod.Name, func(t *testing.T) {
 24533  			for _, oldTestCase := range testCases {
 24534  				newPod := testCase.pod.DeepCopy()
 24535  				newPod.ResourceVersion = "1"
 24536  
 24537  				oldPod := oldTestCase.pod.DeepCopy()
 24538  				oldPod.ResourceVersion = "1"
 24539  				oldPod.Name = newPod.Name
 24540  
 24541  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 24542  
 24543  				if len(errs) == 0 && testCase.expectError {
 24544  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 24545  				}
 24546  				if len(errs) != 0 && !testCase.expectError {
 24547  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 24548  				}
 24549  			}
 24550  		})
 24551  	}
 24552  }
 24553  
 24554  // makes a node with pod cidr and a name
 24555  func makeNode(nodeName string, podCIDRs []string) core.Node {
 24556  	return core.Node{
 24557  		ObjectMeta: metav1.ObjectMeta{
 24558  			Name: nodeName,
 24559  		},
 24560  		Status: core.NodeStatus{
 24561  			Addresses: []core.NodeAddress{
 24562  				{Type: core.NodeExternalIP, Address: "something"},
 24563  			},
 24564  			Capacity: core.ResourceList{
 24565  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 24566  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 24567  			},
 24568  		},
 24569  		Spec: core.NodeSpec{
 24570  			PodCIDRs: podCIDRs,
 24571  		},
 24572  	}
 24573  }
 24574  func TestValidateNodeCIDRs(t *testing.T) {
 24575  	testCases := []struct {
 24576  		expectError bool
 24577  		node        core.Node
 24578  	}{{
 24579  		expectError: false,
 24580  		node:        makeNode("nil-pod-cidr", nil),
 24581  	}, {
 24582  		expectError: false,
 24583  		node:        makeNode("empty-pod-cidr", []string{}),
 24584  	}, {
 24585  		expectError: false,
 24586  		node:        makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}),
 24587  	}, {
 24588  		expectError: false,
 24589  		node:        makeNode("single-pod-cidr-6", []string{"2000::/10"}),
 24590  	},
 24591  
 24592  		{
 24593  			expectError: false,
 24594  			node:        makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}),
 24595  		}, {
 24596  			expectError: false,
 24597  			node:        makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}),
 24598  		},
 24599  		// error cases starts here
 24600  		{
 24601  			expectError: true,
 24602  			node:        makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}),
 24603  		}, {
 24604  			expectError: true,
 24605  			node:        makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}),
 24606  		}, {
 24607  			expectError: true,
 24608  			node:        makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}),
 24609  		}, {
 24610  			expectError: true,
 24611  			node:        makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}),
 24612  		}, {
 24613  			expectError: true,
 24614  			node:        makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}),
 24615  		}, {
 24616  			expectError: true,
 24617  			node:        makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}),
 24618  		}, {
 24619  			expectError: true,
 24620  			node:        makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}),
 24621  		},
 24622  	}
 24623  	for _, testCase := range testCases {
 24624  		errs := ValidateNode(&testCase.node)
 24625  		if len(errs) == 0 && testCase.expectError {
 24626  			t.Errorf("expected failure for %s, but there were none", testCase.node.Name)
 24627  			return
 24628  		}
 24629  		if len(errs) != 0 && !testCase.expectError {
 24630  			t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs)
 24631  			return
 24632  		}
 24633  	}
 24634  }
 24635  
 24636  func TestValidateSeccompAnnotationAndField(t *testing.T) {
 24637  	const containerName = "container"
 24638  	testProfile := "test"
 24639  
 24640  	for _, test := range []struct {
 24641  		description string
 24642  		pod         *core.Pod
 24643  		validation  func(*testing.T, string, field.ErrorList, *v1.Pod)
 24644  	}{{
 24645  		description: "Field type unconfined and annotation does not match",
 24646  		pod: &core.Pod{
 24647  			ObjectMeta: metav1.ObjectMeta{
 24648  				Annotations: map[string]string{
 24649  					v1.SeccompPodAnnotationKey: "not-matching",
 24650  				},
 24651  			},
 24652  			Spec: core.PodSpec{
 24653  				SecurityContext: &core.PodSecurityContext{
 24654  					SeccompProfile: &core.SeccompProfile{
 24655  						Type: core.SeccompProfileTypeUnconfined,
 24656  					},
 24657  				},
 24658  			},
 24659  		},
 24660  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24661  			require.NotNil(t, allErrs, desc)
 24662  		},
 24663  	}, {
 24664  		description: "Field type default and annotation does not match",
 24665  		pod: &core.Pod{
 24666  			ObjectMeta: metav1.ObjectMeta{
 24667  				Annotations: map[string]string{
 24668  					v1.SeccompPodAnnotationKey: "not-matching",
 24669  				},
 24670  			},
 24671  			Spec: core.PodSpec{
 24672  				SecurityContext: &core.PodSecurityContext{
 24673  					SeccompProfile: &core.SeccompProfile{
 24674  						Type: core.SeccompProfileTypeRuntimeDefault,
 24675  					},
 24676  				},
 24677  			},
 24678  		},
 24679  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24680  			require.NotNil(t, allErrs, desc)
 24681  		},
 24682  	}, {
 24683  		description: "Field type localhost and annotation does not match",
 24684  		pod: &core.Pod{
 24685  			ObjectMeta: metav1.ObjectMeta{
 24686  				Annotations: map[string]string{
 24687  					v1.SeccompPodAnnotationKey: "not-matching",
 24688  				},
 24689  			},
 24690  			Spec: core.PodSpec{
 24691  				SecurityContext: &core.PodSecurityContext{
 24692  					SeccompProfile: &core.SeccompProfile{
 24693  						Type:             core.SeccompProfileTypeLocalhost,
 24694  						LocalhostProfile: &testProfile,
 24695  					},
 24696  				},
 24697  			},
 24698  		},
 24699  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24700  			require.NotNil(t, allErrs, desc)
 24701  		},
 24702  	}, {
 24703  		description: "Field type localhost and localhost/ prefixed annotation does not match",
 24704  		pod: &core.Pod{
 24705  			ObjectMeta: metav1.ObjectMeta{
 24706  				Annotations: map[string]string{
 24707  					v1.SeccompPodAnnotationKey: "localhost/not-matching",
 24708  				},
 24709  			},
 24710  			Spec: core.PodSpec{
 24711  				SecurityContext: &core.PodSecurityContext{
 24712  					SeccompProfile: &core.SeccompProfile{
 24713  						Type:             core.SeccompProfileTypeLocalhost,
 24714  						LocalhostProfile: &testProfile,
 24715  					},
 24716  				},
 24717  			},
 24718  		},
 24719  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24720  			require.NotNil(t, allErrs, desc)
 24721  		},
 24722  	}, {
 24723  		description: "Field type unconfined and annotation does not match (container)",
 24724  		pod: &core.Pod{
 24725  			ObjectMeta: metav1.ObjectMeta{
 24726  				Annotations: map[string]string{
 24727  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24728  				},
 24729  			},
 24730  			Spec: core.PodSpec{
 24731  				Containers: []core.Container{{
 24732  					Name: containerName,
 24733  					SecurityContext: &core.SecurityContext{
 24734  						SeccompProfile: &core.SeccompProfile{
 24735  							Type: core.SeccompProfileTypeUnconfined,
 24736  						},
 24737  					},
 24738  				}},
 24739  			},
 24740  		},
 24741  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24742  			require.NotNil(t, allErrs, desc)
 24743  		},
 24744  	}, {
 24745  		description: "Field type default and annotation does not match (container)",
 24746  		pod: &core.Pod{
 24747  			ObjectMeta: metav1.ObjectMeta{
 24748  				Annotations: map[string]string{
 24749  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24750  				},
 24751  			},
 24752  			Spec: core.PodSpec{
 24753  				Containers: []core.Container{{
 24754  					Name: containerName,
 24755  					SecurityContext: &core.SecurityContext{
 24756  						SeccompProfile: &core.SeccompProfile{
 24757  							Type: core.SeccompProfileTypeRuntimeDefault,
 24758  						},
 24759  					},
 24760  				}},
 24761  			},
 24762  		},
 24763  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24764  			require.NotNil(t, allErrs, desc)
 24765  		},
 24766  	}, {
 24767  		description: "Field type localhost and annotation does not match (container)",
 24768  		pod: &core.Pod{
 24769  			ObjectMeta: metav1.ObjectMeta{
 24770  				Annotations: map[string]string{
 24771  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24772  				},
 24773  			},
 24774  			Spec: core.PodSpec{
 24775  				Containers: []core.Container{{
 24776  					Name: containerName,
 24777  					SecurityContext: &core.SecurityContext{
 24778  						SeccompProfile: &core.SeccompProfile{
 24779  							Type:             core.SeccompProfileTypeLocalhost,
 24780  							LocalhostProfile: &testProfile,
 24781  						},
 24782  					},
 24783  				}},
 24784  			},
 24785  		},
 24786  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24787  			require.NotNil(t, allErrs, desc)
 24788  		},
 24789  	}, {
 24790  		description: "Field type localhost and localhost/ prefixed annotation does not match (container)",
 24791  		pod: &core.Pod{
 24792  			ObjectMeta: metav1.ObjectMeta{
 24793  				Annotations: map[string]string{
 24794  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 24795  				},
 24796  			},
 24797  			Spec: core.PodSpec{
 24798  				Containers: []core.Container{{
 24799  					Name: containerName,
 24800  					SecurityContext: &core.SecurityContext{
 24801  						SeccompProfile: &core.SeccompProfile{
 24802  							Type:             core.SeccompProfileTypeLocalhost,
 24803  							LocalhostProfile: &testProfile,
 24804  						},
 24805  					},
 24806  				}},
 24807  			},
 24808  		},
 24809  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24810  			require.NotNil(t, allErrs, desc)
 24811  		},
 24812  	}, {
 24813  		description: "Nil errors must not be appended (pod)",
 24814  		pod: &core.Pod{
 24815  			ObjectMeta: metav1.ObjectMeta{
 24816  				Annotations: map[string]string{
 24817  					v1.SeccompPodAnnotationKey: "localhost/anyprofile",
 24818  				},
 24819  			},
 24820  			Spec: core.PodSpec{
 24821  				SecurityContext: &core.PodSecurityContext{
 24822  					SeccompProfile: &core.SeccompProfile{
 24823  						Type: "Abc",
 24824  					},
 24825  				},
 24826  				Containers: []core.Container{{
 24827  					Name: containerName,
 24828  				}},
 24829  			},
 24830  		},
 24831  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24832  			require.Empty(t, allErrs, desc)
 24833  		},
 24834  	}, {
 24835  		description: "Nil errors must not be appended (container)",
 24836  		pod: &core.Pod{
 24837  			ObjectMeta: metav1.ObjectMeta{
 24838  				Annotations: map[string]string{
 24839  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 24840  				},
 24841  			},
 24842  			Spec: core.PodSpec{
 24843  				Containers: []core.Container{{
 24844  					SecurityContext: &core.SecurityContext{
 24845  						SeccompProfile: &core.SeccompProfile{
 24846  							Type: "Abc",
 24847  						},
 24848  					},
 24849  					Name: containerName,
 24850  				}},
 24851  			},
 24852  		},
 24853  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24854  			require.Empty(t, allErrs, desc)
 24855  		},
 24856  	},
 24857  	} {
 24858  		output := &v1.Pod{
 24859  			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
 24860  		}
 24861  		for i, ctr := range test.pod.Spec.Containers {
 24862  			output.Spec.Containers = append(output.Spec.Containers, v1.Container{})
 24863  			if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
 24864  				output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{
 24865  					SeccompProfile: &v1.SeccompProfile{
 24866  						Type:             v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type),
 24867  						LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile,
 24868  					},
 24869  				}
 24870  			}
 24871  		}
 24872  		errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath(""))
 24873  		test.validation(t, test.description, errList, output)
 24874  	}
 24875  }
 24876  
 24877  func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) {
 24878  	rootFld := field.NewPath("")
 24879  	tests := []struct {
 24880  		description     string
 24881  		annotationValue string
 24882  		seccompField    *core.SeccompProfile
 24883  		fldPath         *field.Path
 24884  		expectedErr     *field.Error
 24885  	}{{
 24886  		description: "seccompField nil should return empty",
 24887  		expectedErr: nil,
 24888  	}, {
 24889  		description:     "unconfined annotation and SeccompProfileTypeUnconfined should return empty",
 24890  		annotationValue: "unconfined",
 24891  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 24892  		expectedErr:     nil,
 24893  	}, {
 24894  		description:     "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24895  		annotationValue: "runtime/default",
 24896  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24897  		expectedErr:     nil,
 24898  	}, {
 24899  		description:     "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24900  		annotationValue: "docker/default",
 24901  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24902  		expectedErr:     nil,
 24903  	}, {
 24904  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty",
 24905  		annotationValue: "localhost/test.json",
 24906  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")},
 24907  		expectedErr:     nil,
 24908  	}, {
 24909  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error",
 24910  		annotationValue: "localhost/test.json",
 24911  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost},
 24912  		fldPath:         rootFld,
 24913  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 24914  	}, {
 24915  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error",
 24916  		annotationValue: "localhost/test.json",
 24917  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")},
 24918  		fldPath:         rootFld,
 24919  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 24920  	}, {
 24921  		description:     "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error",
 24922  		annotationValue: "localhost/test.json",
 24923  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 24924  		fldPath:         rootFld,
 24925  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 24926  	}, {
 24927  		description:     "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error",
 24928  		annotationValue: "localhost/test.json",
 24929  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24930  		fldPath:         rootFld,
 24931  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 24932  	},
 24933  	}
 24934  
 24935  	for i, test := range tests {
 24936  		err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath)
 24937  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 24938  	}
 24939  }
 24940  
 24941  func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
 24942  	rootFld := field.NewPath("template")
 24943  	tests := []struct {
 24944  		description string
 24945  		spec        *core.PodTemplateSpec
 24946  		fldPath     *field.Path
 24947  		expectedErr field.ErrorList
 24948  	}{{
 24949  		description: "seccomp field and container annotation must match",
 24950  		fldPath:     rootFld,
 24951  		expectedErr: field.ErrorList{
 24952  			field.Forbidden(
 24953  				rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"),
 24954  				"seccomp type in annotation and field must match"),
 24955  		},
 24956  		spec: &core.PodTemplateSpec{
 24957  			ObjectMeta: metav1.ObjectMeta{
 24958  				Annotations: map[string]string{
 24959  					"container.seccomp.security.alpha.kubernetes.io/test2": "unconfined",
 24960  				},
 24961  			},
 24962  			Spec: core.PodSpec{
 24963  				Containers: []core.Container{{
 24964  					Name:                     "test1",
 24965  					Image:                    "alpine",
 24966  					ImagePullPolicy:          core.PullAlways,
 24967  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24968  				}, {
 24969  					SecurityContext: &core.SecurityContext{
 24970  						SeccompProfile: &core.SeccompProfile{
 24971  							Type: core.SeccompProfileTypeRuntimeDefault,
 24972  						},
 24973  					},
 24974  					Name:                     "test2",
 24975  					Image:                    "alpine",
 24976  					ImagePullPolicy:          core.PullAlways,
 24977  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24978  				}},
 24979  				RestartPolicy: core.RestartPolicyAlways,
 24980  				DNSPolicy:     core.DNSDefault,
 24981  			},
 24982  		},
 24983  	}, {
 24984  		description: "seccomp field and pod annotation must match",
 24985  		fldPath:     rootFld,
 24986  		expectedErr: field.ErrorList{
 24987  			field.Forbidden(
 24988  				rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"),
 24989  				"seccomp type in annotation and field must match"),
 24990  		},
 24991  		spec: &core.PodTemplateSpec{
 24992  			ObjectMeta: metav1.ObjectMeta{
 24993  				Annotations: map[string]string{
 24994  					"seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
 24995  				},
 24996  			},
 24997  			Spec: core.PodSpec{
 24998  				SecurityContext: &core.PodSecurityContext{
 24999  					SeccompProfile: &core.SeccompProfile{
 25000  						Type: core.SeccompProfileTypeUnconfined,
 25001  					},
 25002  				},
 25003  				Containers: []core.Container{{
 25004  					Name:                     "test",
 25005  					Image:                    "alpine",
 25006  					ImagePullPolicy:          core.PullAlways,
 25007  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25008  				}},
 25009  				RestartPolicy: core.RestartPolicyAlways,
 25010  				DNSPolicy:     core.DNSDefault,
 25011  			},
 25012  		},
 25013  	}, {
 25014  		description: "init seccomp field and container annotation must match",
 25015  		fldPath:     rootFld,
 25016  		expectedErr: field.ErrorList{
 25017  			field.Forbidden(
 25018  				rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"),
 25019  				"seccomp type in annotation and field must match"),
 25020  		},
 25021  		spec: &core.PodTemplateSpec{
 25022  			ObjectMeta: metav1.ObjectMeta{
 25023  				Annotations: map[string]string{
 25024  					"container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined",
 25025  				},
 25026  			},
 25027  			Spec: core.PodSpec{
 25028  				Containers: []core.Container{{
 25029  					Name:                     "test",
 25030  					Image:                    "alpine",
 25031  					ImagePullPolicy:          core.PullAlways,
 25032  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25033  				}},
 25034  				InitContainers: []core.Container{{
 25035  					Name: "init-test",
 25036  					SecurityContext: &core.SecurityContext{
 25037  						SeccompProfile: &core.SeccompProfile{
 25038  							Type: core.SeccompProfileTypeRuntimeDefault,
 25039  						},
 25040  					},
 25041  					Image:                    "alpine",
 25042  					ImagePullPolicy:          core.PullAlways,
 25043  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25044  				}},
 25045  				RestartPolicy: core.RestartPolicyAlways,
 25046  				DNSPolicy:     core.DNSDefault,
 25047  			},
 25048  		},
 25049  	},
 25050  	}
 25051  
 25052  	for i, test := range tests {
 25053  		err := ValidatePodTemplateSpec(test.spec, rootFld, PodValidationOptions{})
 25054  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 25055  	}
 25056  }
 25057  
 25058  func TestValidateResourceRequirements(t *testing.T) {
 25059  	path := field.NewPath("resources")
 25060  	tests := []struct {
 25061  		name         string
 25062  		requirements core.ResourceRequirements
 25063  		opts         PodValidationOptions
 25064  	}{{
 25065  		name: "limits and requests of hugepage resource are equal",
 25066  		requirements: core.ResourceRequirements{
 25067  			Limits: core.ResourceList{
 25068  				core.ResourceCPU: resource.MustParse("10"),
 25069  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25070  			},
 25071  			Requests: core.ResourceList{
 25072  				core.ResourceCPU: resource.MustParse("10"),
 25073  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25074  			},
 25075  		},
 25076  		opts: PodValidationOptions{},
 25077  	}, {
 25078  		name: "limits and requests of memory resource are equal",
 25079  		requirements: core.ResourceRequirements{
 25080  			Limits: core.ResourceList{
 25081  				core.ResourceMemory: resource.MustParse("2Mi"),
 25082  			},
 25083  			Requests: core.ResourceList{
 25084  				core.ResourceMemory: resource.MustParse("2Mi"),
 25085  			},
 25086  		},
 25087  		opts: PodValidationOptions{},
 25088  	}, {
 25089  		name: "limits and requests of cpu resource are equal",
 25090  		requirements: core.ResourceRequirements{
 25091  			Limits: core.ResourceList{
 25092  				core.ResourceCPU: resource.MustParse("10"),
 25093  			},
 25094  			Requests: core.ResourceList{
 25095  				core.ResourceCPU: resource.MustParse("10"),
 25096  			},
 25097  		},
 25098  		opts: PodValidationOptions{},
 25099  	},
 25100  	}
 25101  
 25102  	for _, tc := range tests {
 25103  		t.Run(tc.name, func(t *testing.T) {
 25104  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 {
 25105  				t.Errorf("unexpected errors: %v", errs)
 25106  			}
 25107  		})
 25108  	}
 25109  
 25110  	errTests := []struct {
 25111  		name         string
 25112  		requirements core.ResourceRequirements
 25113  		opts         PodValidationOptions
 25114  	}{{
 25115  		name: "hugepage resource without cpu or memory",
 25116  		requirements: core.ResourceRequirements{
 25117  			Limits: core.ResourceList{
 25118  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25119  			},
 25120  			Requests: core.ResourceList{
 25121  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25122  			},
 25123  		},
 25124  		opts: PodValidationOptions{},
 25125  	},
 25126  	}
 25127  
 25128  	for _, tc := range errTests {
 25129  		t.Run(tc.name, func(t *testing.T) {
 25130  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 {
 25131  				t.Error("expected errors")
 25132  			}
 25133  		})
 25134  	}
 25135  }
 25136  
 25137  func TestValidateNonSpecialIP(t *testing.T) {
 25138  	fp := field.NewPath("ip")
 25139  
 25140  	// Valid values.
 25141  	for _, tc := range []struct {
 25142  		desc string
 25143  		ip   string
 25144  	}{
 25145  		{"ipv4", "10.1.2.3"},
 25146  		{"ipv4 class E", "244.1.2.3"},
 25147  		{"ipv6", "2000::1"},
 25148  	} {
 25149  		t.Run(tc.desc, func(t *testing.T) {
 25150  			errs := ValidateNonSpecialIP(tc.ip, fp)
 25151  			if len(errs) != 0 {
 25152  				t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs)
 25153  			}
 25154  		})
 25155  	}
 25156  	// Invalid cases
 25157  	for _, tc := range []struct {
 25158  		desc string
 25159  		ip   string
 25160  	}{
 25161  		{"ipv4 unspecified", "0.0.0.0"},
 25162  		{"ipv6 unspecified", "::0"},
 25163  		{"ipv4 localhost", "127.0.0.0"},
 25164  		{"ipv4 localhost", "127.255.255.255"},
 25165  		{"ipv6 localhost", "::1"},
 25166  		{"ipv6 link local", "fe80::"},
 25167  		{"ipv6 local multicast", "ff02::"},
 25168  	} {
 25169  		t.Run(tc.desc, func(t *testing.T) {
 25170  			errs := ValidateNonSpecialIP(tc.ip, fp)
 25171  			if len(errs) == 0 {
 25172  				t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip)
 25173  			}
 25174  		})
 25175  	}
 25176  }
 25177  
 25178  func TestValidateHostUsers(t *testing.T) {
 25179  	falseVar := false
 25180  	trueVar := true
 25181  
 25182  	cases := []struct {
 25183  		name    string
 25184  		success bool
 25185  		spec    *core.PodSpec
 25186  	}{{
 25187  		name:    "empty",
 25188  		success: true,
 25189  		spec:    &core.PodSpec{},
 25190  	}, {
 25191  		name:    "hostUsers unset",
 25192  		success: true,
 25193  		spec: &core.PodSpec{
 25194  			SecurityContext: &core.PodSecurityContext{},
 25195  		},
 25196  	}, {
 25197  		name:    "hostUsers=false",
 25198  		success: true,
 25199  		spec: &core.PodSpec{
 25200  			SecurityContext: &core.PodSecurityContext{
 25201  				HostUsers: &falseVar,
 25202  			},
 25203  		},
 25204  	}, {
 25205  		name:    "hostUsers=true",
 25206  		success: true,
 25207  		spec: &core.PodSpec{
 25208  			SecurityContext: &core.PodSecurityContext{
 25209  				HostUsers: &trueVar,
 25210  			},
 25211  		},
 25212  	}, {
 25213  		name:    "hostUsers=false & volumes",
 25214  		success: true,
 25215  		spec: &core.PodSpec{
 25216  			SecurityContext: &core.PodSecurityContext{
 25217  				HostUsers: &falseVar,
 25218  			},
 25219  			Volumes: []core.Volume{{
 25220  				Name: "configmap",
 25221  				VolumeSource: core.VolumeSource{
 25222  					ConfigMap: &core.ConfigMapVolumeSource{
 25223  						LocalObjectReference: core.LocalObjectReference{Name: "configmap"},
 25224  					},
 25225  				},
 25226  			}, {
 25227  				Name: "secret",
 25228  				VolumeSource: core.VolumeSource{
 25229  					Secret: &core.SecretVolumeSource{
 25230  						SecretName: "secret",
 25231  					},
 25232  				},
 25233  			}, {
 25234  				Name: "downward-api",
 25235  				VolumeSource: core.VolumeSource{
 25236  					DownwardAPI: &core.DownwardAPIVolumeSource{},
 25237  				},
 25238  			}, {
 25239  				Name: "proj",
 25240  				VolumeSource: core.VolumeSource{
 25241  					Projected: &core.ProjectedVolumeSource{},
 25242  				},
 25243  			}, {
 25244  				Name: "empty-dir",
 25245  				VolumeSource: core.VolumeSource{
 25246  					EmptyDir: &core.EmptyDirVolumeSource{},
 25247  				},
 25248  			}},
 25249  		},
 25250  	}, {
 25251  		name:    "hostUsers=false - stateful volume",
 25252  		success: true,
 25253  		spec: &core.PodSpec{
 25254  			SecurityContext: &core.PodSecurityContext{
 25255  				HostUsers: &falseVar,
 25256  			},
 25257  			Volumes: []core.Volume{{
 25258  				Name: "host-path",
 25259  				VolumeSource: core.VolumeSource{
 25260  					HostPath: &core.HostPathVolumeSource{},
 25261  				},
 25262  			}},
 25263  		},
 25264  	}, {
 25265  		name:    "hostUsers=true - unsupported volume",
 25266  		success: true,
 25267  		spec: &core.PodSpec{
 25268  			SecurityContext: &core.PodSecurityContext{
 25269  				HostUsers: &trueVar,
 25270  			},
 25271  			Volumes: []core.Volume{{
 25272  				Name: "host-path",
 25273  				VolumeSource: core.VolumeSource{
 25274  					HostPath: &core.HostPathVolumeSource{},
 25275  				},
 25276  			}},
 25277  		},
 25278  	}, {
 25279  		name:    "hostUsers=false & HostNetwork",
 25280  		success: false,
 25281  		spec: &core.PodSpec{
 25282  			SecurityContext: &core.PodSecurityContext{
 25283  				HostUsers:   &falseVar,
 25284  				HostNetwork: true,
 25285  			},
 25286  		},
 25287  	}, {
 25288  		name:    "hostUsers=false & HostPID",
 25289  		success: false,
 25290  		spec: &core.PodSpec{
 25291  			SecurityContext: &core.PodSecurityContext{
 25292  				HostUsers: &falseVar,
 25293  				HostPID:   true,
 25294  			},
 25295  		},
 25296  	}, {
 25297  		name:    "hostUsers=false & HostIPC",
 25298  		success: false,
 25299  		spec: &core.PodSpec{
 25300  			SecurityContext: &core.PodSecurityContext{
 25301  				HostUsers: &falseVar,
 25302  				HostIPC:   true,
 25303  			},
 25304  		},
 25305  	},
 25306  	}
 25307  
 25308  	for _, tc := range cases {
 25309  		t.Run(tc.name, func(t *testing.T) {
 25310  			fPath := field.NewPath("spec")
 25311  
 25312  			allErrs := validateHostUsers(tc.spec, fPath)
 25313  			if !tc.success && len(allErrs) == 0 {
 25314  				t.Errorf("Unexpected success")
 25315  			}
 25316  			if tc.success && len(allErrs) != 0 {
 25317  				t.Errorf("Unexpected error(s): %v", allErrs)
 25318  			}
 25319  		})
 25320  	}
 25321  }
 25322  
 25323  func TestValidateWindowsHostProcessPod(t *testing.T) {
 25324  	const containerName = "container"
 25325  	falseVar := false
 25326  	trueVar := true
 25327  
 25328  	testCases := []struct {
 25329  		name            string
 25330  		expectError     bool
 25331  		allowPrivileged bool
 25332  		podSpec         *core.PodSpec
 25333  	}{{
 25334  		name:            "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate",
 25335  		expectError:     true,
 25336  		allowPrivileged: true,
 25337  		podSpec: &core.PodSpec{
 25338  			SecurityContext: &core.PodSecurityContext{
 25339  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25340  					HostProcess: &trueVar,
 25341  				},
 25342  			},
 25343  			Containers: []core.Container{{
 25344  				Name: containerName,
 25345  			}},
 25346  		},
 25347  	}, {
 25348  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate",
 25349  		expectError:     false,
 25350  		allowPrivileged: true,
 25351  		podSpec: &core.PodSpec{
 25352  			SecurityContext: &core.PodSecurityContext{
 25353  				HostNetwork: true,
 25354  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25355  					HostProcess: &trueVar,
 25356  				},
 25357  			},
 25358  			Containers: []core.Container{{
 25359  				Name: containerName,
 25360  			}},
 25361  		},
 25362  	}, {
 25363  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate",
 25364  		expectError:     false,
 25365  		allowPrivileged: true,
 25366  		podSpec: &core.PodSpec{
 25367  			SecurityContext: &core.PodSecurityContext{
 25368  				HostNetwork: true,
 25369  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25370  					HostProcess: &trueVar,
 25371  				},
 25372  			},
 25373  			Containers: []core.Container{{
 25374  				Name: containerName,
 25375  				SecurityContext: &core.SecurityContext{
 25376  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25377  						HostProcess: &trueVar,
 25378  					},
 25379  				},
 25380  			}},
 25381  			InitContainers: []core.Container{{
 25382  				Name: containerName,
 25383  				SecurityContext: &core.SecurityContext{
 25384  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25385  						HostProcess: &trueVar,
 25386  					},
 25387  				},
 25388  			}},
 25389  		},
 25390  	}, {
 25391  		name:            "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate",
 25392  		expectError:     false,
 25393  		allowPrivileged: true,
 25394  		podSpec: &core.PodSpec{
 25395  			SecurityContext: &core.PodSecurityContext{
 25396  				HostNetwork: true,
 25397  			},
 25398  			Containers: []core.Container{{
 25399  				Name: containerName,
 25400  				SecurityContext: &core.SecurityContext{
 25401  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25402  						HostProcess: &trueVar,
 25403  					},
 25404  				},
 25405  			}},
 25406  			InitContainers: []core.Container{{
 25407  				Name: containerName,
 25408  				SecurityContext: &core.SecurityContext{
 25409  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25410  						HostProcess: &trueVar,
 25411  					},
 25412  				},
 25413  			}},
 25414  		},
 25415  	}, {
 25416  		name:            "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 25417  		expectError:     true,
 25418  		allowPrivileged: true,
 25419  		podSpec: &core.PodSpec{
 25420  			SecurityContext: &core.PodSecurityContext{
 25421  				HostNetwork: true,
 25422  			},
 25423  			Containers: []core.Container{{
 25424  				Name: containerName,
 25425  				SecurityContext: &core.SecurityContext{
 25426  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25427  						HostProcess: &trueVar,
 25428  					},
 25429  				},
 25430  			}},
 25431  			InitContainers: []core.Container{{
 25432  				Name: containerName,
 25433  				SecurityContext: &core.SecurityContext{
 25434  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25435  						HostProcess: &falseVar,
 25436  					},
 25437  				},
 25438  			}},
 25439  		},
 25440  	}, {
 25441  		name:            "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate",
 25442  		expectError:     true,
 25443  		allowPrivileged: true,
 25444  		podSpec: &core.PodSpec{
 25445  			SecurityContext: &core.PodSecurityContext{
 25446  				HostNetwork: true,
 25447  			},
 25448  			Containers: []core.Container{{
 25449  				Name: containerName,
 25450  				SecurityContext: &core.SecurityContext{
 25451  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25452  						HostProcess: &trueVar,
 25453  					},
 25454  				},
 25455  			}},
 25456  			InitContainers: []core.Container{{
 25457  				Name: containerName,
 25458  			}},
 25459  		},
 25460  	}, {
 25461  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate",
 25462  		expectError:     true,
 25463  		allowPrivileged: true,
 25464  		podSpec: &core.PodSpec{
 25465  			SecurityContext: &core.PodSecurityContext{
 25466  				HostNetwork: true,
 25467  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25468  					HostProcess: &trueVar,
 25469  				},
 25470  			},
 25471  			Containers: []core.Container{{
 25472  				Name: containerName,
 25473  				SecurityContext: &core.SecurityContext{
 25474  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25475  						HostProcess: &trueVar,
 25476  					},
 25477  				},
 25478  			}},
 25479  			InitContainers: []core.Container{{
 25480  				Name: containerName,
 25481  				SecurityContext: &core.SecurityContext{
 25482  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25483  						HostProcess: &falseVar,
 25484  					},
 25485  				},
 25486  			}},
 25487  		},
 25488  	}, {
 25489  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 25490  		expectError:     true,
 25491  		allowPrivileged: true,
 25492  		podSpec: &core.PodSpec{
 25493  			SecurityContext: &core.PodSecurityContext{
 25494  				HostNetwork: true,
 25495  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25496  					HostProcess: &trueVar,
 25497  				},
 25498  			},
 25499  			Containers: []core.Container{{
 25500  				Name: containerName,
 25501  				SecurityContext: &core.SecurityContext{
 25502  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25503  						HostProcess: &trueVar,
 25504  					},
 25505  				},
 25506  			}, {
 25507  				Name: containerName,
 25508  				SecurityContext: &core.SecurityContext{
 25509  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25510  						HostProcess: &falseVar,
 25511  					},
 25512  				},
 25513  			}},
 25514  		},
 25515  	}, {
 25516  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate",
 25517  		expectError:     false,
 25518  		allowPrivileged: true,
 25519  		podSpec: &core.PodSpec{
 25520  			SecurityContext: &core.PodSecurityContext{
 25521  				HostNetwork: true,
 25522  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25523  					HostProcess: &trueVar,
 25524  				},
 25525  			},
 25526  			Containers: []core.Container{{
 25527  				Name: containerName,
 25528  				SecurityContext: &core.SecurityContext{
 25529  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25530  						HostProcess: &trueVar,
 25531  					},
 25532  				},
 25533  			}},
 25534  			InitContainers: []core.Container{{
 25535  				Name: containerName,
 25536  			}},
 25537  		},
 25538  	}, {
 25539  		name:            "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate",
 25540  		expectError:     true,
 25541  		allowPrivileged: true,
 25542  		podSpec: &core.PodSpec{
 25543  			SecurityContext: &core.PodSecurityContext{
 25544  				HostNetwork: true,
 25545  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25546  					HostProcess: &falseVar,
 25547  				},
 25548  			},
 25549  			Containers: []core.Container{{
 25550  				Name: containerName,
 25551  				SecurityContext: &core.SecurityContext{
 25552  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25553  						HostProcess: &trueVar,
 25554  					},
 25555  				},
 25556  			}},
 25557  			InitContainers: []core.Container{{
 25558  				Name: containerName,
 25559  			}},
 25560  		},
 25561  	}, {
 25562  		name:            "Pod's HostProcess set to true but all containers override to false should not validate",
 25563  		expectError:     true,
 25564  		allowPrivileged: true,
 25565  		podSpec: &core.PodSpec{
 25566  			SecurityContext: &core.PodSecurityContext{
 25567  				HostNetwork: true,
 25568  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25569  					HostProcess: &trueVar,
 25570  				},
 25571  			},
 25572  			Containers: []core.Container{{
 25573  				Name: containerName,
 25574  				SecurityContext: &core.SecurityContext{
 25575  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25576  						HostProcess: &falseVar,
 25577  					},
 25578  				},
 25579  			}},
 25580  		},
 25581  	}, {
 25582  		name:            "Valid HostProcess pod should spec should not validate if allowPrivileged is not set",
 25583  		expectError:     true,
 25584  		allowPrivileged: false,
 25585  		podSpec: &core.PodSpec{
 25586  			SecurityContext: &core.PodSecurityContext{
 25587  				HostNetwork: true,
 25588  			},
 25589  			Containers: []core.Container{{
 25590  				Name: containerName,
 25591  				SecurityContext: &core.SecurityContext{
 25592  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25593  						HostProcess: &trueVar,
 25594  					},
 25595  				},
 25596  			}},
 25597  		},
 25598  	}, {
 25599  		name:            "Non-HostProcess ephemeral container in HostProcess pod should not validate",
 25600  		expectError:     true,
 25601  		allowPrivileged: true,
 25602  		podSpec: &core.PodSpec{
 25603  			SecurityContext: &core.PodSecurityContext{
 25604  				HostNetwork: true,
 25605  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25606  					HostProcess: &trueVar,
 25607  				},
 25608  			},
 25609  			Containers: []core.Container{{
 25610  				Name: containerName,
 25611  			}},
 25612  			EphemeralContainers: []core.EphemeralContainer{{
 25613  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25614  					SecurityContext: &core.SecurityContext{
 25615  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25616  							HostProcess: &falseVar,
 25617  						},
 25618  					},
 25619  				},
 25620  			}},
 25621  		},
 25622  	}, {
 25623  		name:            "HostProcess ephemeral container in HostProcess pod should validate",
 25624  		expectError:     false,
 25625  		allowPrivileged: true,
 25626  		podSpec: &core.PodSpec{
 25627  			SecurityContext: &core.PodSecurityContext{
 25628  				HostNetwork: true,
 25629  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25630  					HostProcess: &trueVar,
 25631  				},
 25632  			},
 25633  			Containers: []core.Container{{
 25634  				Name: containerName,
 25635  			}},
 25636  			EphemeralContainers: []core.EphemeralContainer{{
 25637  				EphemeralContainerCommon: core.EphemeralContainerCommon{},
 25638  			}},
 25639  		},
 25640  	}, {
 25641  		name:            "Non-HostProcess ephemeral container in Non-HostProcess pod should validate",
 25642  		expectError:     false,
 25643  		allowPrivileged: true,
 25644  		podSpec: &core.PodSpec{
 25645  			Containers: []core.Container{{
 25646  				Name: containerName,
 25647  			}},
 25648  			EphemeralContainers: []core.EphemeralContainer{{
 25649  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25650  					SecurityContext: &core.SecurityContext{
 25651  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25652  							HostProcess: &falseVar,
 25653  						},
 25654  					},
 25655  				},
 25656  			}},
 25657  		},
 25658  	}, {
 25659  		name:            "HostProcess ephemeral container in Non-HostProcess pod should not validate",
 25660  		expectError:     true,
 25661  		allowPrivileged: true,
 25662  		podSpec: &core.PodSpec{
 25663  			Containers: []core.Container{{
 25664  				Name: containerName,
 25665  			}},
 25666  			EphemeralContainers: []core.EphemeralContainer{{
 25667  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25668  					SecurityContext: &core.SecurityContext{
 25669  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25670  							HostProcess: &trueVar,
 25671  						},
 25672  					},
 25673  				},
 25674  			}},
 25675  		},
 25676  	},
 25677  	}
 25678  
 25679  	for _, testCase := range testCases {
 25680  		t.Run(testCase.name, func(t *testing.T) {
 25681  
 25682  			capabilities.SetForTests(capabilities.Capabilities{
 25683  				AllowPrivileged: testCase.allowPrivileged,
 25684  			})
 25685  
 25686  			errs := validateWindowsHostProcessPod(testCase.podSpec, field.NewPath("spec"))
 25687  			if testCase.expectError && len(errs) == 0 {
 25688  				t.Errorf("Unexpected success")
 25689  			}
 25690  			if !testCase.expectError && len(errs) != 0 {
 25691  				t.Errorf("Unexpected error(s): %v", errs)
 25692  			}
 25693  		})
 25694  	}
 25695  }
 25696  
 25697  func TestValidateOS(t *testing.T) {
 25698  	testCases := []struct {
 25699  		name        string
 25700  		expectError bool
 25701  		podSpec     *core.PodSpec
 25702  	}{{
 25703  		name:        "no OS field, featuregate",
 25704  		expectError: false,
 25705  		podSpec:     &core.PodSpec{OS: nil},
 25706  	}, {
 25707  		name:        "empty OS field, featuregate",
 25708  		expectError: true,
 25709  		podSpec:     &core.PodSpec{OS: &core.PodOS{}},
 25710  	}, {
 25711  		name:        "OS field, featuregate, valid OS",
 25712  		expectError: false,
 25713  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Linux}},
 25714  	}, {
 25715  		name:        "OS field, featuregate, valid OS",
 25716  		expectError: false,
 25717  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Windows}},
 25718  	}, {
 25719  		name:        "OS field, featuregate, empty OS",
 25720  		expectError: true,
 25721  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: ""}},
 25722  	}, {
 25723  		name:        "OS field, featuregate, invalid OS",
 25724  		expectError: true,
 25725  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}},
 25726  	},
 25727  	}
 25728  	for _, testCase := range testCases {
 25729  		t.Run(testCase.name, func(t *testing.T) {
 25730  			errs := validateOS(testCase.podSpec, field.NewPath("spec"), PodValidationOptions{})
 25731  			if testCase.expectError && len(errs) == 0 {
 25732  				t.Errorf("Unexpected success")
 25733  			}
 25734  			if !testCase.expectError && len(errs) != 0 {
 25735  				t.Errorf("Unexpected error(s): %v", errs)
 25736  			}
 25737  		})
 25738  	}
 25739  }
 25740  
 25741  func TestValidateAppArmorProfileFormat(t *testing.T) {
 25742  	tests := []struct {
 25743  		profile     string
 25744  		expectValid bool
 25745  	}{
 25746  		{"", true},
 25747  		{v1.DeprecatedAppArmorBetaProfileRuntimeDefault, true},
 25748  		{v1.DeprecatedAppArmorBetaProfileNameUnconfined, true},
 25749  		{"baz", false}, // Missing local prefix.
 25750  		{v1.DeprecatedAppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
 25751  		{v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-bar", true},
 25752  	}
 25753  
 25754  	for _, test := range tests {
 25755  		err := ValidateAppArmorProfileFormat(test.profile)
 25756  		if test.expectValid {
 25757  			assert.NoError(t, err, "Profile %s should be valid", test.profile)
 25758  		} else {
 25759  			assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
 25760  		}
 25761  	}
 25762  }
 25763  
 25764  func TestValidateDownwardAPIHostIPs(t *testing.T) {
 25765  	testCases := []struct {
 25766  		name           string
 25767  		expectError    bool
 25768  		featureEnabled bool
 25769  		fieldSel       *core.ObjectFieldSelector
 25770  	}{
 25771  		{
 25772  			name:           "has no hostIPs field, featuregate enabled",
 25773  			expectError:    false,
 25774  			featureEnabled: true,
 25775  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
 25776  		},
 25777  		{
 25778  			name:           "has hostIPs field, featuregate enabled",
 25779  			expectError:    false,
 25780  			featureEnabled: true,
 25781  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
 25782  		},
 25783  	}
 25784  	for _, testCase := range testCases {
 25785  		t.Run(testCase.name, func(t *testing.T) {
 25786  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)
 25787  
 25788  			errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled})
 25789  			if testCase.expectError && len(errs) == 0 {
 25790  				t.Errorf("Unexpected success")
 25791  			}
 25792  			if !testCase.expectError && len(errs) != 0 {
 25793  				t.Errorf("Unexpected error(s): %v", errs)
 25794  			}
 25795  		})
 25796  	}
 25797  }
 25798  
 25799  func TestValidatePVSecretReference(t *testing.T) {
 25800  	rootFld := field.NewPath("name")
 25801  	type args struct {
 25802  		secretRef *core.SecretReference
 25803  		fldPath   *field.Path
 25804  	}
 25805  	tests := []struct {
 25806  		name          string
 25807  		args          args
 25808  		expectError   bool
 25809  		expectedError string
 25810  	}{{
 25811  		name:          "invalid secret ref name",
 25812  		args:          args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld},
 25813  		expectError:   true,
 25814  		expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
 25815  	}, {
 25816  		name:          "invalid secret ref namespace",
 25817  		args:          args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld},
 25818  		expectError:   true,
 25819  		expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg,
 25820  	}, {
 25821  		name:          "invalid secret: missing namespace",
 25822  		args:          args{&core.SecretReference{Name: "valid"}, rootFld},
 25823  		expectError:   true,
 25824  		expectedError: "name.namespace: Required value",
 25825  	}, {
 25826  		name:          "invalid secret : missing name",
 25827  		args:          args{&core.SecretReference{Namespace: "default"}, rootFld},
 25828  		expectError:   true,
 25829  		expectedError: "name.name: Required value",
 25830  	}, {
 25831  		name:          "valid secret",
 25832  		args:          args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld},
 25833  		expectError:   false,
 25834  		expectedError: "",
 25835  	},
 25836  	}
 25837  	for _, tt := range tests {
 25838  		t.Run(tt.name, func(t *testing.T) {
 25839  			errs := validatePVSecretReference(tt.args.secretRef, tt.args.fldPath)
 25840  			if tt.expectError && len(errs) == 0 {
 25841  				t.Errorf("Unexpected success")
 25842  			}
 25843  			if tt.expectError && len(errs) != 0 {
 25844  				str := errs[0].Error()
 25845  				if str != "" && !strings.Contains(str, tt.expectedError) {
 25846  					t.Errorf("%s: expected error detail either empty or %q, got %q", tt.name, tt.expectedError, str)
 25847  				}
 25848  			}
 25849  			if !tt.expectError && len(errs) != 0 {
 25850  				t.Errorf("Unexpected error(s): %v", errs)
 25851  			}
 25852  		})
 25853  	}
 25854  }
 25855  
 25856  func TestValidateDynamicResourceAllocation(t *testing.T) {
 25857  	externalClaimName := "some-claim"
 25858  	externalClaimTemplateName := "some-claim-template"
 25859  	goodClaimSource := core.ClaimSource{
 25860  		ResourceClaimName: &externalClaimName,
 25861  	}
 25862  	shortPodName := &metav1.ObjectMeta{
 25863  		Name: "some-pod",
 25864  	}
 25865  	brokenPodName := &metav1.ObjectMeta{
 25866  		Name: ".dot.com",
 25867  	}
 25868  	goodClaimTemplate := core.PodSpec{
 25869  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}},
 25870  		RestartPolicy: core.RestartPolicyAlways,
 25871  		DNSPolicy:     core.DNSClusterFirst,
 25872  		ResourceClaims: []core.PodResourceClaim{{
 25873  			Name: "my-claim-template",
 25874  			Source: core.ClaimSource{
 25875  				ResourceClaimTemplateName: &externalClaimTemplateName,
 25876  			},
 25877  		}},
 25878  	}
 25879  	goodClaimReference := core.PodSpec{
 25880  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}},
 25881  		RestartPolicy: core.RestartPolicyAlways,
 25882  		DNSPolicy:     core.DNSClusterFirst,
 25883  		ResourceClaims: []core.PodResourceClaim{{
 25884  			Name: "my-claim-reference",
 25885  			Source: core.ClaimSource{
 25886  				ResourceClaimName: &externalClaimName,
 25887  			},
 25888  		}},
 25889  	}
 25890  
 25891  	successCases := map[string]core.PodSpec{
 25892  		"resource claim reference": goodClaimTemplate,
 25893  		"resource claim template":  goodClaimTemplate,
 25894  		"multiple claims": {
 25895  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}},
 25896  			RestartPolicy: core.RestartPolicyAlways,
 25897  			DNSPolicy:     core.DNSClusterFirst,
 25898  			ResourceClaims: []core.PodResourceClaim{{
 25899  				Name:   "my-claim",
 25900  				Source: goodClaimSource,
 25901  			}, {
 25902  				Name:   "another-claim",
 25903  				Source: goodClaimSource,
 25904  			}},
 25905  		},
 25906  		"init container": {
 25907  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25908  			InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25909  			RestartPolicy:  core.RestartPolicyAlways,
 25910  			DNSPolicy:      core.DNSClusterFirst,
 25911  			ResourceClaims: []core.PodResourceClaim{{
 25912  				Name:   "my-claim",
 25913  				Source: goodClaimSource,
 25914  			}},
 25915  		},
 25916  	}
 25917  	for k, v := range successCases {
 25918  		t.Run(k, func(t *testing.T) {
 25919  			if errs := ValidatePodSpec(&v, shortPodName, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
 25920  				t.Errorf("expected success: %v", errs)
 25921  			}
 25922  		})
 25923  	}
 25924  
 25925  	failureCases := map[string]core.PodSpec{
 25926  		"pod claim name with prefix": {
 25927  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25928  			RestartPolicy: core.RestartPolicyAlways,
 25929  			DNSPolicy:     core.DNSClusterFirst,
 25930  			ResourceClaims: []core.PodResourceClaim{{
 25931  				Name:   "../my-claim",
 25932  				Source: goodClaimSource,
 25933  			}},
 25934  		},
 25935  		"pod claim name with path": {
 25936  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25937  			RestartPolicy: core.RestartPolicyAlways,
 25938  			DNSPolicy:     core.DNSClusterFirst,
 25939  			ResourceClaims: []core.PodResourceClaim{{
 25940  				Name:   "my/claim",
 25941  				Source: goodClaimSource,
 25942  			}},
 25943  		},
 25944  		"pod claim name empty": {
 25945  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25946  			RestartPolicy: core.RestartPolicyAlways,
 25947  			DNSPolicy:     core.DNSClusterFirst,
 25948  			ResourceClaims: []core.PodResourceClaim{{
 25949  				Name:   "",
 25950  				Source: goodClaimSource,
 25951  			}},
 25952  		},
 25953  		"duplicate pod claim entries": {
 25954  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25955  			RestartPolicy: core.RestartPolicyAlways,
 25956  			DNSPolicy:     core.DNSClusterFirst,
 25957  			ResourceClaims: []core.PodResourceClaim{{
 25958  				Name:   "my-claim",
 25959  				Source: goodClaimSource,
 25960  			}, {
 25961  				Name:   "my-claim",
 25962  				Source: goodClaimSource,
 25963  			}},
 25964  		},
 25965  		"resource claim source empty": {
 25966  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25967  			RestartPolicy: core.RestartPolicyAlways,
 25968  			DNSPolicy:     core.DNSClusterFirst,
 25969  			ResourceClaims: []core.PodResourceClaim{{
 25970  				Name:   "my-claim",
 25971  				Source: core.ClaimSource{},
 25972  			}},
 25973  		},
 25974  		"resource claim reference and template": {
 25975  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25976  			RestartPolicy: core.RestartPolicyAlways,
 25977  			DNSPolicy:     core.DNSClusterFirst,
 25978  			ResourceClaims: []core.PodResourceClaim{{
 25979  				Name: "my-claim",
 25980  				Source: core.ClaimSource{
 25981  					ResourceClaimName:         &externalClaimName,
 25982  					ResourceClaimTemplateName: &externalClaimTemplateName,
 25983  				},
 25984  			}},
 25985  		},
 25986  		"claim not found": {
 25987  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}},
 25988  			RestartPolicy: core.RestartPolicyAlways,
 25989  			DNSPolicy:     core.DNSClusterFirst,
 25990  			ResourceClaims: []core.PodResourceClaim{{
 25991  				Name:   "my-claim",
 25992  				Source: goodClaimSource,
 25993  			}},
 25994  		},
 25995  		"claim name empty": {
 25996  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}},
 25997  			RestartPolicy: core.RestartPolicyAlways,
 25998  			DNSPolicy:     core.DNSClusterFirst,
 25999  			ResourceClaims: []core.PodResourceClaim{{
 26000  				Name:   "my-claim",
 26001  				Source: goodClaimSource,
 26002  			}},
 26003  		},
 26004  		"pod claim name duplicates": {
 26005  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}},
 26006  			RestartPolicy: core.RestartPolicyAlways,
 26007  			DNSPolicy:     core.DNSClusterFirst,
 26008  			ResourceClaims: []core.PodResourceClaim{{
 26009  				Name:   "my-claim",
 26010  				Source: goodClaimSource,
 26011  			}},
 26012  		},
 26013  		"no claims defined": {
 26014  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26015  			RestartPolicy: core.RestartPolicyAlways,
 26016  			DNSPolicy:     core.DNSClusterFirst,
 26017  		},
 26018  		"duplicate pod claim name": {
 26019  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26020  			RestartPolicy: core.RestartPolicyAlways,
 26021  			DNSPolicy:     core.DNSClusterFirst,
 26022  			ResourceClaims: []core.PodResourceClaim{{
 26023  				Name:   "my-claim",
 26024  				Source: goodClaimSource,
 26025  			}, {
 26026  				Name:   "my-claim",
 26027  				Source: goodClaimSource,
 26028  			}},
 26029  		},
 26030  		"ephemeral container don't support resource requirements": {
 26031  			Containers:          []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26032  			EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr-ephemeral", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}, TargetContainerName: "ctr"}},
 26033  			RestartPolicy:       core.RestartPolicyAlways,
 26034  			DNSPolicy:           core.DNSClusterFirst,
 26035  			ResourceClaims: []core.PodResourceClaim{{
 26036  				Name:   "my-claim",
 26037  				Source: goodClaimSource,
 26038  			}},
 26039  		},
 26040  		"invalid claim template name": func() core.PodSpec {
 26041  			spec := goodClaimTemplate.DeepCopy()
 26042  			notLabel := ".foo_bar"
 26043  			spec.ResourceClaims[0].Source.ResourceClaimTemplateName = &notLabel
 26044  			return *spec
 26045  		}(),
 26046  		"invalid claim reference name": func() core.PodSpec {
 26047  			spec := goodClaimReference.DeepCopy()
 26048  			notLabel := ".foo_bar"
 26049  			spec.ResourceClaims[0].Source.ResourceClaimName = &notLabel
 26050  			return *spec
 26051  		}(),
 26052  	}
 26053  	for k, v := range failureCases {
 26054  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
 26055  			t.Errorf("expected failure for %q", k)
 26056  		}
 26057  	}
 26058  
 26059  	t.Run("generated-claim-name", func(t *testing.T) {
 26060  		for _, spec := range []*core.PodSpec{&goodClaimTemplate, &goodClaimReference} {
 26061  			claimName := spec.ResourceClaims[0].Name
 26062  			t.Run(claimName, func(t *testing.T) {
 26063  				for _, podMeta := range []*metav1.ObjectMeta{shortPodName, brokenPodName} {
 26064  					t.Run(podMeta.Name, func(t *testing.T) {
 26065  						errs := ValidatePodSpec(spec, podMeta, field.NewPath("field"), PodValidationOptions{})
 26066  						// Only one out of the four combinations fails.
 26067  						expectError := spec == &goodClaimTemplate && podMeta == brokenPodName
 26068  						if expectError && len(errs) == 0 {
 26069  							t.Error("did not get the expected failure")
 26070  						}
 26071  						if !expectError && len(errs) > 0 {
 26072  							t.Errorf("unexpected failures: %+v", errs)
 26073  						}
 26074  					})
 26075  				}
 26076  			})
 26077  		}
 26078  	})
 26079  }
 26080  
 26081  func TestValidateLoadBalancerStatus(t *testing.T) {
 26082  	ipModeVIP := core.LoadBalancerIPModeVIP
 26083  	ipModeProxy := core.LoadBalancerIPModeProxy
 26084  	ipModeDummy := core.LoadBalancerIPMode("dummy")
 26085  
 26086  	testCases := []struct {
 26087  		name          string
 26088  		ipModeEnabled bool
 26089  		nonLBAllowed  bool
 26090  		tweakLBStatus func(s *core.LoadBalancerStatus)
 26091  		tweakSvcSpec  func(s *core.ServiceSpec)
 26092  		numErrs       int
 26093  	}{
 26094  		{
 26095  			name:         "type is not LB",
 26096  			nonLBAllowed: false,
 26097  			tweakSvcSpec: func(s *core.ServiceSpec) {
 26098  				s.Type = core.ServiceTypeClusterIP
 26099  			},
 26100  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26101  				s.Ingress = []core.LoadBalancerIngress{{
 26102  					IP: "1.2.3.4",
 26103  				}}
 26104  			},
 26105  			numErrs: 1,
 26106  		}, {
 26107  			name:         "type is not LB. back-compat",
 26108  			nonLBAllowed: true,
 26109  			tweakSvcSpec: func(s *core.ServiceSpec) {
 26110  				s.Type = core.ServiceTypeClusterIP
 26111  			},
 26112  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26113  				s.Ingress = []core.LoadBalancerIngress{{
 26114  					IP: "1.2.3.4",
 26115  				}}
 26116  			},
 26117  			numErrs: 0,
 26118  		}, {
 26119  			name:          "valid vip ipMode",
 26120  			ipModeEnabled: true,
 26121  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26122  				s.Ingress = []core.LoadBalancerIngress{{
 26123  					IP:     "1.2.3.4",
 26124  					IPMode: &ipModeVIP,
 26125  				}}
 26126  			},
 26127  			numErrs: 0,
 26128  		}, {
 26129  			name:          "valid proxy ipMode",
 26130  			ipModeEnabled: true,
 26131  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26132  				s.Ingress = []core.LoadBalancerIngress{{
 26133  					IP:     "1.2.3.4",
 26134  					IPMode: &ipModeProxy,
 26135  				}}
 26136  			},
 26137  			numErrs: 0,
 26138  		}, {
 26139  			name:          "invalid ipMode",
 26140  			ipModeEnabled: true,
 26141  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26142  				s.Ingress = []core.LoadBalancerIngress{{
 26143  					IP:     "1.2.3.4",
 26144  					IPMode: &ipModeDummy,
 26145  				}}
 26146  			},
 26147  			numErrs: 1,
 26148  		}, {
 26149  			name:          "missing ipMode with LoadbalancerIPMode enabled",
 26150  			ipModeEnabled: true,
 26151  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26152  				s.Ingress = []core.LoadBalancerIngress{{
 26153  					IP: "1.2.3.4",
 26154  				}}
 26155  			},
 26156  			numErrs: 1,
 26157  		}, {
 26158  			name:          "missing ipMode with LoadbalancerIPMode disabled",
 26159  			ipModeEnabled: false,
 26160  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26161  				s.Ingress = []core.LoadBalancerIngress{{
 26162  					IP: "1.2.3.4",
 26163  				}}
 26164  			},
 26165  			numErrs: 0,
 26166  		}, {
 26167  			name:          "missing ip with ipMode present",
 26168  			ipModeEnabled: true,
 26169  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26170  				s.Ingress = []core.LoadBalancerIngress{{
 26171  					IPMode: &ipModeProxy,
 26172  				}}
 26173  			},
 26174  			numErrs: 1,
 26175  		},
 26176  	}
 26177  	for _, tc := range testCases {
 26178  		t.Run(tc.name, func(t *testing.T) {
 26179  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)
 26180  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowServiceLBStatusOnNonLB, tc.nonLBAllowed)
 26181  			status := core.LoadBalancerStatus{}
 26182  			tc.tweakLBStatus(&status)
 26183  			spec := core.ServiceSpec{Type: core.ServiceTypeLoadBalancer}
 26184  			if tc.tweakSvcSpec != nil {
 26185  				tc.tweakSvcSpec(&spec)
 26186  			}
 26187  			errs := ValidateLoadBalancerStatus(&status, field.NewPath("status"), &spec)
 26188  			if len(errs) != tc.numErrs {
 26189  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 26190  			}
 26191  		})
 26192  	}
 26193  }
 26194  
 26195  func TestValidateSleepAction(t *testing.T) {
 26196  	fldPath := field.NewPath("root")
 26197  	getInvalidStr := func(gracePeriod int64) string {
 26198  		return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
 26199  	}
 26200  
 26201  	testCases := []struct {
 26202  		name        string
 26203  		action      *core.SleepAction
 26204  		gracePeriod int64
 26205  		expectErr   field.ErrorList
 26206  	}{
 26207  		{
 26208  			name: "valid setting",
 26209  			action: &core.SleepAction{
 26210  				Seconds: 5,
 26211  			},
 26212  			gracePeriod: 30,
 26213  		},
 26214  		{
 26215  			name: "negative seconds",
 26216  			action: &core.SleepAction{
 26217  				Seconds: -1,
 26218  			},
 26219  			gracePeriod: 30,
 26220  			expectErr:   field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
 26221  		},
 26222  		{
 26223  			name: "longer than gracePeriod",
 26224  			action: &core.SleepAction{
 26225  				Seconds: 5,
 26226  			},
 26227  			gracePeriod: 3,
 26228  			expectErr:   field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
 26229  		},
 26230  	}
 26231  
 26232  	for _, tc := range testCases {
 26233  		t.Run(tc.name, func(t *testing.T) {
 26234  			errs := validateSleepAction(tc.action, tc.gracePeriod, fldPath)
 26235  
 26236  			if len(tc.expectErr) > 0 && len(errs) == 0 {
 26237  				t.Errorf("Unexpected success")
 26238  			} else if len(tc.expectErr) == 0 && len(errs) != 0 {
 26239  				t.Errorf("Unexpected error(s): %v", errs)
 26240  			} else if len(tc.expectErr) > 0 {
 26241  				if tc.expectErr[0].Error() != errs[0].Error() {
 26242  					t.Errorf("Unexpected error(s): %v", errs)
 26243  				}
 26244  			}
 26245  		})
 26246  	}
 26247  }