...

Source file src/k8s.io/kubernetes/pkg/apis/core/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/apis/core/validation

     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  			defer 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  			defer 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  		defer 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  			defer 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  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
  3026  			defer 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  			defer 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  			defer 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 TestValidateReadOnlyPersistentDisks(t *testing.T) {
  5404  	cases := []struct {
  5405  		name        string
  5406  		volumes     []core.Volume
  5407  		oldVolume   []core.Volume
  5408  		gateValue   bool
  5409  		expectError bool
  5410  	}{{
  5411  		name:        "gate on, read-only disk, nil old",
  5412  		gateValue:   true,
  5413  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5414  		oldVolume:   []core.Volume(nil),
  5415  		expectError: false,
  5416  	}, {
  5417  		name:        "gate off, read-only disk, nil old",
  5418  		gateValue:   false,
  5419  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5420  		oldVolume:   []core.Volume(nil),
  5421  		expectError: false,
  5422  	}, {
  5423  		name:        "gate on, read-write, nil old",
  5424  		gateValue:   true,
  5425  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5426  		oldVolume:   []core.Volume(nil),
  5427  		expectError: false,
  5428  	}, {
  5429  		name:        "gate off, read-write, nil old",
  5430  		gateValue:   false,
  5431  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5432  		oldVolume:   []core.Volume(nil),
  5433  		expectError: true,
  5434  	}, {
  5435  		name:        "gate on, new read-only and old read-write",
  5436  		gateValue:   true,
  5437  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5438  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5439  		expectError: false,
  5440  	}, {
  5441  		name:        "gate off, new read-only and old read-write",
  5442  		gateValue:   false,
  5443  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5444  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5445  		expectError: false,
  5446  	}, {
  5447  		name:        "gate on, new read-write and old read-write",
  5448  		gateValue:   true,
  5449  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5450  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5451  		expectError: false,
  5452  	}, {
  5453  		name:        "gate off, new read-write and old read-write",
  5454  		gateValue:   false,
  5455  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5456  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5457  		expectError: false,
  5458  	}, {
  5459  		name:        "gate on, new read-only and old read-only",
  5460  		gateValue:   true,
  5461  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5462  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5463  		expectError: false,
  5464  	}, {
  5465  		name:        "gate off, new read-only and old read-only",
  5466  		gateValue:   false,
  5467  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5468  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5469  		expectError: false,
  5470  	}, {
  5471  		name:        "gate on, new read-write and old read-only",
  5472  		gateValue:   true,
  5473  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5474  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5475  		expectError: false,
  5476  	}, {
  5477  		name:        "gate off, new read-write and old read-only",
  5478  		gateValue:   false,
  5479  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5480  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5481  		expectError: true,
  5482  	},
  5483  	}
  5484  	for _, testCase := range cases {
  5485  		t.Run(testCase.name, func(t *testing.T) {
  5486  			fidPath := field.NewPath("testField")
  5487  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, testCase.gateValue)()
  5488  			errs := ValidateReadOnlyPersistentDisks(testCase.volumes, testCase.oldVolume, fidPath)
  5489  			if !testCase.expectError && len(errs) != 0 {
  5490  				t.Errorf("expected success, got:%v", errs)
  5491  			}
  5492  		})
  5493  	}
  5494  }
  5495  
  5496  func TestHugePagesIsolation(t *testing.T) {
  5497  	testCases := map[string]struct {
  5498  		pod         *core.Pod
  5499  		expectError bool
  5500  	}{
  5501  		"Valid: request hugepages-2Mi": {
  5502  			pod: &core.Pod{
  5503  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
  5504  				Spec: core.PodSpec{
  5505  					Containers: []core.Container{{
  5506  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5507  						Resources: core.ResourceRequirements{
  5508  							Requests: core.ResourceList{
  5509  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5510  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5511  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5512  							},
  5513  							Limits: 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  						},
  5519  					}},
  5520  					RestartPolicy: core.RestartPolicyAlways,
  5521  					DNSPolicy:     core.DNSClusterFirst,
  5522  				},
  5523  			},
  5524  		},
  5525  		"Valid: request more than one hugepages size": {
  5526  			pod: &core.Pod{
  5527  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5528  				Spec: core.PodSpec{
  5529  					Containers: []core.Container{{
  5530  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5531  						Resources: core.ResourceRequirements{
  5532  							Requests: core.ResourceList{
  5533  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5534  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5535  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5536  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5537  							},
  5538  							Limits: core.ResourceList{
  5539  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5540  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5541  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5542  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5543  							},
  5544  						},
  5545  					}},
  5546  					RestartPolicy: core.RestartPolicyAlways,
  5547  					DNSPolicy:     core.DNSClusterFirst,
  5548  				},
  5549  			},
  5550  			expectError: false,
  5551  		},
  5552  		"Valid: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": {
  5553  			pod: &core.Pod{
  5554  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
  5555  				Spec: core.PodSpec{
  5556  					Containers: []core.Container{{
  5557  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5558  						Resources: core.ResourceRequirements{
  5559  							Requests: core.ResourceList{
  5560  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5561  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5562  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5563  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5564  							},
  5565  							Limits: core.ResourceList{
  5566  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5567  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5568  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5569  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5570  							},
  5571  						},
  5572  					}},
  5573  					RestartPolicy: core.RestartPolicyAlways,
  5574  					DNSPolicy:     core.DNSClusterFirst,
  5575  				},
  5576  			},
  5577  		},
  5578  		"Invalid: not requesting cpu and memory": {
  5579  			pod: &core.Pod{
  5580  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
  5581  				Spec: core.PodSpec{
  5582  					Containers: []core.Container{{
  5583  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5584  						Resources: core.ResourceRequirements{
  5585  							Requests: core.ResourceList{
  5586  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5587  							},
  5588  							Limits: core.ResourceList{
  5589  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5590  							},
  5591  						},
  5592  					}},
  5593  					RestartPolicy: core.RestartPolicyAlways,
  5594  					DNSPolicy:     core.DNSClusterFirst,
  5595  				},
  5596  			},
  5597  			expectError: true,
  5598  		},
  5599  		"Invalid: request 1Gi hugepages-2Mi but limit 2Gi": {
  5600  			pod: &core.Pod{
  5601  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5602  				Spec: core.PodSpec{
  5603  					Containers: []core.Container{{
  5604  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5605  						Resources: core.ResourceRequirements{
  5606  							Requests: core.ResourceList{
  5607  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5608  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5609  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5610  							},
  5611  							Limits: core.ResourceList{
  5612  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5613  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5614  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("2Gi"),
  5615  							},
  5616  						},
  5617  					}},
  5618  					RestartPolicy: core.RestartPolicyAlways,
  5619  					DNSPolicy:     core.DNSClusterFirst,
  5620  				},
  5621  			},
  5622  			expectError: true,
  5623  		},
  5624  	}
  5625  	for tcName, tc := range testCases {
  5626  		t.Run(tcName, func(t *testing.T) {
  5627  			errs := ValidatePodCreate(tc.pod, PodValidationOptions{})
  5628  			if tc.expectError && len(errs) == 0 {
  5629  				t.Errorf("Unexpected success")
  5630  			}
  5631  			if !tc.expectError && len(errs) != 0 {
  5632  				t.Errorf("Unexpected error(s): %v", errs)
  5633  			}
  5634  		})
  5635  	}
  5636  }
  5637  
  5638  func TestPVCVolumeMode(t *testing.T) {
  5639  	block := core.PersistentVolumeBlock
  5640  	file := core.PersistentVolumeFilesystem
  5641  	fake := core.PersistentVolumeMode("fake")
  5642  	empty := core.PersistentVolumeMode("")
  5643  
  5644  	// Success Cases
  5645  	successCasesPVC := map[string]*core.PersistentVolumeClaim{
  5646  		"valid block value":      createTestVolModePVC(&block),
  5647  		"valid filesystem value": createTestVolModePVC(&file),
  5648  		"valid nil value":        createTestVolModePVC(nil),
  5649  	}
  5650  	for k, v := range successCasesPVC {
  5651  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5652  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 {
  5653  			t.Errorf("expected success for %s", k)
  5654  		}
  5655  	}
  5656  
  5657  	// Error Cases
  5658  	errorCasesPVC := map[string]*core.PersistentVolumeClaim{
  5659  		"invalid value": createTestVolModePVC(&fake),
  5660  		"empty value":   createTestVolModePVC(&empty),
  5661  	}
  5662  	for k, v := range errorCasesPVC {
  5663  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5664  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 {
  5665  			t.Errorf("expected failure for %s", k)
  5666  		}
  5667  	}
  5668  }
  5669  
  5670  func TestPVVolumeMode(t *testing.T) {
  5671  	block := core.PersistentVolumeBlock
  5672  	file := core.PersistentVolumeFilesystem
  5673  	fake := core.PersistentVolumeMode("fake")
  5674  	empty := core.PersistentVolumeMode("")
  5675  
  5676  	// Success Cases
  5677  	successCasesPV := map[string]*core.PersistentVolume{
  5678  		"valid block value":      createTestVolModePV(&block),
  5679  		"valid filesystem value": createTestVolModePV(&file),
  5680  		"valid nil value":        createTestVolModePV(nil),
  5681  	}
  5682  	for k, v := range successCasesPV {
  5683  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5684  		if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 {
  5685  			t.Errorf("expected success for %s", k)
  5686  		}
  5687  	}
  5688  
  5689  	// Error Cases
  5690  	errorCasesPV := map[string]*core.PersistentVolume{
  5691  		"invalid value": createTestVolModePV(&fake),
  5692  		"empty value":   createTestVolModePV(&empty),
  5693  	}
  5694  	for k, v := range errorCasesPV {
  5695  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5696  		if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 {
  5697  			t.Errorf("expected failure for %s", k)
  5698  		}
  5699  	}
  5700  }
  5701  
  5702  func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
  5703  	validName := "valid-storage-class"
  5704  
  5705  	pvc := core.PersistentVolumeClaim{
  5706  		ObjectMeta: metav1.ObjectMeta{
  5707  			Name:      "foo",
  5708  			Namespace: "default",
  5709  		},
  5710  		Spec: core.PersistentVolumeClaimSpec{
  5711  			Resources: core.VolumeResourceRequirements{
  5712  				Requests: core.ResourceList{
  5713  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5714  				},
  5715  			},
  5716  			AccessModes:      []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5717  			StorageClassName: &validName,
  5718  			VolumeMode:       vmode,
  5719  		},
  5720  	}
  5721  	return &pvc
  5722  }
  5723  
  5724  func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
  5725  
  5726  	// PersistentVolume with VolumeMode set (valid and invalid)
  5727  	pv := core.PersistentVolume{
  5728  		ObjectMeta: metav1.ObjectMeta{
  5729  			Name:      "foo",
  5730  			Namespace: "",
  5731  		},
  5732  		Spec: core.PersistentVolumeSpec{
  5733  			Capacity: core.ResourceList{
  5734  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5735  			},
  5736  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5737  			PersistentVolumeSource: core.PersistentVolumeSource{
  5738  				HostPath: &core.HostPathVolumeSource{
  5739  					Path: "/foo",
  5740  					Type: newHostPathType(string(core.HostPathDirectory)),
  5741  				},
  5742  			},
  5743  			StorageClassName: "test-storage-class",
  5744  			VolumeMode:       vmode,
  5745  		},
  5746  	}
  5747  	return &pv
  5748  }
  5749  
  5750  func createTestPV() *core.PersistentVolume {
  5751  
  5752  	// PersistentVolume with VolumeMode set (valid and invalid)
  5753  	pv := core.PersistentVolume{
  5754  		ObjectMeta: metav1.ObjectMeta{
  5755  			Name:      "foo",
  5756  			Namespace: "",
  5757  		},
  5758  		Spec: core.PersistentVolumeSpec{
  5759  			Capacity: core.ResourceList{
  5760  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5761  			},
  5762  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5763  			PersistentVolumeSource: core.PersistentVolumeSource{
  5764  				HostPath: &core.HostPathVolumeSource{
  5765  					Path: "/foo",
  5766  					Type: newHostPathType(string(core.HostPathDirectory)),
  5767  				},
  5768  			},
  5769  			StorageClassName: "test-storage-class",
  5770  		},
  5771  	}
  5772  	return &pv
  5773  }
  5774  
  5775  func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5776  
  5777  	testCases := []core.VolumeSource{
  5778  		{EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}},
  5779  	}
  5780  
  5781  	for _, tc := range testCases {
  5782  		if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil, PodValidationOptions{}); len(errs) != 0 {
  5783  			t.Errorf("expected success: %v", errs)
  5784  		}
  5785  	}
  5786  
  5787  	containerLimitCase := core.ResourceRequirements{
  5788  		Limits: core.ResourceList{
  5789  			core.ResourceEphemeralStorage: *resource.NewMilliQuantity(
  5790  				int64(40000),
  5791  				resource.BinarySI),
  5792  		},
  5793  	}
  5794  	if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
  5795  		t.Errorf("expected success: %v", errs)
  5796  	}
  5797  }
  5798  
  5799  func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5800  	spec := core.ResourceQuotaSpec{
  5801  		Hard: core.ResourceList{
  5802  			core.ResourceCPU:                      resource.MustParse("100"),
  5803  			core.ResourceMemory:                   resource.MustParse("10000"),
  5804  			core.ResourceRequestsCPU:              resource.MustParse("100"),
  5805  			core.ResourceRequestsMemory:           resource.MustParse("10000"),
  5806  			core.ResourceLimitsCPU:                resource.MustParse("100"),
  5807  			core.ResourceLimitsMemory:             resource.MustParse("10000"),
  5808  			core.ResourcePods:                     resource.MustParse("10"),
  5809  			core.ResourceServices:                 resource.MustParse("0"),
  5810  			core.ResourceReplicationControllers:   resource.MustParse("10"),
  5811  			core.ResourceQuotas:                   resource.MustParse("10"),
  5812  			core.ResourceConfigMaps:               resource.MustParse("10"),
  5813  			core.ResourceSecrets:                  resource.MustParse("10"),
  5814  			core.ResourceEphemeralStorage:         resource.MustParse("10000"),
  5815  			core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"),
  5816  			core.ResourceLimitsEphemeralStorage:   resource.MustParse("10000"),
  5817  		},
  5818  	}
  5819  	resourceQuota := &core.ResourceQuota{
  5820  		ObjectMeta: metav1.ObjectMeta{
  5821  			Name:      "abc",
  5822  			Namespace: "foo",
  5823  		},
  5824  		Spec: spec,
  5825  	}
  5826  
  5827  	if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
  5828  		t.Errorf("expected success: %v", errs)
  5829  	}
  5830  }
  5831  
  5832  func TestValidatePorts(t *testing.T) {
  5833  	successCase := []core.ContainerPort{
  5834  		{Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  5835  		{Name: "easy", ContainerPort: 82, Protocol: "TCP"},
  5836  		{Name: "as", ContainerPort: 83, Protocol: "UDP"},
  5837  		{Name: "do-re-me", ContainerPort: 84, Protocol: "SCTP"},
  5838  		{ContainerPort: 85, Protocol: "TCP"},
  5839  	}
  5840  	if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 {
  5841  		t.Errorf("expected success: %v", errs)
  5842  	}
  5843  
  5844  	nonCanonicalCase := []core.ContainerPort{
  5845  		{ContainerPort: 80, Protocol: "TCP"},
  5846  	}
  5847  	if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 {
  5848  		t.Errorf("expected success: %v", errs)
  5849  	}
  5850  
  5851  	errorCases := map[string]struct {
  5852  		P []core.ContainerPort
  5853  		T field.ErrorType
  5854  		F string
  5855  		D string
  5856  	}{
  5857  		"name > 15 characters": {
  5858  			[]core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
  5859  			field.ErrorTypeInvalid,
  5860  			"name", "15",
  5861  		},
  5862  		"name contains invalid characters": {
  5863  			[]core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
  5864  			field.ErrorTypeInvalid,
  5865  			"name", "alpha-numeric",
  5866  		},
  5867  		"name is a number": {
  5868  			[]core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
  5869  			field.ErrorTypeInvalid,
  5870  			"name", "at least one letter",
  5871  		},
  5872  		"name not unique": {
  5873  			[]core.ContainerPort{
  5874  				{Name: "abc", ContainerPort: 80, Protocol: "TCP"},
  5875  				{Name: "abc", ContainerPort: 81, Protocol: "TCP"},
  5876  			},
  5877  			field.ErrorTypeDuplicate,
  5878  			"[1].name", "",
  5879  		},
  5880  		"zero container port": {
  5881  			[]core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
  5882  			field.ErrorTypeRequired,
  5883  			"containerPort", "",
  5884  		},
  5885  		"invalid container port": {
  5886  			[]core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
  5887  			field.ErrorTypeInvalid,
  5888  			"containerPort", "between",
  5889  		},
  5890  		"invalid host port": {
  5891  			[]core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
  5892  			field.ErrorTypeInvalid,
  5893  			"hostPort", "between",
  5894  		},
  5895  		"invalid protocol case": {
  5896  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
  5897  			field.ErrorTypeNotSupported,
  5898  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5899  		},
  5900  		"invalid protocol": {
  5901  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
  5902  			field.ErrorTypeNotSupported,
  5903  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5904  		},
  5905  		"protocol required": {
  5906  			[]core.ContainerPort{{Name: "abc", ContainerPort: 80}},
  5907  			field.ErrorTypeRequired,
  5908  			"protocol", "",
  5909  		},
  5910  	}
  5911  	for k, v := range errorCases {
  5912  		errs := validateContainerPorts(v.P, field.NewPath("field"))
  5913  		if len(errs) == 0 {
  5914  			t.Errorf("expected failure for %s", k)
  5915  		}
  5916  		for i := range errs {
  5917  			if errs[i].Type != v.T {
  5918  				t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
  5919  			}
  5920  			if !strings.Contains(errs[i].Field, v.F) {
  5921  				t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
  5922  			}
  5923  			if !strings.Contains(errs[i].Detail, v.D) {
  5924  				t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
  5925  			}
  5926  		}
  5927  	}
  5928  }
  5929  
  5930  func TestLocalStorageEnvWithFeatureGate(t *testing.T) {
  5931  	testCases := []core.EnvVar{{
  5932  		Name: "ephemeral-storage-limits",
  5933  		ValueFrom: &core.EnvVarSource{
  5934  			ResourceFieldRef: &core.ResourceFieldSelector{
  5935  				ContainerName: "test-container",
  5936  				Resource:      "limits.ephemeral-storage",
  5937  			},
  5938  		},
  5939  	}, {
  5940  		Name: "ephemeral-storage-requests",
  5941  		ValueFrom: &core.EnvVarSource{
  5942  			ResourceFieldRef: &core.ResourceFieldSelector{
  5943  				ContainerName: "test-container",
  5944  				Resource:      "requests.ephemeral-storage",
  5945  			},
  5946  		},
  5947  	},
  5948  	}
  5949  	for _, testCase := range testCases {
  5950  		if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  5951  			t.Errorf("expected success, got: %v", errs)
  5952  		}
  5953  	}
  5954  }
  5955  
  5956  func TestHugePagesEnv(t *testing.T) {
  5957  	testCases := []core.EnvVar{{
  5958  		Name: "hugepages-limits",
  5959  		ValueFrom: &core.EnvVarSource{
  5960  			ResourceFieldRef: &core.ResourceFieldSelector{
  5961  				ContainerName: "test-container",
  5962  				Resource:      "limits.hugepages-2Mi",
  5963  			},
  5964  		},
  5965  	}, {
  5966  		Name: "hugepages-requests",
  5967  		ValueFrom: &core.EnvVarSource{
  5968  			ResourceFieldRef: &core.ResourceFieldSelector{
  5969  				ContainerName: "test-container",
  5970  				Resource:      "requests.hugepages-2Mi",
  5971  			},
  5972  		},
  5973  	},
  5974  	}
  5975  	// enable gate
  5976  	for _, testCase := range testCases {
  5977  		t.Run(testCase.Name, func(t *testing.T) {
  5978  			opts := PodValidationOptions{}
  5979  			if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), opts); len(errs) != 0 {
  5980  				t.Errorf("expected success, got: %v", errs)
  5981  			}
  5982  		})
  5983  	}
  5984  }
  5985  
  5986  func TestRelaxedValidateEnv(t *testing.T) {
  5987  	successCase := []core.EnvVar{
  5988  		{Name: "!\"#$%&'()", Value: "value"},
  5989  		{Name: "* +,-./0123456789", Value: "value"},
  5990  		{Name: ":;<>?@", Value: "value"},
  5991  		{Name: "ABCDEFG", Value: "value"},
  5992  		{Name: "abcdefghijklmn", Value: "value"},
  5993  		{Name: "[\\]^_`{}|~", Value: "value"},
  5994  		{
  5995  			Name: "!\"#$%&'()",
  5996  			ValueFrom: &core.EnvVarSource{
  5997  				FieldRef: &core.ObjectFieldSelector{
  5998  					APIVersion: "v1",
  5999  					FieldPath:  "metadata.annotations['key']",
  6000  				},
  6001  			},
  6002  		}, {
  6003  			Name: "!\"#$%&'()",
  6004  			ValueFrom: &core.EnvVarSource{
  6005  				FieldRef: &core.ObjectFieldSelector{
  6006  					APIVersion: "v1",
  6007  					FieldPath:  "metadata.labels['key']",
  6008  				},
  6009  			},
  6010  		}, {
  6011  			Name: "* +,-./0123456789",
  6012  			ValueFrom: &core.EnvVarSource{
  6013  				FieldRef: &core.ObjectFieldSelector{
  6014  					APIVersion: "v1",
  6015  					FieldPath:  "metadata.name",
  6016  				},
  6017  			},
  6018  		}, {
  6019  			Name: "* +,-./0123456789",
  6020  			ValueFrom: &core.EnvVarSource{
  6021  				FieldRef: &core.ObjectFieldSelector{
  6022  					APIVersion: "v1",
  6023  					FieldPath:  "metadata.namespace",
  6024  				},
  6025  			},
  6026  		}, {
  6027  			Name: "* +,-./0123456789",
  6028  			ValueFrom: &core.EnvVarSource{
  6029  				FieldRef: &core.ObjectFieldSelector{
  6030  					APIVersion: "v1",
  6031  					FieldPath:  "metadata.uid",
  6032  				},
  6033  			},
  6034  		}, {
  6035  			Name: ":;<>?@",
  6036  			ValueFrom: &core.EnvVarSource{
  6037  				FieldRef: &core.ObjectFieldSelector{
  6038  					APIVersion: "v1",
  6039  					FieldPath:  "spec.nodeName",
  6040  				},
  6041  			},
  6042  		}, {
  6043  			Name: ":;<>?@",
  6044  			ValueFrom: &core.EnvVarSource{
  6045  				FieldRef: &core.ObjectFieldSelector{
  6046  					APIVersion: "v1",
  6047  					FieldPath:  "spec.serviceAccountName",
  6048  				},
  6049  			},
  6050  		}, {
  6051  			Name: ":;<>?@",
  6052  			ValueFrom: &core.EnvVarSource{
  6053  				FieldRef: &core.ObjectFieldSelector{
  6054  					APIVersion: "v1",
  6055  					FieldPath:  "status.hostIP",
  6056  				},
  6057  			},
  6058  		}, {
  6059  			Name: ":;<>?@",
  6060  			ValueFrom: &core.EnvVarSource{
  6061  				FieldRef: &core.ObjectFieldSelector{
  6062  					APIVersion: "v1",
  6063  					FieldPath:  "status.podIP",
  6064  				},
  6065  			},
  6066  		}, {
  6067  			Name: "abcdefghijklmn",
  6068  			ValueFrom: &core.EnvVarSource{
  6069  				FieldRef: &core.ObjectFieldSelector{
  6070  					APIVersion: "v1",
  6071  					FieldPath:  "status.podIPs",
  6072  				},
  6073  			},
  6074  		},
  6075  		{
  6076  			Name: "abcdefghijklmn",
  6077  			ValueFrom: &core.EnvVarSource{
  6078  				SecretKeyRef: &core.SecretKeySelector{
  6079  					LocalObjectReference: core.LocalObjectReference{
  6080  						Name: "some-secret",
  6081  					},
  6082  					Key: "secret-key",
  6083  				},
  6084  			},
  6085  		}, {
  6086  			Name: "!\"#$%&'()",
  6087  			ValueFrom: &core.EnvVarSource{
  6088  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6089  					LocalObjectReference: core.LocalObjectReference{
  6090  						Name: "some-config-map",
  6091  					},
  6092  					Key: "some-key",
  6093  				},
  6094  			},
  6095  		},
  6096  	}
  6097  	if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6098  		t.Errorf("expected success, got: %v", errs)
  6099  	}
  6100  
  6101  	errorCases := []struct {
  6102  		name          string
  6103  		envs          []core.EnvVar
  6104  		expectedError string
  6105  	}{{
  6106  		name:          "illegal character",
  6107  		envs:          []core.EnvVar{{Name: "=abc"}},
  6108  		expectedError: `[0].name: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
  6109  	}, {
  6110  		name:          "zero-length name",
  6111  		envs:          []core.EnvVar{{Name: ""}},
  6112  		expectedError: "[0].name: Required value",
  6113  	}, {
  6114  		name: "value and valueFrom specified",
  6115  		envs: []core.EnvVar{{
  6116  			Name:  "abc",
  6117  			Value: "foo",
  6118  			ValueFrom: &core.EnvVarSource{
  6119  				FieldRef: &core.ObjectFieldSelector{
  6120  					APIVersion: "v1",
  6121  					FieldPath:  "metadata.name",
  6122  				},
  6123  			},
  6124  		}},
  6125  		expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
  6126  	}, {
  6127  		name: "valueFrom without a source",
  6128  		envs: []core.EnvVar{{
  6129  			Name:      "abc",
  6130  			ValueFrom: &core.EnvVarSource{},
  6131  		}},
  6132  		expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
  6133  	}, {
  6134  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6135  		envs: []core.EnvVar{{
  6136  			Name: "abc",
  6137  			ValueFrom: &core.EnvVarSource{
  6138  				FieldRef: &core.ObjectFieldSelector{
  6139  					APIVersion: "v1",
  6140  					FieldPath:  "metadata.name",
  6141  				},
  6142  				SecretKeyRef: &core.SecretKeySelector{
  6143  					LocalObjectReference: core.LocalObjectReference{
  6144  						Name: "a-secret",
  6145  					},
  6146  					Key: "a-key",
  6147  				},
  6148  			},
  6149  		}},
  6150  		expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
  6151  	}, {
  6152  		name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
  6153  		envs: []core.EnvVar{{
  6154  			Name: "some_var_name",
  6155  			ValueFrom: &core.EnvVarSource{
  6156  				FieldRef: &core.ObjectFieldSelector{
  6157  					APIVersion: "v1",
  6158  					FieldPath:  "metadata.name",
  6159  				},
  6160  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6161  					LocalObjectReference: core.LocalObjectReference{
  6162  						Name: "some-config-map",
  6163  					},
  6164  					Key: "some-key",
  6165  				},
  6166  			},
  6167  		}},
  6168  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6169  	}, {
  6170  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6171  		envs: []core.EnvVar{{
  6172  			Name: "abc",
  6173  			ValueFrom: &core.EnvVarSource{
  6174  				FieldRef: &core.ObjectFieldSelector{
  6175  					APIVersion: "v1",
  6176  					FieldPath:  "metadata.name",
  6177  				},
  6178  				SecretKeyRef: &core.SecretKeySelector{
  6179  					LocalObjectReference: core.LocalObjectReference{
  6180  						Name: "a-secret",
  6181  					},
  6182  					Key: "a-key",
  6183  				},
  6184  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6185  					LocalObjectReference: core.LocalObjectReference{
  6186  						Name: "some-config-map",
  6187  					},
  6188  					Key: "some-key",
  6189  				},
  6190  			},
  6191  		}},
  6192  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6193  	}, {
  6194  		name: "valueFrom.secretKeyRef.name invalid",
  6195  		envs: []core.EnvVar{{
  6196  			Name: "abc",
  6197  			ValueFrom: &core.EnvVarSource{
  6198  				SecretKeyRef: &core.SecretKeySelector{
  6199  					LocalObjectReference: core.LocalObjectReference{
  6200  						Name: "$%^&*#",
  6201  					},
  6202  					Key: "a-key",
  6203  				},
  6204  			},
  6205  		}},
  6206  	}, {
  6207  		name: "valueFrom.configMapKeyRef.name invalid",
  6208  		envs: []core.EnvVar{{
  6209  			Name: "abc",
  6210  			ValueFrom: &core.EnvVarSource{
  6211  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6212  					LocalObjectReference: core.LocalObjectReference{
  6213  						Name: "$%^&*#",
  6214  					},
  6215  					Key: "some-key",
  6216  				},
  6217  			},
  6218  		}},
  6219  	}, {
  6220  		name: "missing FieldPath on ObjectFieldSelector",
  6221  		envs: []core.EnvVar{{
  6222  			Name: "abc",
  6223  			ValueFrom: &core.EnvVarSource{
  6224  				FieldRef: &core.ObjectFieldSelector{
  6225  					APIVersion: "v1",
  6226  				},
  6227  			},
  6228  		}},
  6229  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
  6230  	}, {
  6231  		name: "missing APIVersion on ObjectFieldSelector",
  6232  		envs: []core.EnvVar{{
  6233  			Name: "abc",
  6234  			ValueFrom: &core.EnvVarSource{
  6235  				FieldRef: &core.ObjectFieldSelector{
  6236  					FieldPath: "metadata.name",
  6237  				},
  6238  			},
  6239  		}},
  6240  		expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
  6241  	}, {
  6242  		name: "invalid fieldPath",
  6243  		envs: []core.EnvVar{{
  6244  			Name: "abc",
  6245  			ValueFrom: &core.EnvVarSource{
  6246  				FieldRef: &core.ObjectFieldSelector{
  6247  					FieldPath:  "metadata.whoops",
  6248  					APIVersion: "v1",
  6249  				},
  6250  			},
  6251  		}},
  6252  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
  6253  	}, {
  6254  		name: "metadata.name with subscript",
  6255  		envs: []core.EnvVar{{
  6256  			Name: "labels",
  6257  			ValueFrom: &core.EnvVarSource{
  6258  				FieldRef: &core.ObjectFieldSelector{
  6259  					FieldPath:  "metadata.name['key']",
  6260  					APIVersion: "v1",
  6261  				},
  6262  			},
  6263  		}},
  6264  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
  6265  	}, {
  6266  		name: "metadata.labels without subscript",
  6267  		envs: []core.EnvVar{{
  6268  			Name: "labels",
  6269  			ValueFrom: &core.EnvVarSource{
  6270  				FieldRef: &core.ObjectFieldSelector{
  6271  					FieldPath:  "metadata.labels",
  6272  					APIVersion: "v1",
  6273  				},
  6274  			},
  6275  		}},
  6276  		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"`,
  6277  	}, {
  6278  		name: "metadata.annotations without subscript",
  6279  		envs: []core.EnvVar{{
  6280  			Name: "abc",
  6281  			ValueFrom: &core.EnvVarSource{
  6282  				FieldRef: &core.ObjectFieldSelector{
  6283  					FieldPath:  "metadata.annotations",
  6284  					APIVersion: "v1",
  6285  				},
  6286  			},
  6287  		}},
  6288  		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"`,
  6289  	}, {
  6290  		name: "metadata.annotations with invalid key",
  6291  		envs: []core.EnvVar{{
  6292  			Name: "abc",
  6293  			ValueFrom: &core.EnvVarSource{
  6294  				FieldRef: &core.ObjectFieldSelector{
  6295  					FieldPath:  "metadata.annotations['invalid~key']",
  6296  					APIVersion: "v1",
  6297  				},
  6298  			},
  6299  		}},
  6300  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
  6301  	}, {
  6302  		name: "metadata.labels with invalid key",
  6303  		envs: []core.EnvVar{{
  6304  			Name: "abc",
  6305  			ValueFrom: &core.EnvVarSource{
  6306  				FieldRef: &core.ObjectFieldSelector{
  6307  					FieldPath:  "metadata.labels['Www.k8s.io/test']",
  6308  					APIVersion: "v1",
  6309  				},
  6310  			},
  6311  		}},
  6312  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
  6313  	}, {
  6314  		name: "unsupported fieldPath",
  6315  		envs: []core.EnvVar{{
  6316  			Name: "abc",
  6317  			ValueFrom: &core.EnvVarSource{
  6318  				FieldRef: &core.ObjectFieldSelector{
  6319  					FieldPath:  "status.phase",
  6320  					APIVersion: "v1",
  6321  				},
  6322  			},
  6323  		}},
  6324  		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"`,
  6325  	},
  6326  	}
  6327  	for _, tc := range errorCases {
  6328  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
  6329  			t.Errorf("expected failure for %s", tc.name)
  6330  		} else {
  6331  			for i := range errs {
  6332  				str := errs[i].Error()
  6333  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6334  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6335  				}
  6336  			}
  6337  		}
  6338  	}
  6339  }
  6340  
  6341  func TestValidateEnv(t *testing.T) {
  6342  	successCase := []core.EnvVar{
  6343  		{Name: "abc", Value: "value"},
  6344  		{Name: "ABC", Value: "value"},
  6345  		{Name: "AbC_123", Value: "value"},
  6346  		{Name: "abc", Value: ""},
  6347  		{Name: "a.b.c", Value: "value"},
  6348  		{Name: "a-b-c", Value: "value"}, {
  6349  			Name: "abc",
  6350  			ValueFrom: &core.EnvVarSource{
  6351  				FieldRef: &core.ObjectFieldSelector{
  6352  					APIVersion: "v1",
  6353  					FieldPath:  "metadata.annotations['key']",
  6354  				},
  6355  			},
  6356  		}, {
  6357  			Name: "abc",
  6358  			ValueFrom: &core.EnvVarSource{
  6359  				FieldRef: &core.ObjectFieldSelector{
  6360  					APIVersion: "v1",
  6361  					FieldPath:  "metadata.labels['key']",
  6362  				},
  6363  			},
  6364  		}, {
  6365  			Name: "abc",
  6366  			ValueFrom: &core.EnvVarSource{
  6367  				FieldRef: &core.ObjectFieldSelector{
  6368  					APIVersion: "v1",
  6369  					FieldPath:  "metadata.name",
  6370  				},
  6371  			},
  6372  		}, {
  6373  			Name: "abc",
  6374  			ValueFrom: &core.EnvVarSource{
  6375  				FieldRef: &core.ObjectFieldSelector{
  6376  					APIVersion: "v1",
  6377  					FieldPath:  "metadata.namespace",
  6378  				},
  6379  			},
  6380  		}, {
  6381  			Name: "abc",
  6382  			ValueFrom: &core.EnvVarSource{
  6383  				FieldRef: &core.ObjectFieldSelector{
  6384  					APIVersion: "v1",
  6385  					FieldPath:  "metadata.uid",
  6386  				},
  6387  			},
  6388  		}, {
  6389  			Name: "abc",
  6390  			ValueFrom: &core.EnvVarSource{
  6391  				FieldRef: &core.ObjectFieldSelector{
  6392  					APIVersion: "v1",
  6393  					FieldPath:  "spec.nodeName",
  6394  				},
  6395  			},
  6396  		}, {
  6397  			Name: "abc",
  6398  			ValueFrom: &core.EnvVarSource{
  6399  				FieldRef: &core.ObjectFieldSelector{
  6400  					APIVersion: "v1",
  6401  					FieldPath:  "spec.serviceAccountName",
  6402  				},
  6403  			},
  6404  		}, {
  6405  			Name: "abc",
  6406  			ValueFrom: &core.EnvVarSource{
  6407  				FieldRef: &core.ObjectFieldSelector{
  6408  					APIVersion: "v1",
  6409  					FieldPath:  "status.hostIP",
  6410  				},
  6411  			},
  6412  		}, {
  6413  			Name: "abc",
  6414  			ValueFrom: &core.EnvVarSource{
  6415  				FieldRef: &core.ObjectFieldSelector{
  6416  					APIVersion: "v1",
  6417  					FieldPath:  "status.podIP",
  6418  				},
  6419  			},
  6420  		}, {
  6421  			Name: "abc",
  6422  			ValueFrom: &core.EnvVarSource{
  6423  				FieldRef: &core.ObjectFieldSelector{
  6424  					APIVersion: "v1",
  6425  					FieldPath:  "status.podIPs",
  6426  				},
  6427  			},
  6428  		}, {
  6429  			Name: "secret_value",
  6430  			ValueFrom: &core.EnvVarSource{
  6431  				SecretKeyRef: &core.SecretKeySelector{
  6432  					LocalObjectReference: core.LocalObjectReference{
  6433  						Name: "some-secret",
  6434  					},
  6435  					Key: "secret-key",
  6436  				},
  6437  			},
  6438  		}, {
  6439  			Name: "ENV_VAR_1",
  6440  			ValueFrom: &core.EnvVarSource{
  6441  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6442  					LocalObjectReference: core.LocalObjectReference{
  6443  						Name: "some-config-map",
  6444  					},
  6445  					Key: "some-key",
  6446  				},
  6447  			},
  6448  		},
  6449  	}
  6450  	if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  6451  		t.Errorf("expected success, got: %v", errs)
  6452  	}
  6453  
  6454  	updateSuccessCase := []core.EnvVar{
  6455  		{Name: "!\"#$%&'()", Value: "value"},
  6456  		{Name: "* +,-./0123456789", Value: "value"},
  6457  		{Name: ":;<>?@", Value: "value"},
  6458  		{Name: "ABCDEFG", Value: "value"},
  6459  		{Name: "abcdefghijklmn", Value: "value"},
  6460  		{Name: "[\\]^_`{}|~", Value: "value"},
  6461  	}
  6462  
  6463  	if errs := ValidateEnv(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6464  		t.Errorf("expected success, got: %v", errs)
  6465  	}
  6466  
  6467  	updateErrorCase := []struct {
  6468  		name          string
  6469  		envs          []core.EnvVar
  6470  		expectedError string
  6471  	}{
  6472  		{
  6473  			name: "invalid name a",
  6474  			envs: []core.EnvVar{
  6475  				{Name: "!\"#$%&'()", Value: "value"},
  6476  			},
  6477  			expectedError: `field[0].name: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
  6478  		},
  6479  		{
  6480  			name: "invalid name b",
  6481  			envs: []core.EnvVar{
  6482  				{Name: "* +,-./0123456789", Value: "value"},
  6483  			},
  6484  			expectedError: `field[0].name: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
  6485  		},
  6486  		{
  6487  			name: "invalid name c",
  6488  			envs: []core.EnvVar{
  6489  				{Name: ":;<>?@", Value: "value"},
  6490  			},
  6491  			expectedError: `field[0].name: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
  6492  		},
  6493  		{
  6494  			name: "invalid name d",
  6495  			envs: []core.EnvVar{
  6496  				{Name: "[\\]^_{}|~", Value: "value"},
  6497  			},
  6498  			expectedError: `field[0].name: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
  6499  		},
  6500  	}
  6501  
  6502  	for _, tc := range updateErrorCase {
  6503  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6504  			t.Errorf("expected failure for %s", tc.name)
  6505  		} else {
  6506  			for i := range errs {
  6507  				str := errs[i].Error()
  6508  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6509  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6510  				}
  6511  			}
  6512  		}
  6513  	}
  6514  
  6515  	errorCases := []struct {
  6516  		name          string
  6517  		envs          []core.EnvVar
  6518  		expectedError string
  6519  	}{{
  6520  		name:          "zero-length name",
  6521  		envs:          []core.EnvVar{{Name: ""}},
  6522  		expectedError: "[0].name: Required value",
  6523  	}, {
  6524  		name: "value and valueFrom specified",
  6525  		envs: []core.EnvVar{{
  6526  			Name:  "abc",
  6527  			Value: "foo",
  6528  			ValueFrom: &core.EnvVarSource{
  6529  				FieldRef: &core.ObjectFieldSelector{
  6530  					APIVersion: "v1",
  6531  					FieldPath:  "metadata.name",
  6532  				},
  6533  			},
  6534  		}},
  6535  		expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
  6536  	}, {
  6537  		name: "valueFrom without a source",
  6538  		envs: []core.EnvVar{{
  6539  			Name:      "abc",
  6540  			ValueFrom: &core.EnvVarSource{},
  6541  		}},
  6542  		expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
  6543  	}, {
  6544  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6545  		envs: []core.EnvVar{{
  6546  			Name: "abc",
  6547  			ValueFrom: &core.EnvVarSource{
  6548  				FieldRef: &core.ObjectFieldSelector{
  6549  					APIVersion: "v1",
  6550  					FieldPath:  "metadata.name",
  6551  				},
  6552  				SecretKeyRef: &core.SecretKeySelector{
  6553  					LocalObjectReference: core.LocalObjectReference{
  6554  						Name: "a-secret",
  6555  					},
  6556  					Key: "a-key",
  6557  				},
  6558  			},
  6559  		}},
  6560  		expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
  6561  	}, {
  6562  		name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
  6563  		envs: []core.EnvVar{{
  6564  			Name: "some_var_name",
  6565  			ValueFrom: &core.EnvVarSource{
  6566  				FieldRef: &core.ObjectFieldSelector{
  6567  					APIVersion: "v1",
  6568  					FieldPath:  "metadata.name",
  6569  				},
  6570  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6571  					LocalObjectReference: core.LocalObjectReference{
  6572  						Name: "some-config-map",
  6573  					},
  6574  					Key: "some-key",
  6575  				},
  6576  			},
  6577  		}},
  6578  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6579  	}, {
  6580  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6581  		envs: []core.EnvVar{{
  6582  			Name: "abc",
  6583  			ValueFrom: &core.EnvVarSource{
  6584  				FieldRef: &core.ObjectFieldSelector{
  6585  					APIVersion: "v1",
  6586  					FieldPath:  "metadata.name",
  6587  				},
  6588  				SecretKeyRef: &core.SecretKeySelector{
  6589  					LocalObjectReference: core.LocalObjectReference{
  6590  						Name: "a-secret",
  6591  					},
  6592  					Key: "a-key",
  6593  				},
  6594  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6595  					LocalObjectReference: core.LocalObjectReference{
  6596  						Name: "some-config-map",
  6597  					},
  6598  					Key: "some-key",
  6599  				},
  6600  			},
  6601  		}},
  6602  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6603  	}, {
  6604  		name: "valueFrom.secretKeyRef.name invalid",
  6605  		envs: []core.EnvVar{{
  6606  			Name: "abc",
  6607  			ValueFrom: &core.EnvVarSource{
  6608  				SecretKeyRef: &core.SecretKeySelector{
  6609  					LocalObjectReference: core.LocalObjectReference{
  6610  						Name: "$%^&*#",
  6611  					},
  6612  					Key: "a-key",
  6613  				},
  6614  			},
  6615  		}},
  6616  	}, {
  6617  		name: "valueFrom.configMapKeyRef.name invalid",
  6618  		envs: []core.EnvVar{{
  6619  			Name: "abc",
  6620  			ValueFrom: &core.EnvVarSource{
  6621  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6622  					LocalObjectReference: core.LocalObjectReference{
  6623  						Name: "$%^&*#",
  6624  					},
  6625  					Key: "some-key",
  6626  				},
  6627  			},
  6628  		}},
  6629  	}, {
  6630  		name: "missing FieldPath on ObjectFieldSelector",
  6631  		envs: []core.EnvVar{{
  6632  			Name: "abc",
  6633  			ValueFrom: &core.EnvVarSource{
  6634  				FieldRef: &core.ObjectFieldSelector{
  6635  					APIVersion: "v1",
  6636  				},
  6637  			},
  6638  		}},
  6639  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
  6640  	}, {
  6641  		name: "missing APIVersion on ObjectFieldSelector",
  6642  		envs: []core.EnvVar{{
  6643  			Name: "abc",
  6644  			ValueFrom: &core.EnvVarSource{
  6645  				FieldRef: &core.ObjectFieldSelector{
  6646  					FieldPath: "metadata.name",
  6647  				},
  6648  			},
  6649  		}},
  6650  		expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
  6651  	}, {
  6652  		name: "invalid fieldPath",
  6653  		envs: []core.EnvVar{{
  6654  			Name: "abc",
  6655  			ValueFrom: &core.EnvVarSource{
  6656  				FieldRef: &core.ObjectFieldSelector{
  6657  					FieldPath:  "metadata.whoops",
  6658  					APIVersion: "v1",
  6659  				},
  6660  			},
  6661  		}},
  6662  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
  6663  	}, {
  6664  		name: "metadata.name with subscript",
  6665  		envs: []core.EnvVar{{
  6666  			Name: "labels",
  6667  			ValueFrom: &core.EnvVarSource{
  6668  				FieldRef: &core.ObjectFieldSelector{
  6669  					FieldPath:  "metadata.name['key']",
  6670  					APIVersion: "v1",
  6671  				},
  6672  			},
  6673  		}},
  6674  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
  6675  	}, {
  6676  		name: "metadata.labels without subscript",
  6677  		envs: []core.EnvVar{{
  6678  			Name: "labels",
  6679  			ValueFrom: &core.EnvVarSource{
  6680  				FieldRef: &core.ObjectFieldSelector{
  6681  					FieldPath:  "metadata.labels",
  6682  					APIVersion: "v1",
  6683  				},
  6684  			},
  6685  		}},
  6686  		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"`,
  6687  	}, {
  6688  		name: "metadata.annotations without subscript",
  6689  		envs: []core.EnvVar{{
  6690  			Name: "abc",
  6691  			ValueFrom: &core.EnvVarSource{
  6692  				FieldRef: &core.ObjectFieldSelector{
  6693  					FieldPath:  "metadata.annotations",
  6694  					APIVersion: "v1",
  6695  				},
  6696  			},
  6697  		}},
  6698  		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"`,
  6699  	}, {
  6700  		name: "metadata.annotations with invalid key",
  6701  		envs: []core.EnvVar{{
  6702  			Name: "abc",
  6703  			ValueFrom: &core.EnvVarSource{
  6704  				FieldRef: &core.ObjectFieldSelector{
  6705  					FieldPath:  "metadata.annotations['invalid~key']",
  6706  					APIVersion: "v1",
  6707  				},
  6708  			},
  6709  		}},
  6710  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
  6711  	}, {
  6712  		name: "metadata.labels with invalid key",
  6713  		envs: []core.EnvVar{{
  6714  			Name: "abc",
  6715  			ValueFrom: &core.EnvVarSource{
  6716  				FieldRef: &core.ObjectFieldSelector{
  6717  					FieldPath:  "metadata.labels['Www.k8s.io/test']",
  6718  					APIVersion: "v1",
  6719  				},
  6720  			},
  6721  		}},
  6722  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
  6723  	}, {
  6724  		name: "unsupported fieldPath",
  6725  		envs: []core.EnvVar{{
  6726  			Name: "abc",
  6727  			ValueFrom: &core.EnvVarSource{
  6728  				FieldRef: &core.ObjectFieldSelector{
  6729  					FieldPath:  "status.phase",
  6730  					APIVersion: "v1",
  6731  				},
  6732  			},
  6733  		}},
  6734  		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"`,
  6735  	},
  6736  	}
  6737  	for _, tc := range errorCases {
  6738  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6739  			t.Errorf("expected failure for %s", tc.name)
  6740  		} else {
  6741  			for i := range errs {
  6742  				str := errs[i].Error()
  6743  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6744  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6745  				}
  6746  			}
  6747  		}
  6748  	}
  6749  }
  6750  
  6751  func TestValidateEnvFrom(t *testing.T) {
  6752  	successCase := []core.EnvFromSource{{
  6753  		ConfigMapRef: &core.ConfigMapEnvSource{
  6754  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6755  		},
  6756  	}, {
  6757  		Prefix: "pre_",
  6758  		ConfigMapRef: &core.ConfigMapEnvSource{
  6759  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6760  		},
  6761  	}, {
  6762  		Prefix: "a.b",
  6763  		ConfigMapRef: &core.ConfigMapEnvSource{
  6764  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6765  		},
  6766  	}, {
  6767  		SecretRef: &core.SecretEnvSource{
  6768  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6769  		},
  6770  	}, {
  6771  		Prefix: "pre_",
  6772  		SecretRef: &core.SecretEnvSource{
  6773  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6774  		},
  6775  	}, {
  6776  		Prefix: "a.b",
  6777  		SecretRef: &core.SecretEnvSource{
  6778  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6779  		},
  6780  	},
  6781  	}
  6782  	if errs := ValidateEnvFrom(successCase, nil, PodValidationOptions{}); len(errs) != 0 {
  6783  		t.Errorf("expected success: %v", errs)
  6784  	}
  6785  
  6786  	updateSuccessCase := []core.EnvFromSource{{
  6787  		ConfigMapRef: &core.ConfigMapEnvSource{
  6788  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6789  		},
  6790  	}, {
  6791  		Prefix: "* +,-./0123456789",
  6792  		ConfigMapRef: &core.ConfigMapEnvSource{
  6793  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6794  		},
  6795  	}, {
  6796  		Prefix: ":;<>?@",
  6797  		ConfigMapRef: &core.ConfigMapEnvSource{
  6798  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6799  		},
  6800  	}, {
  6801  		SecretRef: &core.SecretEnvSource{
  6802  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6803  		},
  6804  	}, {
  6805  		Prefix: "abcdefghijklmn",
  6806  		SecretRef: &core.SecretEnvSource{
  6807  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6808  		},
  6809  	}, {
  6810  		Prefix: "[\\]^_`{}|~",
  6811  		SecretRef: &core.SecretEnvSource{
  6812  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6813  		},
  6814  	}}
  6815  
  6816  	if errs := ValidateEnvFrom(updateSuccessCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6817  		t.Errorf("expected success, got: %v", errs)
  6818  	}
  6819  
  6820  	updateErrorCase := []struct {
  6821  		name          string
  6822  		envs          []core.EnvFromSource
  6823  		expectedError string
  6824  	}{
  6825  		{
  6826  			name: "invalid name a",
  6827  			envs: []core.EnvFromSource{
  6828  				{
  6829  					Prefix: "!\"#$%&'()",
  6830  					SecretRef: &core.SecretEnvSource{
  6831  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6832  					},
  6833  				},
  6834  			},
  6835  			expectedError: `field[0].prefix: Invalid value: ` + "\"!\\\"#$%&'()\": " + envVarNameErrMsg,
  6836  		},
  6837  		{
  6838  			name: "invalid name b",
  6839  			envs: []core.EnvFromSource{
  6840  				{
  6841  					Prefix: "* +,-./0123456789",
  6842  					SecretRef: &core.SecretEnvSource{
  6843  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6844  					},
  6845  				},
  6846  			},
  6847  			expectedError: `field[0].prefix: Invalid value: ` + "\"* +,-./0123456789\": " + envVarNameErrMsg,
  6848  		},
  6849  		{
  6850  			name: "invalid name c",
  6851  			envs: []core.EnvFromSource{
  6852  				{
  6853  					Prefix: ":;<>?@",
  6854  					SecretRef: &core.SecretEnvSource{
  6855  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6856  					},
  6857  				},
  6858  			},
  6859  			expectedError: `field[0].prefix: Invalid value: ` + "\":;<>?@\": " + envVarNameErrMsg,
  6860  		},
  6861  		{
  6862  			name: "invalid name d",
  6863  			envs: []core.EnvFromSource{
  6864  				{
  6865  					Prefix: "[\\]^_{}|~",
  6866  					SecretRef: &core.SecretEnvSource{
  6867  						LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6868  					},
  6869  				},
  6870  			},
  6871  			expectedError: `field[0].prefix: Invalid value: ` + "\"[\\\\]^_{}|~\": " + envVarNameErrMsg,
  6872  		},
  6873  	}
  6874  
  6875  	for _, tc := range updateErrorCase {
  6876  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6877  			t.Errorf("expected failure for %s", tc.name)
  6878  		} else {
  6879  			for i := range errs {
  6880  				str := errs[i].Error()
  6881  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6882  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6883  				}
  6884  			}
  6885  		}
  6886  	}
  6887  
  6888  	errorCases := []struct {
  6889  		name          string
  6890  		envs          []core.EnvFromSource
  6891  		expectedError string
  6892  	}{{
  6893  		name: "zero-length name",
  6894  		envs: []core.EnvFromSource{{
  6895  			ConfigMapRef: &core.ConfigMapEnvSource{
  6896  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6897  		}},
  6898  		expectedError: "field[0].configMapRef.name: Required value",
  6899  	}, {
  6900  		name: "invalid name",
  6901  		envs: []core.EnvFromSource{{
  6902  			ConfigMapRef: &core.ConfigMapEnvSource{
  6903  				LocalObjectReference: core.LocalObjectReference{Name: "$"}},
  6904  		}},
  6905  		expectedError: "field[0].configMapRef.name: Invalid value",
  6906  	}, {
  6907  		name: "zero-length name",
  6908  		envs: []core.EnvFromSource{{
  6909  			SecretRef: &core.SecretEnvSource{
  6910  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6911  		}},
  6912  		expectedError: "field[0].secretRef.name: Required value",
  6913  	}, {
  6914  		name: "invalid name",
  6915  		envs: []core.EnvFromSource{{
  6916  			SecretRef: &core.SecretEnvSource{
  6917  				LocalObjectReference: core.LocalObjectReference{Name: "&"}},
  6918  		}},
  6919  		expectedError: "field[0].secretRef.name: Invalid value",
  6920  	}, {
  6921  		name: "no refs",
  6922  		envs: []core.EnvFromSource{
  6923  			{},
  6924  		},
  6925  		expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
  6926  	}, {
  6927  		name: "multiple refs",
  6928  		envs: []core.EnvFromSource{{
  6929  			SecretRef: &core.SecretEnvSource{
  6930  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6931  			ConfigMapRef: &core.ConfigMapEnvSource{
  6932  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6933  		}},
  6934  		expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
  6935  	}, {
  6936  		name: "invalid secret ref name",
  6937  		envs: []core.EnvFromSource{{
  6938  			SecretRef: &core.SecretEnvSource{
  6939  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6940  		}},
  6941  		expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6942  	}, {
  6943  		name: "invalid config ref name",
  6944  		envs: []core.EnvFromSource{{
  6945  			ConfigMapRef: &core.ConfigMapEnvSource{
  6946  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6947  		}},
  6948  		expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6949  	},
  6950  	}
  6951  	for _, tc := range errorCases {
  6952  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6953  			t.Errorf("expected failure for %s", tc.name)
  6954  		} else {
  6955  			for i := range errs {
  6956  				str := errs[i].Error()
  6957  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6958  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6959  				}
  6960  			}
  6961  		}
  6962  	}
  6963  }
  6964  
  6965  func TestRelaxedValidateEnvFrom(t *testing.T) {
  6966  	successCase := []core.EnvFromSource{{
  6967  		ConfigMapRef: &core.ConfigMapEnvSource{
  6968  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6969  		},
  6970  	}, {
  6971  		Prefix: "!\"#$%&'()",
  6972  		ConfigMapRef: &core.ConfigMapEnvSource{
  6973  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6974  		},
  6975  	}, {
  6976  		Prefix: "* +,-./0123456789",
  6977  		ConfigMapRef: &core.ConfigMapEnvSource{
  6978  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6979  		},
  6980  	}, {
  6981  		SecretRef: &core.SecretEnvSource{
  6982  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6983  		},
  6984  	}, {
  6985  		Prefix: ":;<>?@",
  6986  		SecretRef: &core.SecretEnvSource{
  6987  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6988  		},
  6989  	}, {
  6990  		Prefix: "[\\]^_`{}|~",
  6991  		SecretRef: &core.SecretEnvSource{
  6992  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6993  		},
  6994  	},
  6995  	}
  6996  	if errs := ValidateEnvFrom(successCase, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) != 0 {
  6997  		t.Errorf("expected success: %v", errs)
  6998  	}
  6999  
  7000  	errorCases := []struct {
  7001  		name          string
  7002  		envs          []core.EnvFromSource
  7003  		expectedError string
  7004  	}{
  7005  		{
  7006  			name: "zero-length name",
  7007  			envs: []core.EnvFromSource{{
  7008  				ConfigMapRef: &core.ConfigMapEnvSource{
  7009  					LocalObjectReference: core.LocalObjectReference{Name: ""}},
  7010  			}},
  7011  			expectedError: "field[0].configMapRef.name: Required value",
  7012  		},
  7013  		{
  7014  			name: "invalid prefix",
  7015  			envs: []core.EnvFromSource{{
  7016  				Prefix: "=abc",
  7017  				ConfigMapRef: &core.ConfigMapEnvSource{
  7018  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  7019  			}},
  7020  			expectedError: `field[0].prefix: Invalid value: "=abc": ` + relaxedEnvVarNameFmtErrMsg,
  7021  		},
  7022  		{
  7023  			name: "zero-length name",
  7024  			envs: []core.EnvFromSource{{
  7025  				SecretRef: &core.SecretEnvSource{
  7026  					LocalObjectReference: core.LocalObjectReference{Name: ""}},
  7027  			}},
  7028  			expectedError: "field[0].secretRef.name: Required value",
  7029  		}, {
  7030  			name: "invalid name",
  7031  			envs: []core.EnvFromSource{{
  7032  				SecretRef: &core.SecretEnvSource{
  7033  					LocalObjectReference: core.LocalObjectReference{Name: "&"}},
  7034  			}},
  7035  			expectedError: "field[0].secretRef.name: Invalid value",
  7036  		}, {
  7037  			name: "no refs",
  7038  			envs: []core.EnvFromSource{
  7039  				{},
  7040  			},
  7041  			expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
  7042  		}, {
  7043  			name: "multiple refs",
  7044  			envs: []core.EnvFromSource{{
  7045  				SecretRef: &core.SecretEnvSource{
  7046  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  7047  				ConfigMapRef: &core.ConfigMapEnvSource{
  7048  					LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  7049  			}},
  7050  			expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
  7051  		}, {
  7052  			name: "invalid secret ref name",
  7053  			envs: []core.EnvFromSource{{
  7054  				SecretRef: &core.SecretEnvSource{
  7055  					LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  7056  			}},
  7057  			expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  7058  		}, {
  7059  			name: "invalid config ref name",
  7060  			envs: []core.EnvFromSource{{
  7061  				ConfigMapRef: &core.ConfigMapEnvSource{
  7062  					LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  7063  			}},
  7064  			expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  7065  		},
  7066  	}
  7067  	for _, tc := range errorCases {
  7068  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field"), PodValidationOptions{AllowRelaxedEnvironmentVariableValidation: true}); len(errs) == 0 {
  7069  			t.Errorf("expected failure for %s", tc.name)
  7070  		} else {
  7071  			for i := range errs {
  7072  				str := errs[i].Error()
  7073  				if str != "" && !strings.Contains(str, tc.expectedError) {
  7074  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  7075  				}
  7076  			}
  7077  		}
  7078  	}
  7079  }
  7080  
  7081  func TestValidateVolumeMounts(t *testing.T) {
  7082  	volumes := []core.Volume{
  7083  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7084  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7085  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7086  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  7087  			Spec: core.PersistentVolumeClaimSpec{
  7088  				AccessModes: []core.PersistentVolumeAccessMode{
  7089  					core.ReadWriteOnce,
  7090  				},
  7091  				Resources: core.VolumeResourceRequirements{
  7092  					Requests: core.ResourceList{
  7093  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  7094  					},
  7095  				},
  7096  			},
  7097  		}}}},
  7098  	}
  7099  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7100  	if len(v1err) > 0 {
  7101  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7102  		return
  7103  	}
  7104  	container := core.Container{
  7105  		SecurityContext: nil,
  7106  	}
  7107  	propagation := core.MountPropagationBidirectional
  7108  
  7109  	successCase := []core.VolumeMount{
  7110  		{Name: "abc", MountPath: "/foo"},
  7111  		{Name: "123", MountPath: "/bar"},
  7112  		{Name: "abc-123", MountPath: "/baz"},
  7113  		{Name: "abc-123", MountPath: "/baa", SubPath: ""},
  7114  		{Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
  7115  		{Name: "abc-123", MountPath: "d:", SubPath: ""},
  7116  		{Name: "abc-123", MountPath: "F:", SubPath: ""},
  7117  		{Name: "abc-123", MountPath: "G:\\mount", SubPath: ""},
  7118  		{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
  7119  		{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
  7120  		{Name: "ephemeral", MountPath: "/foobar"},
  7121  		{Name: "123", MountPath: "/rro-nil", ReadOnly: true, RecursiveReadOnly: nil},
  7122  		{Name: "123", MountPath: "/rro-disabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
  7123  		{Name: "123", MountPath: "/rro-disabled-2", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)},
  7124  		{Name: "123", MountPath: "/rro-ifpossible", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)},
  7125  		{Name: "123", MountPath: "/rro-enabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)},
  7126  		{Name: "123", MountPath: "/rro-enabled-2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationNone)},
  7127  	}
  7128  	goodVolumeDevices := []core.VolumeDevice{
  7129  		{Name: "xyz", DevicePath: "/foofoo"},
  7130  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7131  	}
  7132  	if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
  7133  		t.Errorf("expected success: %v", errs)
  7134  	}
  7135  
  7136  	errorCases := map[string][]core.VolumeMount{
  7137  		"empty name":                             {{Name: "", MountPath: "/foo"}},
  7138  		"name not found":                         {{Name: "", MountPath: "/foo"}},
  7139  		"empty mountpath":                        {{Name: "abc", MountPath: ""}},
  7140  		"mountpath collision":                    {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
  7141  		"absolute subpath":                       {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
  7142  		"subpath in ..":                          {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
  7143  		"subpath contains ..":                    {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
  7144  		"subpath ends in ..":                     {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
  7145  		"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
  7146  		"name exists in volumeDevice":            {{Name: "xyz", MountPath: "/bar"}},
  7147  		"mountpath exists in volumeDevice":       {{Name: "uvw", MountPath: "/mnt/exists"}},
  7148  		"both exist in volumeDevice":             {{Name: "xyz", MountPath: "/mnt/exists"}},
  7149  		"rro but not ro":                         {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}},
  7150  		"rro with incompatible propagation":      {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}},
  7151  		"rro-if-possible but not ro":             {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}},
  7152  	}
  7153  	badVolumeDevice := []core.VolumeDevice{
  7154  		{Name: "xyz", DevicePath: "/mnt/exists"},
  7155  	}
  7156  
  7157  	for k, v := range errorCases {
  7158  		if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
  7159  			t.Errorf("expected failure for %s", k)
  7160  		}
  7161  	}
  7162  }
  7163  
  7164  func TestValidateSubpathMutuallyExclusive(t *testing.T) {
  7165  	volumes := []core.Volume{
  7166  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7167  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7168  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7169  	}
  7170  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7171  	if len(v1err) > 0 {
  7172  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7173  		return
  7174  	}
  7175  
  7176  	container := core.Container{
  7177  		SecurityContext: nil,
  7178  	}
  7179  
  7180  	goodVolumeDevices := []core.VolumeDevice{
  7181  		{Name: "xyz", DevicePath: "/foofoo"},
  7182  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7183  	}
  7184  
  7185  	cases := map[string]struct {
  7186  		mounts      []core.VolumeMount
  7187  		expectError bool
  7188  	}{
  7189  		"subpath and subpathexpr not specified": {
  7190  			[]core.VolumeMount{{
  7191  				Name:      "abc-123",
  7192  				MountPath: "/bab",
  7193  			}},
  7194  			false,
  7195  		},
  7196  		"subpath expr specified": {
  7197  			[]core.VolumeMount{{
  7198  				Name:        "abc-123",
  7199  				MountPath:   "/bab",
  7200  				SubPathExpr: "$(POD_NAME)",
  7201  			}},
  7202  			false,
  7203  		},
  7204  		"subpath specified": {
  7205  			[]core.VolumeMount{{
  7206  				Name:      "abc-123",
  7207  				MountPath: "/bab",
  7208  				SubPath:   "baz",
  7209  			}},
  7210  			false,
  7211  		},
  7212  		"subpath and subpathexpr specified": {
  7213  			[]core.VolumeMount{{
  7214  				Name:        "abc-123",
  7215  				MountPath:   "/bab",
  7216  				SubPath:     "baz",
  7217  				SubPathExpr: "$(POD_NAME)",
  7218  			}},
  7219  			true,
  7220  		},
  7221  	}
  7222  
  7223  	for name, test := range cases {
  7224  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  7225  
  7226  		if len(errs) != 0 && !test.expectError {
  7227  			t.Errorf("test %v failed: %+v", name, errs)
  7228  		}
  7229  
  7230  		if len(errs) == 0 && test.expectError {
  7231  			t.Errorf("test %v failed, expected error", name)
  7232  		}
  7233  	}
  7234  }
  7235  
  7236  func TestValidateDisabledSubpathExpr(t *testing.T) {
  7237  
  7238  	volumes := []core.Volume{
  7239  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7240  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7241  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7242  	}
  7243  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7244  	if len(v1err) > 0 {
  7245  		t.Errorf("Invalid test volume - expected success %v", v1err)
  7246  		return
  7247  	}
  7248  
  7249  	container := core.Container{
  7250  		SecurityContext: nil,
  7251  	}
  7252  
  7253  	goodVolumeDevices := []core.VolumeDevice{
  7254  		{Name: "xyz", DevicePath: "/foofoo"},
  7255  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  7256  	}
  7257  
  7258  	cases := map[string]struct {
  7259  		mounts      []core.VolumeMount
  7260  		expectError bool
  7261  	}{
  7262  		"subpath expr not specified": {
  7263  			[]core.VolumeMount{{
  7264  				Name:      "abc-123",
  7265  				MountPath: "/bab",
  7266  			}},
  7267  			false,
  7268  		},
  7269  		"subpath expr specified": {
  7270  			[]core.VolumeMount{{
  7271  				Name:        "abc-123",
  7272  				MountPath:   "/bab",
  7273  				SubPathExpr: "$(POD_NAME)",
  7274  			}},
  7275  			false,
  7276  		},
  7277  	}
  7278  
  7279  	for name, test := range cases {
  7280  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  7281  
  7282  		if len(errs) != 0 && !test.expectError {
  7283  			t.Errorf("test %v failed: %+v", name, errs)
  7284  		}
  7285  
  7286  		if len(errs) == 0 && test.expectError {
  7287  			t.Errorf("test %v failed, expected error", name)
  7288  		}
  7289  	}
  7290  }
  7291  
  7292  func TestValidateMountPropagation(t *testing.T) {
  7293  	bTrue := true
  7294  	bFalse := false
  7295  	privilegedContainer := &core.Container{
  7296  		SecurityContext: &core.SecurityContext{
  7297  			Privileged: &bTrue,
  7298  		},
  7299  	}
  7300  	nonPrivilegedContainer := &core.Container{
  7301  		SecurityContext: &core.SecurityContext{
  7302  			Privileged: &bFalse,
  7303  		},
  7304  	}
  7305  	defaultContainer := &core.Container{}
  7306  
  7307  	propagationBidirectional := core.MountPropagationBidirectional
  7308  	propagationHostToContainer := core.MountPropagationHostToContainer
  7309  	propagationNone := core.MountPropagationNone
  7310  	propagationInvalid := core.MountPropagationMode("invalid")
  7311  
  7312  	tests := []struct {
  7313  		mount       core.VolumeMount
  7314  		container   *core.Container
  7315  		expectError bool
  7316  	}{{
  7317  		// implicitly non-privileged container + no propagation
  7318  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7319  		defaultContainer,
  7320  		false,
  7321  	}, {
  7322  		// implicitly non-privileged container + HostToContainer
  7323  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7324  		defaultContainer,
  7325  		false,
  7326  	}, {
  7327  		// non-privileged container + None
  7328  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone},
  7329  		defaultContainer,
  7330  		false,
  7331  	}, {
  7332  		// error: implicitly non-privileged container + Bidirectional
  7333  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7334  		defaultContainer,
  7335  		true,
  7336  	}, {
  7337  		// explicitly non-privileged container + no propagation
  7338  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7339  		nonPrivilegedContainer,
  7340  		false,
  7341  	}, {
  7342  		// explicitly non-privileged container + HostToContainer
  7343  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7344  		nonPrivilegedContainer,
  7345  		false,
  7346  	}, {
  7347  		// explicitly non-privileged container + HostToContainer
  7348  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7349  		nonPrivilegedContainer,
  7350  		true,
  7351  	}, {
  7352  		// privileged container + no propagation
  7353  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  7354  		privilegedContainer,
  7355  		false,
  7356  	}, {
  7357  		// privileged container + HostToContainer
  7358  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  7359  		privilegedContainer,
  7360  		false,
  7361  	}, {
  7362  		// privileged container + Bidirectional
  7363  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7364  		privilegedContainer,
  7365  		false,
  7366  	}, {
  7367  		// error: privileged container + invalid mount propagation
  7368  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
  7369  		privilegedContainer,
  7370  		true,
  7371  	}, {
  7372  		// no container + Bidirectional
  7373  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  7374  		nil,
  7375  		false,
  7376  	},
  7377  	}
  7378  
  7379  	volumes := []core.Volume{
  7380  		{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7381  	}
  7382  	vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7383  	if len(v2err) > 0 {
  7384  		t.Errorf("Invalid test volume - expected success %v", v2err)
  7385  		return
  7386  	}
  7387  	for i, test := range tests {
  7388  		errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
  7389  		if test.expectError && len(errs) == 0 {
  7390  			t.Errorf("test %d expected error, got none", i)
  7391  		}
  7392  		if !test.expectError && len(errs) != 0 {
  7393  			t.Errorf("test %d expected success, got error: %v", i, errs)
  7394  		}
  7395  	}
  7396  }
  7397  
  7398  func TestAlphaValidateVolumeDevices(t *testing.T) {
  7399  	volumes := []core.Volume{
  7400  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  7401  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  7402  		{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  7403  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  7404  			Spec: core.PersistentVolumeClaimSpec{
  7405  				AccessModes: []core.PersistentVolumeAccessMode{
  7406  					core.ReadWriteOnce,
  7407  				},
  7408  				Resources: core.VolumeResourceRequirements{
  7409  					Requests: core.ResourceList{
  7410  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  7411  					},
  7412  				},
  7413  			},
  7414  		}}}},
  7415  	}
  7416  
  7417  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  7418  	if len(v1err) > 0 {
  7419  		t.Errorf("Invalid test volumes - expected success %v", v1err)
  7420  		return
  7421  	}
  7422  
  7423  	successCase := []core.VolumeDevice{
  7424  		{Name: "abc", DevicePath: "/foo"},
  7425  		{Name: "abc-123", DevicePath: "/usr/share/test"},
  7426  		{Name: "ephemeral", DevicePath: "/disk"},
  7427  	}
  7428  	goodVolumeMounts := []core.VolumeMount{
  7429  		{Name: "xyz", MountPath: "/foofoo"},
  7430  		{Name: "ghi", MountPath: "/foo/usr/share/test"},
  7431  	}
  7432  
  7433  	errorCases := map[string][]core.VolumeDevice{
  7434  		"empty name":                    {{Name: "", DevicePath: "/foo"}},
  7435  		"duplicate name":                {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
  7436  		"name not found":                {{Name: "not-found", DevicePath: "/usr/share/test"}},
  7437  		"name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
  7438  		"empty devicepath":              {{Name: "abc", DevicePath: ""}},
  7439  		"relative devicepath":           {{Name: "abc-123", DevicePath: "baz"}},
  7440  		"duplicate devicepath":          {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
  7441  		"no backsteps":                  {{Name: "def", DevicePath: "/baz/../"}},
  7442  		"name exists in volumemounts":   {{Name: "abc", DevicePath: "/baz/../"}},
  7443  		"path exists in volumemounts":   {{Name: "xyz", DevicePath: "/this/path/exists"}},
  7444  		"both exist in volumemounts":    {{Name: "abc", DevicePath: "/this/path/exists"}},
  7445  	}
  7446  	badVolumeMounts := []core.VolumeMount{
  7447  		{Name: "abc", MountPath: "/foo"},
  7448  		{Name: "abc-123", MountPath: "/this/path/exists"},
  7449  	}
  7450  
  7451  	// Success Cases:
  7452  	// Validate normal success cases - only PVC volumeSource or generic ephemeral volume
  7453  	if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
  7454  		t.Errorf("expected success: %v", errs)
  7455  	}
  7456  
  7457  	// Error Cases:
  7458  	// Validate normal error cases - only PVC volumeSource
  7459  	for k, v := range errorCases {
  7460  		if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
  7461  			t.Errorf("expected failure for %s", k)
  7462  		}
  7463  	}
  7464  }
  7465  
  7466  func TestValidateProbe(t *testing.T) {
  7467  	handler := core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}
  7468  	// These fields must be positive.
  7469  	positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"}
  7470  	successCases := []*core.Probe{nil}
  7471  	for _, field := range positiveFields {
  7472  		probe := &core.Probe{ProbeHandler: handler}
  7473  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10)
  7474  		successCases = append(successCases, probe)
  7475  	}
  7476  
  7477  	for _, p := range successCases {
  7478  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  7479  			t.Errorf("expected success: %v", errs)
  7480  		}
  7481  	}
  7482  
  7483  	errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}}
  7484  	for _, field := range positiveFields {
  7485  		probe := &core.Probe{ProbeHandler: handler}
  7486  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10)
  7487  		errorCases = append(errorCases, probe)
  7488  	}
  7489  	for _, p := range errorCases {
  7490  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  7491  			t.Errorf("expected failure for %v", p)
  7492  		}
  7493  	}
  7494  }
  7495  
  7496  func Test_validateProbe(t *testing.T) {
  7497  	fldPath := field.NewPath("test")
  7498  	type args struct {
  7499  		probe   *core.Probe
  7500  		fldPath *field.Path
  7501  	}
  7502  	tests := []struct {
  7503  		name string
  7504  		args args
  7505  		want field.ErrorList
  7506  	}{{
  7507  		args: args{
  7508  			probe:   &core.Probe{},
  7509  			fldPath: fldPath,
  7510  		},
  7511  		want: field.ErrorList{field.Required(fldPath, "must specify a handler type")},
  7512  	}, {
  7513  		args: args{
  7514  			probe: &core.Probe{
  7515  				ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7516  			},
  7517  			fldPath: fldPath,
  7518  		},
  7519  		want: field.ErrorList{},
  7520  	}, {
  7521  		args: args{
  7522  			probe: &core.Probe{
  7523  				ProbeHandler:        core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7524  				InitialDelaySeconds: -1,
  7525  			},
  7526  			fldPath: fldPath,
  7527  		},
  7528  		want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")},
  7529  	}, {
  7530  		args: args{
  7531  			probe: &core.Probe{
  7532  				ProbeHandler:   core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7533  				TimeoutSeconds: -1,
  7534  			},
  7535  			fldPath: fldPath,
  7536  		},
  7537  		want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")},
  7538  	}, {
  7539  		args: args{
  7540  			probe: &core.Probe{
  7541  				ProbeHandler:  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7542  				PeriodSeconds: -1,
  7543  			},
  7544  			fldPath: fldPath,
  7545  		},
  7546  		want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")},
  7547  	}, {
  7548  		args: args{
  7549  			probe: &core.Probe{
  7550  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7551  				SuccessThreshold: -1,
  7552  			},
  7553  			fldPath: fldPath,
  7554  		},
  7555  		want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")},
  7556  	}, {
  7557  		args: args{
  7558  			probe: &core.Probe{
  7559  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7560  				FailureThreshold: -1,
  7561  			},
  7562  			fldPath: fldPath,
  7563  		},
  7564  		want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")},
  7565  	}, {
  7566  		args: args{
  7567  			probe: &core.Probe{
  7568  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7569  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  7570  			},
  7571  			fldPath: fldPath,
  7572  		},
  7573  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")},
  7574  	}, {
  7575  		args: args{
  7576  			probe: &core.Probe{
  7577  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7578  				TerminationGracePeriodSeconds: utilpointer.Int64(0),
  7579  			},
  7580  			fldPath: fldPath,
  7581  		},
  7582  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")},
  7583  	}, {
  7584  		args: args{
  7585  			probe: &core.Probe{
  7586  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7587  				TerminationGracePeriodSeconds: utilpointer.Int64(1),
  7588  			},
  7589  			fldPath: fldPath,
  7590  		},
  7591  		want: field.ErrorList{},
  7592  	},
  7593  	}
  7594  	for _, tt := range tests {
  7595  		t.Run(tt.name, func(t *testing.T) {
  7596  			got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath)
  7597  			if len(got) != len(tt.want) {
  7598  				t.Errorf("validateProbe() = %v, want %v", got, tt.want)
  7599  				return
  7600  			}
  7601  			for i := range got {
  7602  				if got[i].Type != tt.want[i].Type ||
  7603  					got[i].Field != tt.want[i].Field {
  7604  					t.Errorf("validateProbe()[%d] = %v, want %v", i, got[i], tt.want[i])
  7605  				}
  7606  			}
  7607  		})
  7608  	}
  7609  }
  7610  
  7611  func TestValidateHandler(t *testing.T) {
  7612  	successCases := []core.ProbeHandler{
  7613  		{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7614  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt32(1), Host: "", Scheme: "HTTP"}},
  7615  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65535), Host: "host", Scheme: "HTTP"}},
  7616  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
  7617  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}},
  7618  		{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"}}}},
  7619  	}
  7620  	for _, h := range successCases {
  7621  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  7622  			t.Errorf("expected success: %v", errs)
  7623  		}
  7624  	}
  7625  
  7626  	errorCases := []core.ProbeHandler{
  7627  		{},
  7628  		{Exec: &core.ExecAction{Command: []string{}}},
  7629  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt32(0), Host: ""}},
  7630  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65536), Host: "host"}},
  7631  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
  7632  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}},
  7633  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
  7634  	}
  7635  	for _, h := range errorCases {
  7636  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  7637  			t.Errorf("expected failure for %#v", h)
  7638  		}
  7639  	}
  7640  }
  7641  
  7642  func TestValidatePullPolicy(t *testing.T) {
  7643  	type T struct {
  7644  		Container      core.Container
  7645  		ExpectedPolicy core.PullPolicy
  7646  	}
  7647  	testCases := map[string]T{
  7648  		"NotPresent1": {
  7649  			core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7650  			core.PullIfNotPresent,
  7651  		},
  7652  		"NotPresent2": {
  7653  			core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7654  			core.PullIfNotPresent,
  7655  		},
  7656  		"Always1": {
  7657  			core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"},
  7658  			core.PullAlways,
  7659  		},
  7660  		"Always2": {
  7661  			core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"},
  7662  			core.PullAlways,
  7663  		},
  7664  		"Never1": {
  7665  			core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"},
  7666  			core.PullNever,
  7667  		},
  7668  		"Never2": {
  7669  			core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
  7670  			core.PullNever,
  7671  		},
  7672  	}
  7673  	for k, v := range testCases {
  7674  		ctr := &v.Container
  7675  		errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field"))
  7676  		if len(errs) != 0 {
  7677  			t.Errorf("case[%s] expected success, got %#v", k, errs)
  7678  		}
  7679  		if ctr.ImagePullPolicy != v.ExpectedPolicy {
  7680  			t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
  7681  		}
  7682  	}
  7683  }
  7684  
  7685  func TestValidateResizePolicy(t *testing.T) {
  7686  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
  7687  	tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
  7688  	tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer))
  7689  	type T struct {
  7690  		PolicyList       []core.ContainerResizePolicy
  7691  		ExpectError      bool
  7692  		Errors           field.ErrorList
  7693  		PodRestartPolicy core.RestartPolicy
  7694  	}
  7695  
  7696  	testCases := map[string]T{
  7697  		"ValidCPUandMemoryPolicies": {
  7698  			PolicyList: []core.ContainerResizePolicy{
  7699  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7700  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7701  			},
  7702  			ExpectError:      false,
  7703  			Errors:           nil,
  7704  			PodRestartPolicy: "Always",
  7705  		},
  7706  		"ValidCPUPolicy": {
  7707  			PolicyList: []core.ContainerResizePolicy{
  7708  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7709  			},
  7710  			ExpectError:      false,
  7711  			Errors:           nil,
  7712  			PodRestartPolicy: "Always",
  7713  		},
  7714  		"ValidMemoryPolicy": {
  7715  			PolicyList: []core.ContainerResizePolicy{
  7716  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7717  			},
  7718  			ExpectError:      false,
  7719  			Errors:           nil,
  7720  			PodRestartPolicy: "Always",
  7721  		},
  7722  		"NoPolicy": {
  7723  			PolicyList:       []core.ContainerResizePolicy{},
  7724  			ExpectError:      false,
  7725  			Errors:           nil,
  7726  			PodRestartPolicy: "Always",
  7727  		},
  7728  		"ValidCPUandInvalidMemoryPolicy": {
  7729  			PolicyList: []core.ContainerResizePolicy{
  7730  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7731  				{ResourceName: "memory", RestartPolicy: "Restarrrt"},
  7732  			},
  7733  			ExpectError:      true,
  7734  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("Restarrrt"), tSupportedResizePolicies.List())},
  7735  			PodRestartPolicy: "Always",
  7736  		},
  7737  		"ValidMemoryandInvalidCPUPolicy": {
  7738  			PolicyList: []core.ContainerResizePolicy{
  7739  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequirrred"},
  7740  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7741  			},
  7742  			ExpectError:      true,
  7743  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartNotRequirrred"), tSupportedResizePolicies.List())},
  7744  			PodRestartPolicy: "Always",
  7745  		},
  7746  		"InvalidResourceNameValidPolicy": {
  7747  			PolicyList: []core.ContainerResizePolicy{
  7748  				{ResourceName: "cpuuu", RestartPolicy: "NotRequired"},
  7749  			},
  7750  			ExpectError:      true,
  7751  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceName("cpuuu"), tSupportedResizeResources.List())},
  7752  			PodRestartPolicy: "Always",
  7753  		},
  7754  		"ValidResourceNameMissingPolicy": {
  7755  			PolicyList: []core.ContainerResizePolicy{
  7756  				{ResourceName: "memory", RestartPolicy: ""},
  7757  			},
  7758  			ExpectError:      true,
  7759  			Errors:           field.ErrorList{field.Required(field.NewPath("field"), "")},
  7760  			PodRestartPolicy: "Always",
  7761  		},
  7762  		"RepeatedPolicies": {
  7763  			PolicyList: []core.ContainerResizePolicy{
  7764  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7765  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7766  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7767  			},
  7768  			ExpectError:      true,
  7769  			Errors:           field.ErrorList{field.Duplicate(field.NewPath("field").Index(2), core.ResourceCPU)},
  7770  			PodRestartPolicy: "Always",
  7771  		},
  7772  		"InvalidCPUPolicyWithPodRestartPolicy": {
  7773  			PolicyList: []core.ContainerResizePolicy{
  7774  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7775  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7776  			},
  7777  			ExpectError:      true,
  7778  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7779  			PodRestartPolicy: "Never",
  7780  		},
  7781  		"InvalidMemoryPolicyWithPodRestartPolicy": {
  7782  			PolicyList: []core.ContainerResizePolicy{
  7783  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7784  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7785  			},
  7786  			ExpectError:      true,
  7787  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7788  			PodRestartPolicy: "Never",
  7789  		},
  7790  		"InvalidMemoryCPUPolicyWithPodRestartPolicy": {
  7791  			PolicyList: []core.ContainerResizePolicy{
  7792  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7793  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7794  			},
  7795  			ExpectError:      true,
  7796  			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'")},
  7797  			PodRestartPolicy: "Never",
  7798  		},
  7799  		"ValidMemoryCPUPolicyWithPodRestartPolicy": {
  7800  			PolicyList: []core.ContainerResizePolicy{
  7801  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7802  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7803  			},
  7804  			ExpectError:      false,
  7805  			Errors:           nil,
  7806  			PodRestartPolicy: "Never",
  7807  		},
  7808  	}
  7809  	for k, v := range testCases {
  7810  		errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
  7811  		if !v.ExpectError && len(errs) > 0 {
  7812  			t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
  7813  		}
  7814  		if v.ExpectError {
  7815  			if len(errs) == 0 {
  7816  				t.Errorf("Testcase %s - expected error, got success", k)
  7817  			}
  7818  			delta := cmp.Diff(errs, v.Errors)
  7819  			if delta != "" {
  7820  				t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
  7821  			}
  7822  		}
  7823  	}
  7824  }
  7825  
  7826  func getResourceLimits(cpu, memory string) core.ResourceList {
  7827  	res := core.ResourceList{}
  7828  	res[core.ResourceCPU] = resource.MustParse(cpu)
  7829  	res[core.ResourceMemory] = resource.MustParse(memory)
  7830  	return res
  7831  }
  7832  
  7833  func getResources(cpu, memory, storage string) core.ResourceList {
  7834  	res := core.ResourceList{}
  7835  	if cpu != "" {
  7836  		res[core.ResourceCPU] = resource.MustParse(cpu)
  7837  	}
  7838  	if memory != "" {
  7839  		res[core.ResourceMemory] = resource.MustParse(memory)
  7840  	}
  7841  	if storage != "" {
  7842  		res[core.ResourceEphemeralStorage] = resource.MustParse(storage)
  7843  	}
  7844  	return res
  7845  }
  7846  
  7847  func TestValidateEphemeralContainers(t *testing.T) {
  7848  	containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7849  	initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7850  	vols := map[string]core.VolumeSource{
  7851  		"blk": {PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}},
  7852  		"vol": {EmptyDir: &core.EmptyDirVolumeSource{}},
  7853  	}
  7854  
  7855  	// Success Cases
  7856  	for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
  7857  		"Empty Ephemeral Containers": {},
  7858  		"Single Container": {
  7859  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7860  		},
  7861  		"Multiple Containers": {
  7862  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7863  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7864  		},
  7865  		"Single Container with Target": {{
  7866  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7867  			TargetContainerName:      "ctr",
  7868  		}},
  7869  		"All allowed fields": {{
  7870  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7871  
  7872  				Name:       "debug",
  7873  				Image:      "image",
  7874  				Command:    []string{"bash"},
  7875  				Args:       []string{"bash"},
  7876  				WorkingDir: "/",
  7877  				EnvFrom: []core.EnvFromSource{{
  7878  					ConfigMapRef: &core.ConfigMapEnvSource{
  7879  						LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
  7880  						Optional:             &[]bool{true}[0],
  7881  					},
  7882  				}},
  7883  				Env: []core.EnvVar{
  7884  					{Name: "TEST", Value: "TRUE"},
  7885  				},
  7886  				VolumeMounts: []core.VolumeMount{
  7887  					{Name: "vol", MountPath: "/vol"},
  7888  				},
  7889  				VolumeDevices: []core.VolumeDevice{
  7890  					{Name: "blk", DevicePath: "/dev/block"},
  7891  				},
  7892  				TerminationMessagePath:   "/dev/termination-log",
  7893  				TerminationMessagePolicy: "File",
  7894  				ImagePullPolicy:          "IfNotPresent",
  7895  				SecurityContext: &core.SecurityContext{
  7896  					Capabilities: &core.Capabilities{
  7897  						Add: []core.Capability{"SYS_ADMIN"},
  7898  					},
  7899  				},
  7900  				Stdin:     true,
  7901  				StdinOnce: true,
  7902  				TTY:       true,
  7903  			},
  7904  		}},
  7905  	} {
  7906  		var PodRestartPolicy core.RestartPolicy
  7907  		PodRestartPolicy = "Never"
  7908  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7909  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7910  		}
  7911  
  7912  		PodRestartPolicy = "Always"
  7913  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7914  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7915  		}
  7916  
  7917  		PodRestartPolicy = "OnFailure"
  7918  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  7919  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7920  		}
  7921  	}
  7922  
  7923  	// Failure Cases
  7924  	tcs := []struct {
  7925  		title, line         string
  7926  		ephemeralContainers []core.EphemeralContainer
  7927  		expectedErrors      field.ErrorList
  7928  	}{{
  7929  		"Name Collision with Container.Containers",
  7930  		line(),
  7931  		[]core.EphemeralContainer{
  7932  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7933  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7934  		},
  7935  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7936  	}, {
  7937  		"Name Collision with Container.InitContainers",
  7938  		line(),
  7939  		[]core.EphemeralContainer{
  7940  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7941  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7942  		},
  7943  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7944  	}, {
  7945  		"Name Collision with EphemeralContainers",
  7946  		line(),
  7947  		[]core.EphemeralContainer{
  7948  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7949  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7950  		},
  7951  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}},
  7952  	}, {
  7953  		"empty Container",
  7954  		line(),
  7955  		[]core.EphemeralContainer{
  7956  			{EphemeralContainerCommon: core.EphemeralContainerCommon{}},
  7957  		},
  7958  		field.ErrorList{
  7959  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"},
  7960  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"},
  7961  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"},
  7962  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"},
  7963  		},
  7964  	}, {
  7965  		"empty Container Name",
  7966  		line(),
  7967  		[]core.EphemeralContainer{
  7968  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7969  		},
  7970  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}},
  7971  	}, {
  7972  		"whitespace padded image name",
  7973  		line(),
  7974  		[]core.EphemeralContainer{
  7975  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7976  		},
  7977  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}},
  7978  	}, {
  7979  		"invalid image pull policy",
  7980  		line(),
  7981  		[]core.EphemeralContainer{
  7982  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}},
  7983  		},
  7984  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}},
  7985  	}, {
  7986  		"TargetContainerName doesn't exist",
  7987  		line(),
  7988  		[]core.EphemeralContainer{{
  7989  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7990  			TargetContainerName:      "bogus",
  7991  		}},
  7992  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}},
  7993  	}, {
  7994  		"Targets an ephemeral container",
  7995  		line(),
  7996  		[]core.EphemeralContainer{{
  7997  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7998  		}, {
  7999  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8000  			TargetContainerName:      "debug",
  8001  		}},
  8002  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}},
  8003  	}, {
  8004  		"Container uses disallowed field: Lifecycle",
  8005  		line(),
  8006  		[]core.EphemeralContainer{{
  8007  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8008  				Name:                     "debug",
  8009  				Image:                    "image",
  8010  				ImagePullPolicy:          "IfNotPresent",
  8011  				TerminationMessagePolicy: "File",
  8012  				Lifecycle: &core.Lifecycle{
  8013  					PreStop: &core.LifecycleHandler{
  8014  						Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  8015  					},
  8016  				},
  8017  			},
  8018  		}},
  8019  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  8020  	}, {
  8021  		"Container uses disallowed field: LivenessProbe",
  8022  		line(),
  8023  		[]core.EphemeralContainer{{
  8024  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8025  				Name:                     "debug",
  8026  				Image:                    "image",
  8027  				ImagePullPolicy:          "IfNotPresent",
  8028  				TerminationMessagePolicy: "File",
  8029  				LivenessProbe: &core.Probe{
  8030  					ProbeHandler: core.ProbeHandler{
  8031  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8032  					},
  8033  					SuccessThreshold: 1,
  8034  				},
  8035  			},
  8036  		}},
  8037  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}},
  8038  	}, {
  8039  		"Container uses disallowed field: Ports",
  8040  		line(),
  8041  		[]core.EphemeralContainer{{
  8042  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8043  				Name:                     "debug",
  8044  				Image:                    "image",
  8045  				ImagePullPolicy:          "IfNotPresent",
  8046  				TerminationMessagePolicy: "File",
  8047  				Ports: []core.ContainerPort{
  8048  					{Protocol: "TCP", ContainerPort: 80},
  8049  				},
  8050  			},
  8051  		}},
  8052  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}},
  8053  	}, {
  8054  		"Container uses disallowed field: ReadinessProbe",
  8055  		line(),
  8056  		[]core.EphemeralContainer{{
  8057  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8058  				Name:                     "debug",
  8059  				Image:                    "image",
  8060  				ImagePullPolicy:          "IfNotPresent",
  8061  				TerminationMessagePolicy: "File",
  8062  				ReadinessProbe: &core.Probe{
  8063  					ProbeHandler: core.ProbeHandler{
  8064  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8065  					},
  8066  				},
  8067  			},
  8068  		}},
  8069  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}},
  8070  	}, {
  8071  		"Container uses disallowed field: StartupProbe",
  8072  		line(),
  8073  		[]core.EphemeralContainer{{
  8074  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8075  				Name:                     "debug",
  8076  				Image:                    "image",
  8077  				ImagePullPolicy:          "IfNotPresent",
  8078  				TerminationMessagePolicy: "File",
  8079  				StartupProbe: &core.Probe{
  8080  					ProbeHandler: core.ProbeHandler{
  8081  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8082  					},
  8083  					SuccessThreshold: 1,
  8084  				},
  8085  			},
  8086  		}},
  8087  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}},
  8088  	}, {
  8089  		"Container uses disallowed field: Resources",
  8090  		line(),
  8091  		[]core.EphemeralContainer{{
  8092  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8093  				Name:                     "debug",
  8094  				Image:                    "image",
  8095  				ImagePullPolicy:          "IfNotPresent",
  8096  				TerminationMessagePolicy: "File",
  8097  				Resources: core.ResourceRequirements{
  8098  					Limits: core.ResourceList{
  8099  						core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  8100  					},
  8101  				},
  8102  			},
  8103  		}},
  8104  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}},
  8105  	}, {
  8106  		"Container uses disallowed field: VolumeMount.SubPath",
  8107  		line(),
  8108  		[]core.EphemeralContainer{{
  8109  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8110  				Name:                     "debug",
  8111  				Image:                    "image",
  8112  				ImagePullPolicy:          "IfNotPresent",
  8113  				TerminationMessagePolicy: "File",
  8114  				VolumeMounts: []core.VolumeMount{
  8115  					{Name: "vol", MountPath: "/vol"},
  8116  					{Name: "vol", MountPath: "/volsub", SubPath: "foo"},
  8117  				},
  8118  			},
  8119  		}},
  8120  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}},
  8121  	}, {
  8122  		"Container uses disallowed field: VolumeMount.SubPathExpr",
  8123  		line(),
  8124  		[]core.EphemeralContainer{{
  8125  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8126  				Name:                     "debug",
  8127  				Image:                    "image",
  8128  				ImagePullPolicy:          "IfNotPresent",
  8129  				TerminationMessagePolicy: "File",
  8130  				VolumeMounts: []core.VolumeMount{
  8131  					{Name: "vol", MountPath: "/vol"},
  8132  					{Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"},
  8133  				},
  8134  			},
  8135  		}},
  8136  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}},
  8137  	}, {
  8138  		"Disallowed field with other errors should only return a single Forbidden",
  8139  		line(),
  8140  		[]core.EphemeralContainer{{
  8141  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8142  				Name:                     "debug",
  8143  				Image:                    "image",
  8144  				ImagePullPolicy:          "IfNotPresent",
  8145  				TerminationMessagePolicy: "File",
  8146  				Lifecycle: &core.Lifecycle{
  8147  					PreStop: &core.LifecycleHandler{
  8148  						Exec: &core.ExecAction{Command: []string{}},
  8149  					},
  8150  				},
  8151  			},
  8152  		}},
  8153  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  8154  	}, {
  8155  		"Container uses disallowed field: ResizePolicy",
  8156  		line(),
  8157  		[]core.EphemeralContainer{{
  8158  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8159  				Name:                     "resources-resize-policy",
  8160  				Image:                    "image",
  8161  				ImagePullPolicy:          "IfNotPresent",
  8162  				TerminationMessagePolicy: "File",
  8163  				ResizePolicy: []core.ContainerResizePolicy{
  8164  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8165  				},
  8166  			},
  8167  		}},
  8168  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}},
  8169  	}, {
  8170  		"Forbidden RestartPolicy: Always",
  8171  		line(),
  8172  		[]core.EphemeralContainer{{
  8173  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8174  				Name:                     "foo",
  8175  				Image:                    "image",
  8176  				ImagePullPolicy:          "IfNotPresent",
  8177  				TerminationMessagePolicy: "File",
  8178  				RestartPolicy:            &containerRestartPolicyAlways,
  8179  			},
  8180  		}},
  8181  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8182  	}, {
  8183  		"Forbidden RestartPolicy: OnFailure",
  8184  		line(),
  8185  		[]core.EphemeralContainer{{
  8186  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8187  				Name:                     "foo",
  8188  				Image:                    "image",
  8189  				ImagePullPolicy:          "IfNotPresent",
  8190  				TerminationMessagePolicy: "File",
  8191  				RestartPolicy:            &containerRestartPolicyOnFailure,
  8192  			},
  8193  		}},
  8194  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8195  	}, {
  8196  		"Forbidden RestartPolicy: Never",
  8197  		line(),
  8198  		[]core.EphemeralContainer{{
  8199  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8200  				Name:                     "foo",
  8201  				Image:                    "image",
  8202  				ImagePullPolicy:          "IfNotPresent",
  8203  				TerminationMessagePolicy: "File",
  8204  				RestartPolicy:            &containerRestartPolicyNever,
  8205  			},
  8206  		}},
  8207  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8208  	}, {
  8209  		"Forbidden RestartPolicy: invalid",
  8210  		line(),
  8211  		[]core.EphemeralContainer{{
  8212  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8213  				Name:                     "foo",
  8214  				Image:                    "image",
  8215  				ImagePullPolicy:          "IfNotPresent",
  8216  				TerminationMessagePolicy: "File",
  8217  				RestartPolicy:            &containerRestartPolicyInvalid,
  8218  			},
  8219  		}},
  8220  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8221  	}, {
  8222  		"Forbidden RestartPolicy: empty",
  8223  		line(),
  8224  		[]core.EphemeralContainer{{
  8225  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  8226  				Name:                     "foo",
  8227  				Image:                    "image",
  8228  				ImagePullPolicy:          "IfNotPresent",
  8229  				TerminationMessagePolicy: "File",
  8230  				RestartPolicy:            &containerRestartPolicyEmpty,
  8231  			},
  8232  		}},
  8233  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  8234  	},
  8235  	}
  8236  
  8237  	var PodRestartPolicy core.RestartPolicy
  8238  
  8239  	for _, tc := range tcs {
  8240  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  8241  
  8242  			PodRestartPolicy = "Never"
  8243  			errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8244  			if len(errs) == 0 {
  8245  				t.Fatal("expected error but received none")
  8246  			}
  8247  
  8248  			PodRestartPolicy = "Always"
  8249  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8250  			if len(errs) == 0 {
  8251  				t.Fatal("expected error but received none")
  8252  			}
  8253  
  8254  			PodRestartPolicy = "OnFailure"
  8255  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  8256  			if len(errs) == 0 {
  8257  				t.Fatal("expected error but received none")
  8258  			}
  8259  
  8260  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  8261  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  8262  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  8263  			}
  8264  		})
  8265  	}
  8266  }
  8267  
  8268  func TestValidateWindowsPodSecurityContext(t *testing.T) {
  8269  	validWindowsSC := &core.PodSecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}
  8270  	invalidWindowsSC := &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummyRole"}}
  8271  	cases := map[string]struct {
  8272  		podSec      *core.PodSpec
  8273  		expectErr   bool
  8274  		errorType   field.ErrorType
  8275  		errorDetail string
  8276  	}{
  8277  		"valid SC, windows, no error": {
  8278  			podSec:    &core.PodSpec{SecurityContext: validWindowsSC},
  8279  			expectErr: false,
  8280  		},
  8281  		"invalid SC, windows, error": {
  8282  			podSec:      &core.PodSpec{SecurityContext: invalidWindowsSC},
  8283  			errorType:   "FieldValueForbidden",
  8284  			errorDetail: "cannot be set for a windows pod",
  8285  			expectErr:   true,
  8286  		},
  8287  	}
  8288  	for k, v := range cases {
  8289  		t.Run(k, func(t *testing.T) {
  8290  			errs := validateWindows(v.podSec, field.NewPath("field"))
  8291  			if v.expectErr && len(errs) > 0 {
  8292  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  8293  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  8294  				}
  8295  			} else if v.expectErr && len(errs) == 0 {
  8296  				t.Errorf("Unexpected success")
  8297  			}
  8298  			if !v.expectErr && len(errs) != 0 {
  8299  				t.Errorf("Unexpected error(s): %v", errs)
  8300  			}
  8301  		})
  8302  	}
  8303  }
  8304  
  8305  func TestValidateLinuxPodSecurityContext(t *testing.T) {
  8306  	runAsUser := int64(1)
  8307  	validLinuxSC := &core.PodSecurityContext{
  8308  		SELinuxOptions: &core.SELinuxOptions{
  8309  			User:  "user",
  8310  			Role:  "role",
  8311  			Type:  "type",
  8312  			Level: "level",
  8313  		},
  8314  		RunAsUser: &runAsUser,
  8315  	}
  8316  	invalidLinuxSC := &core.PodSecurityContext{
  8317  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
  8318  	}
  8319  
  8320  	cases := map[string]struct {
  8321  		podSpec     *core.PodSpec
  8322  		expectErr   bool
  8323  		errorType   field.ErrorType
  8324  		errorDetail string
  8325  	}{
  8326  		"valid SC, linux, no error": {
  8327  			podSpec:   &core.PodSpec{SecurityContext: validLinuxSC},
  8328  			expectErr: false,
  8329  		},
  8330  		"invalid SC, linux, error": {
  8331  			podSpec:     &core.PodSpec{SecurityContext: invalidLinuxSC},
  8332  			errorType:   "FieldValueForbidden",
  8333  			errorDetail: "windows options cannot be set for a linux pod",
  8334  			expectErr:   true,
  8335  		},
  8336  	}
  8337  	for k, v := range cases {
  8338  		t.Run(k, func(t *testing.T) {
  8339  			errs := validateLinux(v.podSpec, field.NewPath("field"))
  8340  			if v.expectErr && len(errs) > 0 {
  8341  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  8342  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  8343  				}
  8344  			} else if v.expectErr && len(errs) == 0 {
  8345  				t.Errorf("Unexpected success")
  8346  			}
  8347  			if !v.expectErr && len(errs) != 0 {
  8348  				t.Errorf("Unexpected error(s): %v", errs)
  8349  			}
  8350  		})
  8351  	}
  8352  }
  8353  
  8354  func TestValidateContainers(t *testing.T) {
  8355  	volumeDevices := make(map[string]core.VolumeSource)
  8356  	capabilities.SetForTests(capabilities.Capabilities{
  8357  		AllowPrivileged: true,
  8358  	})
  8359  
  8360  	successCase := []core.Container{
  8361  		{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8362  		// backwards compatibility to ensure containers in pod template spec do not check for this
  8363  		{Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8364  		{Name: "ghi", Image: " some  ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8365  		{Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8366  		{Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {
  8367  			Name:  "life-123",
  8368  			Image: "image",
  8369  			Lifecycle: &core.Lifecycle{
  8370  				PreStop: &core.LifecycleHandler{
  8371  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  8372  				},
  8373  			},
  8374  			ImagePullPolicy:          "IfNotPresent",
  8375  			TerminationMessagePolicy: "File",
  8376  		}, {
  8377  			Name:  "resources-test",
  8378  			Image: "image",
  8379  			Resources: core.ResourceRequirements{
  8380  				Limits: core.ResourceList{
  8381  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8382  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8383  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8384  				},
  8385  			},
  8386  			ImagePullPolicy:          "IfNotPresent",
  8387  			TerminationMessagePolicy: "File",
  8388  		}, {
  8389  			Name:  "resources-test-with-request-and-limit",
  8390  			Image: "image",
  8391  			Resources: core.ResourceRequirements{
  8392  				Requests: core.ResourceList{
  8393  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8394  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8395  				},
  8396  				Limits: core.ResourceList{
  8397  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8398  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8399  				},
  8400  			},
  8401  			ImagePullPolicy:          "IfNotPresent",
  8402  			TerminationMessagePolicy: "File",
  8403  		}, {
  8404  			Name:  "resources-request-limit-simple",
  8405  			Image: "image",
  8406  			Resources: core.ResourceRequirements{
  8407  				Requests: core.ResourceList{
  8408  					core.ResourceName(core.ResourceCPU): resource.MustParse("8"),
  8409  				},
  8410  				Limits: core.ResourceList{
  8411  					core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  8412  				},
  8413  			},
  8414  			ImagePullPolicy:          "IfNotPresent",
  8415  			TerminationMessagePolicy: "File",
  8416  		}, {
  8417  			Name:  "resources-request-limit-edge",
  8418  			Image: "image",
  8419  			Resources: core.ResourceRequirements{
  8420  				Requests: core.ResourceList{
  8421  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8422  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8423  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8424  				},
  8425  				Limits: core.ResourceList{
  8426  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  8427  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8428  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  8429  				},
  8430  			},
  8431  			ImagePullPolicy:          "IfNotPresent",
  8432  			TerminationMessagePolicy: "File",
  8433  		}, {
  8434  			Name:  "resources-request-limit-partials",
  8435  			Image: "image",
  8436  			Resources: core.ResourceRequirements{
  8437  				Requests: core.ResourceList{
  8438  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  8439  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8440  				},
  8441  				Limits: core.ResourceList{
  8442  					core.ResourceName(core.ResourceCPU):  resource.MustParse("10"),
  8443  					core.ResourceName("my.org/resource"): resource.MustParse("10"),
  8444  				},
  8445  			},
  8446  			ImagePullPolicy:          "IfNotPresent",
  8447  			TerminationMessagePolicy: "File",
  8448  		}, {
  8449  			Name:  "resources-request",
  8450  			Image: "image",
  8451  			Resources: core.ResourceRequirements{
  8452  				Requests: core.ResourceList{
  8453  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  8454  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  8455  				},
  8456  			},
  8457  			ImagePullPolicy:          "IfNotPresent",
  8458  			TerminationMessagePolicy: "File",
  8459  		}, {
  8460  			Name:  "resources-resize-policy",
  8461  			Image: "image",
  8462  			ResizePolicy: []core.ContainerResizePolicy{
  8463  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8464  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  8465  			},
  8466  			ImagePullPolicy:          "IfNotPresent",
  8467  			TerminationMessagePolicy: "File",
  8468  		}, {
  8469  			Name:  "same-host-port-different-protocol",
  8470  			Image: "image",
  8471  			Ports: []core.ContainerPort{
  8472  				{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  8473  				{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  8474  			},
  8475  			ImagePullPolicy:          "IfNotPresent",
  8476  			TerminationMessagePolicy: "File",
  8477  		}, {
  8478  			Name:                     "fallback-to-logs-termination-message",
  8479  			Image:                    "image",
  8480  			ImagePullPolicy:          "IfNotPresent",
  8481  			TerminationMessagePolicy: "FallbackToLogsOnError",
  8482  		}, {
  8483  			Name:                     "file-termination-message",
  8484  			Image:                    "image",
  8485  			ImagePullPolicy:          "IfNotPresent",
  8486  			TerminationMessagePolicy: "File",
  8487  		}, {
  8488  			Name:                     "env-from-source",
  8489  			Image:                    "image",
  8490  			ImagePullPolicy:          "IfNotPresent",
  8491  			TerminationMessagePolicy: "File",
  8492  			EnvFrom: []core.EnvFromSource{{
  8493  				ConfigMapRef: &core.ConfigMapEnvSource{
  8494  					LocalObjectReference: core.LocalObjectReference{
  8495  						Name: "test",
  8496  					},
  8497  				},
  8498  			}},
  8499  		},
  8500  		{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, {
  8501  			Name:  "live-123",
  8502  			Image: "image",
  8503  			LivenessProbe: &core.Probe{
  8504  				ProbeHandler: core.ProbeHandler{
  8505  					TCPSocket: &core.TCPSocketAction{
  8506  						Port: intstr.FromInt32(80),
  8507  					},
  8508  				},
  8509  				SuccessThreshold: 1,
  8510  			},
  8511  			ImagePullPolicy:          "IfNotPresent",
  8512  			TerminationMessagePolicy: "File",
  8513  		}, {
  8514  			Name:  "startup-123",
  8515  			Image: "image",
  8516  			StartupProbe: &core.Probe{
  8517  				ProbeHandler: core.ProbeHandler{
  8518  					TCPSocket: &core.TCPSocketAction{
  8519  						Port: intstr.FromInt32(80),
  8520  					},
  8521  				},
  8522  				SuccessThreshold: 1,
  8523  			},
  8524  			ImagePullPolicy:          "IfNotPresent",
  8525  			TerminationMessagePolicy: "File",
  8526  		}, {
  8527  			Name:                     "resize-policy-cpu",
  8528  			Image:                    "image",
  8529  			ImagePullPolicy:          "IfNotPresent",
  8530  			TerminationMessagePolicy: "File",
  8531  			ResizePolicy: []core.ContainerResizePolicy{
  8532  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  8533  			},
  8534  		}, {
  8535  			Name:                     "resize-policy-mem",
  8536  			Image:                    "image",
  8537  			ImagePullPolicy:          "IfNotPresent",
  8538  			TerminationMessagePolicy: "File",
  8539  			ResizePolicy: []core.ContainerResizePolicy{
  8540  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  8541  			},
  8542  		}, {
  8543  			Name:                     "resize-policy-cpu-and-mem",
  8544  			Image:                    "image",
  8545  			ImagePullPolicy:          "IfNotPresent",
  8546  			TerminationMessagePolicy: "File",
  8547  			ResizePolicy: []core.ContainerResizePolicy{
  8548  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  8549  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  8550  			},
  8551  		},
  8552  	}
  8553  
  8554  	var PodRestartPolicy core.RestartPolicy = "Always"
  8555  	if errs := validateContainers(successCase, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  8556  		t.Errorf("expected success: %v", errs)
  8557  	}
  8558  
  8559  	capabilities.SetForTests(capabilities.Capabilities{
  8560  		AllowPrivileged: false,
  8561  	})
  8562  	errorCases := []struct {
  8563  		title, line    string
  8564  		containers     []core.Container
  8565  		expectedErrors field.ErrorList
  8566  	}{{
  8567  		"zero-length name",
  8568  		line(),
  8569  		[]core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8570  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}},
  8571  	}, {
  8572  		"zero-length-image",
  8573  		line(),
  8574  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8575  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  8576  	}, {
  8577  		"name > 63 characters",
  8578  		line(),
  8579  		[]core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8580  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  8581  	}, {
  8582  		"name not a DNS label",
  8583  		line(),
  8584  		[]core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8585  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  8586  	}, {
  8587  		"name not unique",
  8588  		line(),
  8589  		[]core.Container{
  8590  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8591  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8592  		},
  8593  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}},
  8594  	}, {
  8595  		"zero-length image",
  8596  		line(),
  8597  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  8598  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  8599  	}, {
  8600  		"host port not unique",
  8601  		line(),
  8602  		[]core.Container{
  8603  			{Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
  8604  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8605  			{Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
  8606  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8607  		},
  8608  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}},
  8609  	}, {
  8610  		"invalid env var name",
  8611  		line(),
  8612  		[]core.Container{
  8613  			{Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8614  		},
  8615  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}},
  8616  	}, {
  8617  		"unknown volume name",
  8618  		line(),
  8619  		[]core.Container{
  8620  			{Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}},
  8621  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8622  		},
  8623  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}},
  8624  	}, {
  8625  		"invalid lifecycle, no exec command.",
  8626  		line(),
  8627  		[]core.Container{{
  8628  			Name:  "life-123",
  8629  			Image: "image",
  8630  			Lifecycle: &core.Lifecycle{
  8631  				PreStop: &core.LifecycleHandler{
  8632  					Exec: &core.ExecAction{},
  8633  				},
  8634  			},
  8635  			ImagePullPolicy:          "IfNotPresent",
  8636  			TerminationMessagePolicy: "File",
  8637  		}},
  8638  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}},
  8639  	}, {
  8640  		"invalid lifecycle, no http path.",
  8641  		line(),
  8642  		[]core.Container{{
  8643  			Name:  "life-123",
  8644  			Image: "image",
  8645  			Lifecycle: &core.Lifecycle{
  8646  				PreStop: &core.LifecycleHandler{
  8647  					HTTPGet: &core.HTTPGetAction{
  8648  						Port:   intstr.FromInt32(80),
  8649  						Scheme: "HTTP",
  8650  					},
  8651  				},
  8652  			},
  8653  			ImagePullPolicy:          "IfNotPresent",
  8654  			TerminationMessagePolicy: "File",
  8655  		}},
  8656  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}},
  8657  	}, {
  8658  		"invalid lifecycle, no http port.",
  8659  		line(),
  8660  		[]core.Container{{
  8661  			Name:  "life-123",
  8662  			Image: "image",
  8663  			Lifecycle: &core.Lifecycle{
  8664  				PreStop: &core.LifecycleHandler{
  8665  					HTTPGet: &core.HTTPGetAction{
  8666  						Path:   "/",
  8667  						Scheme: "HTTP",
  8668  					},
  8669  				},
  8670  			},
  8671  			ImagePullPolicy:          "IfNotPresent",
  8672  			TerminationMessagePolicy: "File",
  8673  		}},
  8674  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}},
  8675  	}, {
  8676  		"invalid lifecycle, no http scheme.",
  8677  		line(),
  8678  		[]core.Container{{
  8679  			Name:  "life-123",
  8680  			Image: "image",
  8681  			Lifecycle: &core.Lifecycle{
  8682  				PreStop: &core.LifecycleHandler{
  8683  					HTTPGet: &core.HTTPGetAction{
  8684  						Path: "/",
  8685  						Port: intstr.FromInt32(80),
  8686  					},
  8687  				},
  8688  			},
  8689  			ImagePullPolicy:          "IfNotPresent",
  8690  			TerminationMessagePolicy: "File",
  8691  		}},
  8692  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}},
  8693  	}, {
  8694  		"invalid lifecycle, no tcp socket port.",
  8695  		line(),
  8696  		[]core.Container{{
  8697  			Name:  "life-123",
  8698  			Image: "image",
  8699  			Lifecycle: &core.Lifecycle{
  8700  				PreStop: &core.LifecycleHandler{
  8701  					TCPSocket: &core.TCPSocketAction{},
  8702  				},
  8703  			},
  8704  			ImagePullPolicy:          "IfNotPresent",
  8705  			TerminationMessagePolicy: "File",
  8706  		}},
  8707  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8708  	}, {
  8709  		"invalid lifecycle, zero tcp socket port.",
  8710  		line(),
  8711  		[]core.Container{{
  8712  			Name:  "life-123",
  8713  			Image: "image",
  8714  			Lifecycle: &core.Lifecycle{
  8715  				PreStop: &core.LifecycleHandler{
  8716  					TCPSocket: &core.TCPSocketAction{
  8717  						Port: intstr.FromInt32(0),
  8718  					},
  8719  				},
  8720  			},
  8721  			ImagePullPolicy:          "IfNotPresent",
  8722  			TerminationMessagePolicy: "File",
  8723  		}},
  8724  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8725  	}, {
  8726  		"invalid lifecycle, no action.",
  8727  		line(),
  8728  		[]core.Container{{
  8729  			Name:  "life-123",
  8730  			Image: "image",
  8731  			Lifecycle: &core.Lifecycle{
  8732  				PreStop: &core.LifecycleHandler{},
  8733  			},
  8734  			ImagePullPolicy:          "IfNotPresent",
  8735  			TerminationMessagePolicy: "File",
  8736  		}},
  8737  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}},
  8738  	}, {
  8739  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  8740  		line(),
  8741  		[]core.Container{{
  8742  			Name:  "life-123",
  8743  			Image: "image",
  8744  			ReadinessProbe: &core.Probe{
  8745  				ProbeHandler: core.ProbeHandler{
  8746  					TCPSocket: &core.TCPSocketAction{
  8747  						Port: intstr.FromInt32(80),
  8748  					},
  8749  				},
  8750  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  8751  			},
  8752  			ImagePullPolicy:          "IfNotPresent",
  8753  			TerminationMessagePolicy: "File",
  8754  		}},
  8755  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}},
  8756  	}, {
  8757  		"invalid liveness probe, no tcp socket port.",
  8758  		line(),
  8759  		[]core.Container{{
  8760  			Name:  "live-123",
  8761  			Image: "image",
  8762  			LivenessProbe: &core.Probe{
  8763  				ProbeHandler: core.ProbeHandler{
  8764  					TCPSocket: &core.TCPSocketAction{},
  8765  				},
  8766  				SuccessThreshold: 1,
  8767  			},
  8768  			ImagePullPolicy:          "IfNotPresent",
  8769  			TerminationMessagePolicy: "File",
  8770  		}},
  8771  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}},
  8772  	}, {
  8773  		"invalid liveness probe, no action.",
  8774  		line(),
  8775  		[]core.Container{{
  8776  			Name:  "live-123",
  8777  			Image: "image",
  8778  			LivenessProbe: &core.Probe{
  8779  				ProbeHandler:     core.ProbeHandler{},
  8780  				SuccessThreshold: 1,
  8781  			},
  8782  			ImagePullPolicy:          "IfNotPresent",
  8783  			TerminationMessagePolicy: "File",
  8784  		}},
  8785  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}},
  8786  	}, {
  8787  		"invalid liveness probe, successThreshold != 1",
  8788  		line(),
  8789  		[]core.Container{{
  8790  			Name:  "live-123",
  8791  			Image: "image",
  8792  			LivenessProbe: &core.Probe{
  8793  				ProbeHandler: core.ProbeHandler{
  8794  					TCPSocket: &core.TCPSocketAction{
  8795  						Port: intstr.FromInt32(80),
  8796  					},
  8797  				},
  8798  				SuccessThreshold: 2,
  8799  			},
  8800  			ImagePullPolicy:          "IfNotPresent",
  8801  			TerminationMessagePolicy: "File",
  8802  		}},
  8803  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}},
  8804  	}, {
  8805  		"invalid startup probe, successThreshold != 1",
  8806  		line(),
  8807  		[]core.Container{{
  8808  			Name:  "startup-123",
  8809  			Image: "image",
  8810  			StartupProbe: &core.Probe{
  8811  				ProbeHandler: core.ProbeHandler{
  8812  					TCPSocket: &core.TCPSocketAction{
  8813  						Port: intstr.FromInt32(80),
  8814  					},
  8815  				},
  8816  				SuccessThreshold: 2,
  8817  			},
  8818  			ImagePullPolicy:          "IfNotPresent",
  8819  			TerminationMessagePolicy: "File",
  8820  		}},
  8821  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}},
  8822  	}, {
  8823  		"invalid liveness probe, negative numbers",
  8824  		line(),
  8825  		[]core.Container{{
  8826  			Name:  "live-123",
  8827  			Image: "image",
  8828  			LivenessProbe: &core.Probe{
  8829  				ProbeHandler: core.ProbeHandler{
  8830  					TCPSocket: &core.TCPSocketAction{
  8831  						Port: intstr.FromInt32(80),
  8832  					},
  8833  				},
  8834  				InitialDelaySeconds:           -1,
  8835  				TimeoutSeconds:                -1,
  8836  				PeriodSeconds:                 -1,
  8837  				SuccessThreshold:              -1,
  8838  				FailureThreshold:              -1,
  8839  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8840  			},
  8841  			ImagePullPolicy:          "IfNotPresent",
  8842  			TerminationMessagePolicy: "File",
  8843  		}},
  8844  		field.ErrorList{
  8845  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"},
  8846  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"},
  8847  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"},
  8848  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8849  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"},
  8850  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"},
  8851  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8852  		},
  8853  	}, {
  8854  		"invalid readiness probe, negative numbers",
  8855  		line(),
  8856  		[]core.Container{{
  8857  			Name:  "ready-123",
  8858  			Image: "image",
  8859  			ReadinessProbe: &core.Probe{
  8860  				ProbeHandler: core.ProbeHandler{
  8861  					TCPSocket: &core.TCPSocketAction{
  8862  						Port: intstr.FromInt32(80),
  8863  					},
  8864  				},
  8865  				InitialDelaySeconds:           -1,
  8866  				TimeoutSeconds:                -1,
  8867  				PeriodSeconds:                 -1,
  8868  				SuccessThreshold:              -1,
  8869  				FailureThreshold:              -1,
  8870  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8871  			},
  8872  			ImagePullPolicy:          "IfNotPresent",
  8873  			TerminationMessagePolicy: "File",
  8874  		}},
  8875  		field.ErrorList{
  8876  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"},
  8877  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"},
  8878  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"},
  8879  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"},
  8880  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"},
  8881  			// terminationGracePeriodSeconds returns multiple validation errors here:
  8882  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0
  8883  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8884  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes
  8885  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8886  		},
  8887  	}, {
  8888  		"invalid startup probe, negative numbers",
  8889  		line(),
  8890  		[]core.Container{{
  8891  			Name:  "startup-123",
  8892  			Image: "image",
  8893  			StartupProbe: &core.Probe{
  8894  				ProbeHandler: core.ProbeHandler{
  8895  					TCPSocket: &core.TCPSocketAction{
  8896  						Port: intstr.FromInt32(80),
  8897  					},
  8898  				},
  8899  				InitialDelaySeconds:           -1,
  8900  				TimeoutSeconds:                -1,
  8901  				PeriodSeconds:                 -1,
  8902  				SuccessThreshold:              -1,
  8903  				FailureThreshold:              -1,
  8904  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8905  			},
  8906  			ImagePullPolicy:          "IfNotPresent",
  8907  			TerminationMessagePolicy: "File",
  8908  		}},
  8909  		field.ErrorList{
  8910  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"},
  8911  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"},
  8912  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"},
  8913  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8914  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"},
  8915  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"},
  8916  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8917  		},
  8918  	}, {
  8919  		"invalid message termination policy",
  8920  		line(),
  8921  		[]core.Container{{
  8922  			Name:                     "life-123",
  8923  			Image:                    "image",
  8924  			ImagePullPolicy:          "IfNotPresent",
  8925  			TerminationMessagePolicy: "Unknown",
  8926  		}},
  8927  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}},
  8928  	}, {
  8929  		"empty message termination policy",
  8930  		line(),
  8931  		[]core.Container{{
  8932  			Name:                     "life-123",
  8933  			Image:                    "image",
  8934  			ImagePullPolicy:          "IfNotPresent",
  8935  			TerminationMessagePolicy: "",
  8936  		}},
  8937  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}},
  8938  	}, {
  8939  		"privilege disabled",
  8940  		line(),
  8941  		[]core.Container{{
  8942  			Name:                     "abc",
  8943  			Image:                    "image",
  8944  			SecurityContext:          fakeValidSecurityContext(true),
  8945  			ImagePullPolicy:          "IfNotPresent",
  8946  			TerminationMessagePolicy: "File",
  8947  		}},
  8948  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}},
  8949  	}, {
  8950  		"invalid compute resource",
  8951  		line(),
  8952  		[]core.Container{{
  8953  			Name:  "abc-123",
  8954  			Image: "image",
  8955  			Resources: core.ResourceRequirements{
  8956  				Limits: core.ResourceList{
  8957  					"disk": resource.MustParse("10G"),
  8958  				},
  8959  			},
  8960  			ImagePullPolicy:          "IfNotPresent",
  8961  			TerminationMessagePolicy: "File",
  8962  		}},
  8963  		field.ErrorList{
  8964  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8965  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8966  		},
  8967  	}, {
  8968  		"Resource CPU invalid",
  8969  		line(),
  8970  		[]core.Container{{
  8971  			Name:  "abc-123",
  8972  			Image: "image",
  8973  			Resources: core.ResourceRequirements{
  8974  				Limits: getResourceLimits("-10", "0"),
  8975  			},
  8976  			ImagePullPolicy:          "IfNotPresent",
  8977  			TerminationMessagePolicy: "File",
  8978  		}},
  8979  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}},
  8980  	}, {
  8981  		"Resource Requests CPU invalid",
  8982  		line(),
  8983  		[]core.Container{{
  8984  			Name:  "abc-123",
  8985  			Image: "image",
  8986  			Resources: core.ResourceRequirements{
  8987  				Requests: getResourceLimits("-10", "0"),
  8988  			},
  8989  			ImagePullPolicy:          "IfNotPresent",
  8990  			TerminationMessagePolicy: "File",
  8991  		}},
  8992  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}},
  8993  	}, {
  8994  		"Resource Memory invalid",
  8995  		line(),
  8996  		[]core.Container{{
  8997  			Name:  "abc-123",
  8998  			Image: "image",
  8999  			Resources: core.ResourceRequirements{
  9000  				Limits: getResourceLimits("0", "-10"),
  9001  			},
  9002  			ImagePullPolicy:          "IfNotPresent",
  9003  			TerminationMessagePolicy: "File",
  9004  		}},
  9005  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}},
  9006  	}, {
  9007  		"Request limit simple invalid",
  9008  		line(),
  9009  		[]core.Container{{
  9010  			Name:  "abc-123",
  9011  			Image: "image",
  9012  			Resources: core.ResourceRequirements{
  9013  				Limits:   getResourceLimits("5", "3"),
  9014  				Requests: getResourceLimits("6", "3"),
  9015  			},
  9016  			ImagePullPolicy:          "IfNotPresent",
  9017  			TerminationMessagePolicy: "File",
  9018  		}},
  9019  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  9020  	}, {
  9021  		"Invalid storage limit request",
  9022  		line(),
  9023  		[]core.Container{{
  9024  			Name:  "abc-123",
  9025  			Image: "image",
  9026  			Resources: core.ResourceRequirements{
  9027  				Limits: core.ResourceList{
  9028  					core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI),
  9029  				},
  9030  			},
  9031  			ImagePullPolicy:          "IfNotPresent",
  9032  			TerminationMessagePolicy: "File",
  9033  		}},
  9034  		field.ErrorList{
  9035  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  9036  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  9037  		},
  9038  	}, {
  9039  		"CPU request limit multiple invalid",
  9040  		line(),
  9041  		[]core.Container{{
  9042  			Name:  "abc-123",
  9043  			Image: "image",
  9044  			Resources: core.ResourceRequirements{
  9045  				Limits:   getResourceLimits("5", "3"),
  9046  				Requests: getResourceLimits("6", "3"),
  9047  			},
  9048  			ImagePullPolicy:          "IfNotPresent",
  9049  			TerminationMessagePolicy: "File",
  9050  		}},
  9051  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  9052  	}, {
  9053  		"Memory request limit multiple invalid",
  9054  		line(),
  9055  		[]core.Container{{
  9056  			Name:  "abc-123",
  9057  			Image: "image",
  9058  			Resources: core.ResourceRequirements{
  9059  				Limits:   getResourceLimits("5", "3"),
  9060  				Requests: getResourceLimits("5", "4"),
  9061  			},
  9062  			ImagePullPolicy:          "IfNotPresent",
  9063  			TerminationMessagePolicy: "File",
  9064  		}},
  9065  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  9066  	}, {
  9067  		"Invalid env from",
  9068  		line(),
  9069  		[]core.Container{{
  9070  			Name:                     "env-from-source",
  9071  			Image:                    "image",
  9072  			ImagePullPolicy:          "IfNotPresent",
  9073  			TerminationMessagePolicy: "File",
  9074  			EnvFrom: []core.EnvFromSource{{
  9075  				ConfigMapRef: &core.ConfigMapEnvSource{
  9076  					LocalObjectReference: core.LocalObjectReference{
  9077  						Name: "$%^&*#",
  9078  					},
  9079  				},
  9080  			}},
  9081  		}},
  9082  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}},
  9083  	}, {
  9084  		"Unsupported resize policy for memory",
  9085  		line(),
  9086  		[]core.Container{{
  9087  			Name:                     "resize-policy-mem-invalid",
  9088  			Image:                    "image",
  9089  			ImagePullPolicy:          "IfNotPresent",
  9090  			TerminationMessagePolicy: "File",
  9091  			ResizePolicy: []core.ContainerResizePolicy{
  9092  				{ResourceName: "memory", RestartPolicy: "RestartContainerrrr"},
  9093  			},
  9094  		}},
  9095  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  9096  	}, {
  9097  		"Unsupported resize policy for CPU",
  9098  		line(),
  9099  		[]core.Container{{
  9100  			Name:                     "resize-policy-cpu-invalid",
  9101  			Image:                    "image",
  9102  			ImagePullPolicy:          "IfNotPresent",
  9103  			TerminationMessagePolicy: "File",
  9104  			ResizePolicy: []core.ContainerResizePolicy{
  9105  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequired"},
  9106  			},
  9107  		}},
  9108  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  9109  	}, {
  9110  		"Forbidden RestartPolicy: Always",
  9111  		line(),
  9112  		[]core.Container{{
  9113  			Name:                     "foo",
  9114  			Image:                    "image",
  9115  			ImagePullPolicy:          "IfNotPresent",
  9116  			TerminationMessagePolicy: "File",
  9117  			RestartPolicy:            &containerRestartPolicyAlways,
  9118  		}},
  9119  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9120  	}, {
  9121  		"Forbidden RestartPolicy: OnFailure",
  9122  		line(),
  9123  		[]core.Container{{
  9124  			Name:                     "foo",
  9125  			Image:                    "image",
  9126  			ImagePullPolicy:          "IfNotPresent",
  9127  			TerminationMessagePolicy: "File",
  9128  			RestartPolicy:            &containerRestartPolicyOnFailure,
  9129  		}},
  9130  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9131  	}, {
  9132  		"Forbidden RestartPolicy: Never",
  9133  		line(),
  9134  		[]core.Container{{
  9135  			Name:                     "foo",
  9136  			Image:                    "image",
  9137  			ImagePullPolicy:          "IfNotPresent",
  9138  			TerminationMessagePolicy: "File",
  9139  			RestartPolicy:            &containerRestartPolicyNever,
  9140  		}},
  9141  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9142  	}, {
  9143  		"Forbidden RestartPolicy: invalid",
  9144  		line(),
  9145  		[]core.Container{{
  9146  			Name:                     "foo",
  9147  			Image:                    "image",
  9148  			ImagePullPolicy:          "IfNotPresent",
  9149  			TerminationMessagePolicy: "File",
  9150  			RestartPolicy:            &containerRestartPolicyInvalid,
  9151  		}},
  9152  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9153  	}, {
  9154  		"Forbidden RestartPolicy: empty",
  9155  		line(),
  9156  		[]core.Container{{
  9157  			Name:                     "foo",
  9158  			Image:                    "image",
  9159  			ImagePullPolicy:          "IfNotPresent",
  9160  			TerminationMessagePolicy: "File",
  9161  			RestartPolicy:            &containerRestartPolicyEmpty,
  9162  		}},
  9163  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  9164  	},
  9165  	}
  9166  
  9167  	for _, tc := range errorCases {
  9168  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  9169  			errs := validateContainers(tc.containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("containers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  9170  			if len(errs) == 0 {
  9171  				t.Fatal("expected error but received none")
  9172  			}
  9173  
  9174  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  9175  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  9176  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  9177  			}
  9178  		})
  9179  	}
  9180  }
  9181  
  9182  func TestValidateInitContainers(t *testing.T) {
  9183  	volumeDevices := make(map[string]core.VolumeSource)
  9184  	capabilities.SetForTests(capabilities.Capabilities{
  9185  		AllowPrivileged: true,
  9186  	})
  9187  
  9188  	containers := []core.Container{{
  9189  		Name:                     "app",
  9190  		Image:                    "nginx",
  9191  		ImagePullPolicy:          "IfNotPresent",
  9192  		TerminationMessagePolicy: "File",
  9193  	},
  9194  	}
  9195  
  9196  	successCase := []core.Container{{
  9197  		Name:  "container-1-same-host-port-different-protocol",
  9198  		Image: "image",
  9199  		Ports: []core.ContainerPort{
  9200  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  9201  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  9202  		},
  9203  		ImagePullPolicy:          "IfNotPresent",
  9204  		TerminationMessagePolicy: "File",
  9205  	}, {
  9206  		Name:  "container-2-same-host-port-different-protocol",
  9207  		Image: "image",
  9208  		Ports: []core.ContainerPort{
  9209  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  9210  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  9211  		},
  9212  		ImagePullPolicy:          "IfNotPresent",
  9213  		TerminationMessagePolicy: "File",
  9214  	}, {
  9215  		Name:                     "container-3-restart-always-with-lifecycle-hook-and-probes",
  9216  		Image:                    "image",
  9217  		ImagePullPolicy:          "IfNotPresent",
  9218  		TerminationMessagePolicy: "File",
  9219  		RestartPolicy:            &containerRestartPolicyAlways,
  9220  		Lifecycle: &core.Lifecycle{
  9221  			PostStart: &core.LifecycleHandler{
  9222  				Exec: &core.ExecAction{
  9223  					Command: []string{"echo", "post start"},
  9224  				},
  9225  			},
  9226  			PreStop: &core.LifecycleHandler{
  9227  				Exec: &core.ExecAction{
  9228  					Command: []string{"echo", "pre stop"},
  9229  				},
  9230  			},
  9231  		},
  9232  		LivenessProbe: &core.Probe{
  9233  			ProbeHandler: core.ProbeHandler{
  9234  				TCPSocket: &core.TCPSocketAction{
  9235  					Port: intstr.FromInt32(80),
  9236  				},
  9237  			},
  9238  			SuccessThreshold: 1,
  9239  		},
  9240  		ReadinessProbe: &core.Probe{
  9241  			ProbeHandler: core.ProbeHandler{
  9242  				TCPSocket: &core.TCPSocketAction{
  9243  					Port: intstr.FromInt32(80),
  9244  				},
  9245  			},
  9246  		},
  9247  		StartupProbe: &core.Probe{
  9248  			ProbeHandler: core.ProbeHandler{
  9249  				TCPSocket: &core.TCPSocketAction{
  9250  					Port: intstr.FromInt32(80),
  9251  				},
  9252  			},
  9253  			SuccessThreshold: 1,
  9254  		},
  9255  	},
  9256  	}
  9257  	var PodRestartPolicy core.RestartPolicy = "Never"
  9258  	if errs := validateInitContainers(successCase, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
  9259  		t.Errorf("expected success: %v", errs)
  9260  	}
  9261  
  9262  	capabilities.SetForTests(capabilities.Capabilities{
  9263  		AllowPrivileged: false,
  9264  	})
  9265  	errorCases := []struct {
  9266  		title, line    string
  9267  		initContainers []core.Container
  9268  		expectedErrors field.ErrorList
  9269  	}{{
  9270  		"empty name",
  9271  		line(),
  9272  		[]core.Container{{
  9273  			Name:                     "",
  9274  			Image:                    "image",
  9275  			ImagePullPolicy:          "IfNotPresent",
  9276  			TerminationMessagePolicy: "File",
  9277  		}},
  9278  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}},
  9279  	}, {
  9280  		"name collision with regular container",
  9281  		line(),
  9282  		[]core.Container{{
  9283  			Name:                     "app",
  9284  			Image:                    "image",
  9285  			ImagePullPolicy:          "IfNotPresent",
  9286  			TerminationMessagePolicy: "File",
  9287  		}},
  9288  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}},
  9289  	}, {
  9290  		"invalid termination message policy",
  9291  		line(),
  9292  		[]core.Container{{
  9293  			Name:                     "init",
  9294  			Image:                    "image",
  9295  			ImagePullPolicy:          "IfNotPresent",
  9296  			TerminationMessagePolicy: "Unknown",
  9297  		}},
  9298  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}},
  9299  	}, {
  9300  		"duplicate names",
  9301  		line(),
  9302  		[]core.Container{{
  9303  			Name:                     "init",
  9304  			Image:                    "image",
  9305  			ImagePullPolicy:          "IfNotPresent",
  9306  			TerminationMessagePolicy: "File",
  9307  		}, {
  9308  			Name:                     "init",
  9309  			Image:                    "image",
  9310  			ImagePullPolicy:          "IfNotPresent",
  9311  			TerminationMessagePolicy: "File",
  9312  		}},
  9313  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}},
  9314  	}, {
  9315  		"duplicate ports",
  9316  		line(),
  9317  		[]core.Container{{
  9318  			Name:  "abc",
  9319  			Image: "image",
  9320  			Ports: []core.ContainerPort{{
  9321  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  9322  			}, {
  9323  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  9324  			}},
  9325  			ImagePullPolicy:          "IfNotPresent",
  9326  			TerminationMessagePolicy: "File",
  9327  		}},
  9328  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}},
  9329  	}, {
  9330  		"uses disallowed field: Lifecycle",
  9331  		line(),
  9332  		[]core.Container{{
  9333  			Name:                     "debug",
  9334  			Image:                    "image",
  9335  			ImagePullPolicy:          "IfNotPresent",
  9336  			TerminationMessagePolicy: "File",
  9337  			Lifecycle: &core.Lifecycle{
  9338  				PreStop: &core.LifecycleHandler{
  9339  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  9340  				},
  9341  			},
  9342  		}},
  9343  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}},
  9344  	}, {
  9345  		"uses disallowed field: LivenessProbe",
  9346  		line(),
  9347  		[]core.Container{{
  9348  			Name:                     "debug",
  9349  			Image:                    "image",
  9350  			ImagePullPolicy:          "IfNotPresent",
  9351  			TerminationMessagePolicy: "File",
  9352  			LivenessProbe: &core.Probe{
  9353  				ProbeHandler: core.ProbeHandler{
  9354  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9355  				},
  9356  				SuccessThreshold: 1,
  9357  			},
  9358  		}},
  9359  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}},
  9360  	}, {
  9361  		"uses disallowed field: ReadinessProbe",
  9362  		line(),
  9363  		[]core.Container{{
  9364  			Name:                     "debug",
  9365  			Image:                    "image",
  9366  			ImagePullPolicy:          "IfNotPresent",
  9367  			TerminationMessagePolicy: "File",
  9368  			ReadinessProbe: &core.Probe{
  9369  				ProbeHandler: core.ProbeHandler{
  9370  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9371  				},
  9372  			},
  9373  		}},
  9374  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}},
  9375  	}, {
  9376  		"Container uses disallowed field: StartupProbe",
  9377  		line(),
  9378  		[]core.Container{{
  9379  			Name:                     "debug",
  9380  			Image:                    "image",
  9381  			ImagePullPolicy:          "IfNotPresent",
  9382  			TerminationMessagePolicy: "File",
  9383  			StartupProbe: &core.Probe{
  9384  				ProbeHandler: core.ProbeHandler{
  9385  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9386  				},
  9387  				SuccessThreshold: 1,
  9388  			},
  9389  		}},
  9390  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  9391  	}, {
  9392  		"Disallowed field with other errors should only return a single Forbidden",
  9393  		line(),
  9394  		[]core.Container{{
  9395  			Name:                     "debug",
  9396  			Image:                    "image",
  9397  			ImagePullPolicy:          "IfNotPresent",
  9398  			TerminationMessagePolicy: "File",
  9399  			StartupProbe: &core.Probe{
  9400  				ProbeHandler: core.ProbeHandler{
  9401  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9402  				},
  9403  				InitialDelaySeconds:           -1,
  9404  				TimeoutSeconds:                -1,
  9405  				PeriodSeconds:                 -1,
  9406  				SuccessThreshold:              -1,
  9407  				FailureThreshold:              -1,
  9408  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  9409  			},
  9410  		}},
  9411  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  9412  	}, {
  9413  		"Not supported RestartPolicy: OnFailure",
  9414  		line(),
  9415  		[]core.Container{{
  9416  			Name:                     "init",
  9417  			Image:                    "image",
  9418  			ImagePullPolicy:          "IfNotPresent",
  9419  			TerminationMessagePolicy: "File",
  9420  			RestartPolicy:            &containerRestartPolicyOnFailure,
  9421  		}},
  9422  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}},
  9423  	}, {
  9424  		"Not supported RestartPolicy: Never",
  9425  		line(),
  9426  		[]core.Container{{
  9427  			Name:                     "init",
  9428  			Image:                    "image",
  9429  			ImagePullPolicy:          "IfNotPresent",
  9430  			TerminationMessagePolicy: "File",
  9431  			RestartPolicy:            &containerRestartPolicyNever,
  9432  		}},
  9433  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}},
  9434  	}, {
  9435  		"Not supported RestartPolicy: invalid",
  9436  		line(),
  9437  		[]core.Container{{
  9438  			Name:                     "init",
  9439  			Image:                    "image",
  9440  			ImagePullPolicy:          "IfNotPresent",
  9441  			TerminationMessagePolicy: "File",
  9442  			RestartPolicy:            &containerRestartPolicyInvalid,
  9443  		}},
  9444  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}},
  9445  	}, {
  9446  		"Not supported RestartPolicy: empty",
  9447  		line(),
  9448  		[]core.Container{{
  9449  			Name:                     "init",
  9450  			Image:                    "image",
  9451  			ImagePullPolicy:          "IfNotPresent",
  9452  			TerminationMessagePolicy: "File",
  9453  			RestartPolicy:            &containerRestartPolicyEmpty,
  9454  		}},
  9455  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}},
  9456  	}, {
  9457  		"invalid startup probe in restartable container, successThreshold != 1",
  9458  		line(),
  9459  		[]core.Container{{
  9460  			Name:                     "restartable-init",
  9461  			Image:                    "image",
  9462  			ImagePullPolicy:          "IfNotPresent",
  9463  			TerminationMessagePolicy: "File",
  9464  			RestartPolicy:            &containerRestartPolicyAlways,
  9465  			StartupProbe: &core.Probe{
  9466  				ProbeHandler: core.ProbeHandler{
  9467  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  9468  				},
  9469  				SuccessThreshold: 2,
  9470  			},
  9471  		}},
  9472  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}},
  9473  	}, {
  9474  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  9475  		line(),
  9476  		[]core.Container{{
  9477  			Name:                     "life-123",
  9478  			Image:                    "image",
  9479  			ImagePullPolicy:          "IfNotPresent",
  9480  			TerminationMessagePolicy: "File",
  9481  			RestartPolicy:            &containerRestartPolicyAlways,
  9482  			ReadinessProbe: &core.Probe{
  9483  				ProbeHandler: core.ProbeHandler{
  9484  					TCPSocket: &core.TCPSocketAction{
  9485  						Port: intstr.FromInt32(80),
  9486  					},
  9487  				},
  9488  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  9489  			},
  9490  		}},
  9491  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}},
  9492  	}, {
  9493  		"invalid liveness probe, successThreshold != 1",
  9494  		line(),
  9495  		[]core.Container{{
  9496  			Name:                     "live-123",
  9497  			Image:                    "image",
  9498  			ImagePullPolicy:          "IfNotPresent",
  9499  			TerminationMessagePolicy: "File",
  9500  			RestartPolicy:            &containerRestartPolicyAlways,
  9501  			LivenessProbe: &core.Probe{
  9502  				ProbeHandler: core.ProbeHandler{
  9503  					TCPSocket: &core.TCPSocketAction{
  9504  						Port: intstr.FromInt32(80),
  9505  					},
  9506  				},
  9507  				SuccessThreshold: 2,
  9508  			},
  9509  		}},
  9510  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}},
  9511  	}, {
  9512  		"invalid lifecycle, no exec command.",
  9513  		line(),
  9514  		[]core.Container{{
  9515  			Name:                     "life-123",
  9516  			Image:                    "image",
  9517  			ImagePullPolicy:          "IfNotPresent",
  9518  			TerminationMessagePolicy: "File",
  9519  			RestartPolicy:            &containerRestartPolicyAlways,
  9520  			Lifecycle: &core.Lifecycle{
  9521  				PreStop: &core.LifecycleHandler{
  9522  					Exec: &core.ExecAction{},
  9523  				},
  9524  			},
  9525  		}},
  9526  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}},
  9527  	}, {
  9528  		"invalid lifecycle, no http path.",
  9529  		line(),
  9530  		[]core.Container{{
  9531  			Name:                     "life-123",
  9532  			Image:                    "image",
  9533  			ImagePullPolicy:          "IfNotPresent",
  9534  			TerminationMessagePolicy: "File",
  9535  			RestartPolicy:            &containerRestartPolicyAlways,
  9536  			Lifecycle: &core.Lifecycle{
  9537  				PreStop: &core.LifecycleHandler{
  9538  					HTTPGet: &core.HTTPGetAction{
  9539  						Port:   intstr.FromInt32(80),
  9540  						Scheme: "HTTP",
  9541  					},
  9542  				},
  9543  			},
  9544  		}},
  9545  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}},
  9546  	}, {
  9547  		"invalid lifecycle, no http port.",
  9548  		line(),
  9549  		[]core.Container{{
  9550  			Name:                     "life-123",
  9551  			Image:                    "image",
  9552  			ImagePullPolicy:          "IfNotPresent",
  9553  			TerminationMessagePolicy: "File",
  9554  			RestartPolicy:            &containerRestartPolicyAlways,
  9555  			Lifecycle: &core.Lifecycle{
  9556  				PreStop: &core.LifecycleHandler{
  9557  					HTTPGet: &core.HTTPGetAction{
  9558  						Path:   "/",
  9559  						Scheme: "HTTP",
  9560  					},
  9561  				},
  9562  			},
  9563  		}},
  9564  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}},
  9565  	}, {
  9566  		"invalid lifecycle, no http scheme.",
  9567  		line(),
  9568  		[]core.Container{{
  9569  			Name:                     "life-123",
  9570  			Image:                    "image",
  9571  			ImagePullPolicy:          "IfNotPresent",
  9572  			TerminationMessagePolicy: "File",
  9573  			RestartPolicy:            &containerRestartPolicyAlways,
  9574  			Lifecycle: &core.Lifecycle{
  9575  				PreStop: &core.LifecycleHandler{
  9576  					HTTPGet: &core.HTTPGetAction{
  9577  						Path: "/",
  9578  						Port: intstr.FromInt32(80),
  9579  					},
  9580  				},
  9581  			},
  9582  		}},
  9583  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}},
  9584  	}, {
  9585  		"invalid lifecycle, no tcp socket port.",
  9586  		line(),
  9587  		[]core.Container{{
  9588  			Name:                     "life-123",
  9589  			Image:                    "image",
  9590  			ImagePullPolicy:          "IfNotPresent",
  9591  			TerminationMessagePolicy: "File",
  9592  			RestartPolicy:            &containerRestartPolicyAlways,
  9593  			Lifecycle: &core.Lifecycle{
  9594  				PreStop: &core.LifecycleHandler{
  9595  					TCPSocket: &core.TCPSocketAction{},
  9596  				},
  9597  			},
  9598  		}},
  9599  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  9600  	}, {
  9601  		"invalid lifecycle, zero tcp socket port.",
  9602  		line(),
  9603  		[]core.Container{{
  9604  			Name:                     "life-123",
  9605  			Image:                    "image",
  9606  			ImagePullPolicy:          "IfNotPresent",
  9607  			TerminationMessagePolicy: "File",
  9608  			RestartPolicy:            &containerRestartPolicyAlways,
  9609  			Lifecycle: &core.Lifecycle{
  9610  				PreStop: &core.LifecycleHandler{
  9611  					TCPSocket: &core.TCPSocketAction{
  9612  						Port: intstr.FromInt32(0),
  9613  					},
  9614  				},
  9615  			},
  9616  		}},
  9617  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  9618  	}, {
  9619  		"invalid lifecycle, no action.",
  9620  		line(),
  9621  		[]core.Container{{
  9622  			Name:                     "life-123",
  9623  			Image:                    "image",
  9624  			ImagePullPolicy:          "IfNotPresent",
  9625  			TerminationMessagePolicy: "File",
  9626  			RestartPolicy:            &containerRestartPolicyAlways,
  9627  			Lifecycle: &core.Lifecycle{
  9628  				PreStop: &core.LifecycleHandler{},
  9629  			},
  9630  		}},
  9631  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
  9632  	},
  9633  	}
  9634  
  9635  	for _, tc := range errorCases {
  9636  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  9637  			errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("initContainers"), PodValidationOptions{}, &PodRestartPolicy, noUserNamespace)
  9638  			if len(errs) == 0 {
  9639  				t.Fatal("expected error but received none")
  9640  			}
  9641  
  9642  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "Detail")); diff != "" {
  9643  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  9644  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  9645  			}
  9646  		})
  9647  	}
  9648  }
  9649  
  9650  func TestValidateRestartPolicy(t *testing.T) {
  9651  	successCases := []core.RestartPolicy{
  9652  		core.RestartPolicyAlways,
  9653  		core.RestartPolicyOnFailure,
  9654  		core.RestartPolicyNever,
  9655  	}
  9656  	for _, policy := range successCases {
  9657  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9658  			t.Errorf("expected success: %v", errs)
  9659  		}
  9660  	}
  9661  
  9662  	errorCases := []core.RestartPolicy{"", "newpolicy"}
  9663  
  9664  	for k, policy := range errorCases {
  9665  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9666  			t.Errorf("expected failure for %d", k)
  9667  		}
  9668  	}
  9669  }
  9670  
  9671  func TestValidateDNSPolicy(t *testing.T) {
  9672  	successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSClusterFirstWithHostNet, core.DNSNone}
  9673  	for _, policy := range successCases {
  9674  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9675  			t.Errorf("expected success: %v", errs)
  9676  		}
  9677  	}
  9678  
  9679  	errorCases := []core.DNSPolicy{core.DNSPolicy("invalid"), core.DNSPolicy("")}
  9680  	for _, policy := range errorCases {
  9681  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9682  			t.Errorf("expected failure for %v", policy)
  9683  		}
  9684  	}
  9685  }
  9686  
  9687  func TestValidatePodDNSConfig(t *testing.T) {
  9688  	generateTestSearchPathFunc := func(numChars int) string {
  9689  		res := ""
  9690  		for i := 0; i < numChars; i++ {
  9691  			res = res + "a"
  9692  		}
  9693  		return res
  9694  	}
  9695  	testOptionValue := "2"
  9696  	testDNSNone := core.DNSNone
  9697  	testDNSClusterFirst := core.DNSClusterFirst
  9698  
  9699  	testCases := []struct {
  9700  		desc          string
  9701  		dnsConfig     *core.PodDNSConfig
  9702  		dnsPolicy     *core.DNSPolicy
  9703  		opts          PodValidationOptions
  9704  		expectedError bool
  9705  	}{{
  9706  		desc:          "valid: empty DNSConfig",
  9707  		dnsConfig:     &core.PodDNSConfig{},
  9708  		expectedError: false,
  9709  	}, {
  9710  		desc: "valid: 1 option",
  9711  		dnsConfig: &core.PodDNSConfig{
  9712  			Options: []core.PodDNSConfigOption{
  9713  				{Name: "ndots", Value: &testOptionValue},
  9714  			},
  9715  		},
  9716  		expectedError: false,
  9717  	}, {
  9718  		desc: "valid: 1 nameserver",
  9719  		dnsConfig: &core.PodDNSConfig{
  9720  			Nameservers: []string{"127.0.0.1"},
  9721  		},
  9722  		expectedError: false,
  9723  	}, {
  9724  		desc: "valid: DNSNone with 1 nameserver",
  9725  		dnsConfig: &core.PodDNSConfig{
  9726  			Nameservers: []string{"127.0.0.1"},
  9727  		},
  9728  		dnsPolicy:     &testDNSNone,
  9729  		expectedError: false,
  9730  	}, {
  9731  		desc: "valid: 1 search path",
  9732  		dnsConfig: &core.PodDNSConfig{
  9733  			Searches: []string{"custom"},
  9734  		},
  9735  		expectedError: false,
  9736  	}, {
  9737  		desc: "valid: 1 search path with trailing period",
  9738  		dnsConfig: &core.PodDNSConfig{
  9739  			Searches: []string{"custom."},
  9740  		},
  9741  		expectedError: false,
  9742  	}, {
  9743  		desc: "valid: 3 nameservers and 6 search paths(legacy)",
  9744  		dnsConfig: &core.PodDNSConfig{
  9745  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9746  			Searches:    []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."},
  9747  		},
  9748  		expectedError: false,
  9749  	}, {
  9750  		desc: "valid: 3 nameservers and 32 search paths",
  9751  		dnsConfig: &core.PodDNSConfig{
  9752  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9753  			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"},
  9754  		},
  9755  		expectedError: false,
  9756  	}, {
  9757  		desc: "valid: 256 characters in search path list(legacy)",
  9758  		dnsConfig: &core.PodDNSConfig{
  9759  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9760  			Searches: []string{
  9761  				generateTestSearchPathFunc(1),
  9762  				generateTestSearchPathFunc(50),
  9763  				generateTestSearchPathFunc(50),
  9764  				generateTestSearchPathFunc(50),
  9765  				generateTestSearchPathFunc(50),
  9766  				generateTestSearchPathFunc(50),
  9767  			},
  9768  		},
  9769  		expectedError: false,
  9770  	}, {
  9771  		desc: "valid: 2048 characters in search path list",
  9772  		dnsConfig: &core.PodDNSConfig{
  9773  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9774  			Searches: []string{
  9775  				generateTestSearchPathFunc(64),
  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  				generateTestSearchPathFunc(63),
  9791  				generateTestSearchPathFunc(63),
  9792  				generateTestSearchPathFunc(63),
  9793  				generateTestSearchPathFunc(63),
  9794  				generateTestSearchPathFunc(63),
  9795  				generateTestSearchPathFunc(63),
  9796  				generateTestSearchPathFunc(63),
  9797  				generateTestSearchPathFunc(63),
  9798  				generateTestSearchPathFunc(63),
  9799  				generateTestSearchPathFunc(63),
  9800  				generateTestSearchPathFunc(63),
  9801  				generateTestSearchPathFunc(63),
  9802  				generateTestSearchPathFunc(63),
  9803  				generateTestSearchPathFunc(63),
  9804  				generateTestSearchPathFunc(63),
  9805  				generateTestSearchPathFunc(63),
  9806  				generateTestSearchPathFunc(63),
  9807  			},
  9808  		},
  9809  		expectedError: false,
  9810  	}, {
  9811  		desc: "valid: ipv6 nameserver",
  9812  		dnsConfig: &core.PodDNSConfig{
  9813  			Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
  9814  		},
  9815  		expectedError: false,
  9816  	}, {
  9817  		desc: "invalid: 4 nameservers",
  9818  		dnsConfig: &core.PodDNSConfig{
  9819  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
  9820  		},
  9821  		expectedError: true,
  9822  	}, {
  9823  		desc: "valid: 7 search paths",
  9824  		dnsConfig: &core.PodDNSConfig{
  9825  			Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
  9826  		},
  9827  	}, {
  9828  		desc: "invalid: 33 search paths",
  9829  		dnsConfig: &core.PodDNSConfig{
  9830  			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"},
  9831  		},
  9832  		expectedError: true,
  9833  	}, {
  9834  		desc: "valid: 257 characters in search path list",
  9835  		dnsConfig: &core.PodDNSConfig{
  9836  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9837  			Searches: []string{
  9838  				generateTestSearchPathFunc(2),
  9839  				generateTestSearchPathFunc(50),
  9840  				generateTestSearchPathFunc(50),
  9841  				generateTestSearchPathFunc(50),
  9842  				generateTestSearchPathFunc(50),
  9843  				generateTestSearchPathFunc(50),
  9844  			},
  9845  		},
  9846  	}, {
  9847  		desc: "invalid: 2049 characters in search path list",
  9848  		dnsConfig: &core.PodDNSConfig{
  9849  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9850  			Searches: []string{
  9851  				generateTestSearchPathFunc(65),
  9852  				generateTestSearchPathFunc(63),
  9853  				generateTestSearchPathFunc(63),
  9854  				generateTestSearchPathFunc(63),
  9855  				generateTestSearchPathFunc(63),
  9856  				generateTestSearchPathFunc(63),
  9857  				generateTestSearchPathFunc(63),
  9858  				generateTestSearchPathFunc(63),
  9859  				generateTestSearchPathFunc(63),
  9860  				generateTestSearchPathFunc(63),
  9861  				generateTestSearchPathFunc(63),
  9862  				generateTestSearchPathFunc(63),
  9863  				generateTestSearchPathFunc(63),
  9864  				generateTestSearchPathFunc(63),
  9865  				generateTestSearchPathFunc(63),
  9866  				generateTestSearchPathFunc(63),
  9867  				generateTestSearchPathFunc(63),
  9868  				generateTestSearchPathFunc(63),
  9869  				generateTestSearchPathFunc(63),
  9870  				generateTestSearchPathFunc(63),
  9871  				generateTestSearchPathFunc(63),
  9872  				generateTestSearchPathFunc(63),
  9873  				generateTestSearchPathFunc(63),
  9874  				generateTestSearchPathFunc(63),
  9875  				generateTestSearchPathFunc(63),
  9876  				generateTestSearchPathFunc(63),
  9877  				generateTestSearchPathFunc(63),
  9878  				generateTestSearchPathFunc(63),
  9879  				generateTestSearchPathFunc(63),
  9880  				generateTestSearchPathFunc(63),
  9881  				generateTestSearchPathFunc(63),
  9882  				generateTestSearchPathFunc(63),
  9883  			},
  9884  		},
  9885  		expectedError: true,
  9886  	}, {
  9887  		desc: "invalid search path",
  9888  		dnsConfig: &core.PodDNSConfig{
  9889  			Searches: []string{"custom?"},
  9890  		},
  9891  		expectedError: true,
  9892  	}, {
  9893  		desc: "invalid nameserver",
  9894  		dnsConfig: &core.PodDNSConfig{
  9895  			Nameservers: []string{"invalid"},
  9896  		},
  9897  		expectedError: true,
  9898  	}, {
  9899  		desc: "invalid empty option name",
  9900  		dnsConfig: &core.PodDNSConfig{
  9901  			Options: []core.PodDNSConfigOption{
  9902  				{Value: &testOptionValue},
  9903  			},
  9904  		},
  9905  		expectedError: true,
  9906  	}, {
  9907  		desc: "invalid: DNSNone with 0 nameserver",
  9908  		dnsConfig: &core.PodDNSConfig{
  9909  			Searches: []string{"custom"},
  9910  		},
  9911  		dnsPolicy:     &testDNSNone,
  9912  		expectedError: true,
  9913  	},
  9914  	}
  9915  
  9916  	for _, tc := range testCases {
  9917  		if tc.dnsPolicy == nil {
  9918  			tc.dnsPolicy = &testDNSClusterFirst
  9919  		}
  9920  
  9921  		errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"), tc.opts)
  9922  		if len(errs) != 0 && !tc.expectedError {
  9923  			t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
  9924  		} else if len(errs) == 0 && tc.expectedError {
  9925  			t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
  9926  		}
  9927  	}
  9928  }
  9929  
  9930  func TestValidatePodReadinessGates(t *testing.T) {
  9931  	successCases := []struct {
  9932  		desc           string
  9933  		readinessGates []core.PodReadinessGate
  9934  	}{{
  9935  		"no gate",
  9936  		[]core.PodReadinessGate{},
  9937  	}, {
  9938  		"one readiness gate",
  9939  		[]core.PodReadinessGate{{
  9940  			ConditionType: core.PodConditionType("example.com/condition"),
  9941  		}},
  9942  	}, {
  9943  		"two readiness gates",
  9944  		[]core.PodReadinessGate{{
  9945  			ConditionType: core.PodConditionType("example.com/condition1"),
  9946  		}, {
  9947  			ConditionType: core.PodConditionType("example.com/condition2"),
  9948  		}},
  9949  	},
  9950  	}
  9951  	for _, tc := range successCases {
  9952  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
  9953  			t.Errorf("expect tc %q to success: %v", tc.desc, errs)
  9954  		}
  9955  	}
  9956  
  9957  	errorCases := []struct {
  9958  		desc           string
  9959  		readinessGates []core.PodReadinessGate
  9960  	}{{
  9961  		"invalid condition type",
  9962  		[]core.PodReadinessGate{{
  9963  			ConditionType: core.PodConditionType("invalid/condition/type"),
  9964  		}},
  9965  	},
  9966  	}
  9967  	for _, tc := range errorCases {
  9968  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
  9969  			t.Errorf("expected tc %q to fail", tc.desc)
  9970  		}
  9971  	}
  9972  }
  9973  
  9974  func TestValidatePodConditions(t *testing.T) {
  9975  	successCases := []struct {
  9976  		desc          string
  9977  		podConditions []core.PodCondition
  9978  	}{{
  9979  		"no condition",
  9980  		[]core.PodCondition{},
  9981  	}, {
  9982  		"one system condition",
  9983  		[]core.PodCondition{{
  9984  			Type:   core.PodReady,
  9985  			Status: core.ConditionTrue,
  9986  		}},
  9987  	}, {
  9988  		"one system condition and one custom condition",
  9989  		[]core.PodCondition{{
  9990  			Type:   core.PodReady,
  9991  			Status: core.ConditionTrue,
  9992  		}, {
  9993  			Type:   core.PodConditionType("example.com/condition"),
  9994  			Status: core.ConditionFalse,
  9995  		}},
  9996  	}, {
  9997  		"two custom condition",
  9998  		[]core.PodCondition{{
  9999  			Type:   core.PodConditionType("foobar"),
 10000  			Status: core.ConditionTrue,
 10001  		}, {
 10002  			Type:   core.PodConditionType("example.com/condition"),
 10003  			Status: core.ConditionFalse,
 10004  		}},
 10005  	},
 10006  	}
 10007  
 10008  	for _, tc := range successCases {
 10009  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
 10010  			t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
 10011  		}
 10012  	}
 10013  
 10014  	errorCases := []struct {
 10015  		desc          string
 10016  		podConditions []core.PodCondition
 10017  	}{{
 10018  		"one system condition and a invalid custom condition",
 10019  		[]core.PodCondition{{
 10020  			Type:   core.PodReady,
 10021  			Status: core.ConditionStatus("True"),
 10022  		}, {
 10023  			Type:   core.PodConditionType("invalid/custom/condition"),
 10024  			Status: core.ConditionStatus("True"),
 10025  		}},
 10026  	},
 10027  	}
 10028  	for _, tc := range errorCases {
 10029  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
 10030  			t.Errorf("expected tc %q to fail", tc.desc)
 10031  		}
 10032  	}
 10033  }
 10034  
 10035  func TestValidatePodSpec(t *testing.T) {
 10036  	activeDeadlineSeconds := int64(30)
 10037  	activeDeadlineSecondsMax := int64(math.MaxInt32)
 10038  
 10039  	minUserID := int64(0)
 10040  	maxUserID := int64(2147483647)
 10041  	minGroupID := int64(0)
 10042  	maxGroupID := int64(2147483647)
 10043  	goodfsGroupChangePolicy := core.FSGroupChangeAlways
 10044  	badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
 10045  	badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
 10046  
 10047  	successCases := map[string]core.PodSpec{
 10048  		"populate basic fields, leave defaults for most": {
 10049  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10050  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10051  			RestartPolicy: core.RestartPolicyAlways,
 10052  			DNSPolicy:     core.DNSClusterFirst,
 10053  		},
 10054  		"populate all fields": {
 10055  			Volumes: []core.Volume{
 10056  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10057  			},
 10058  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10059  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10060  			RestartPolicy:  core.RestartPolicyAlways,
 10061  			NodeSelector: map[string]string{
 10062  				"key": "value",
 10063  			},
 10064  			NodeName:              "foobar",
 10065  			DNSPolicy:             core.DNSClusterFirst,
 10066  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
 10067  			ServiceAccountName:    "acct",
 10068  		},
 10069  		"populate all fields with larger active deadline": {
 10070  			Volumes: []core.Volume{
 10071  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10072  			},
 10073  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10074  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10075  			RestartPolicy:  core.RestartPolicyAlways,
 10076  			NodeSelector: map[string]string{
 10077  				"key": "value",
 10078  			},
 10079  			NodeName:              "foobar",
 10080  			DNSPolicy:             core.DNSClusterFirst,
 10081  			ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
 10082  			ServiceAccountName:    "acct",
 10083  		},
 10084  		"populate HostNetwork": {
 10085  			Containers: []core.Container{
 10086  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10087  					Ports: []core.ContainerPort{
 10088  						{HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
 10089  				},
 10090  			},
 10091  			SecurityContext: &core.PodSecurityContext{
 10092  				HostNetwork: true,
 10093  			},
 10094  			RestartPolicy: core.RestartPolicyAlways,
 10095  			DNSPolicy:     core.DNSClusterFirst,
 10096  		},
 10097  		"populate RunAsUser SupplementalGroups FSGroup with minID 0": {
 10098  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10099  			SecurityContext: &core.PodSecurityContext{
 10100  				SupplementalGroups: []int64{minGroupID},
 10101  				RunAsUser:          &minUserID,
 10102  				FSGroup:            &minGroupID,
 10103  			},
 10104  			RestartPolicy: core.RestartPolicyAlways,
 10105  			DNSPolicy:     core.DNSClusterFirst,
 10106  		},
 10107  		"populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
 10108  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10109  			SecurityContext: &core.PodSecurityContext{
 10110  				SupplementalGroups: []int64{maxGroupID},
 10111  				RunAsUser:          &maxUserID,
 10112  				FSGroup:            &maxGroupID,
 10113  			},
 10114  			RestartPolicy: core.RestartPolicyAlways,
 10115  			DNSPolicy:     core.DNSClusterFirst,
 10116  		},
 10117  		"populate HostIPC": {
 10118  			SecurityContext: &core.PodSecurityContext{
 10119  				HostIPC: true,
 10120  			},
 10121  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10122  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10123  			RestartPolicy: core.RestartPolicyAlways,
 10124  			DNSPolicy:     core.DNSClusterFirst,
 10125  		},
 10126  		"populate HostPID": {
 10127  			SecurityContext: &core.PodSecurityContext{
 10128  				HostPID: true,
 10129  			},
 10130  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10131  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10132  			RestartPolicy: core.RestartPolicyAlways,
 10133  			DNSPolicy:     core.DNSClusterFirst,
 10134  		},
 10135  		"populate Affinity": {
 10136  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10137  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10138  			RestartPolicy: core.RestartPolicyAlways,
 10139  			DNSPolicy:     core.DNSClusterFirst,
 10140  		},
 10141  		"populate HostAliases": {
 10142  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
 10143  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10144  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10145  			RestartPolicy: core.RestartPolicyAlways,
 10146  			DNSPolicy:     core.DNSClusterFirst,
 10147  		},
 10148  		"populate HostAliases with `foo.bar` hostnames": {
 10149  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
 10150  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10151  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10152  			RestartPolicy: core.RestartPolicyAlways,
 10153  			DNSPolicy:     core.DNSClusterFirst,
 10154  		},
 10155  		"populate HostAliases with HostNetwork": {
 10156  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
 10157  			Containers:  []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10158  			SecurityContext: &core.PodSecurityContext{
 10159  				HostNetwork: true,
 10160  			},
 10161  			RestartPolicy: core.RestartPolicyAlways,
 10162  			DNSPolicy:     core.DNSClusterFirst,
 10163  		},
 10164  		"populate PriorityClassName": {
 10165  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10166  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10167  			RestartPolicy:     core.RestartPolicyAlways,
 10168  			DNSPolicy:         core.DNSClusterFirst,
 10169  			PriorityClassName: "valid-name",
 10170  		},
 10171  		"populate ShareProcessNamespace": {
 10172  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10173  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10174  			RestartPolicy: core.RestartPolicyAlways,
 10175  			DNSPolicy:     core.DNSClusterFirst,
 10176  			SecurityContext: &core.PodSecurityContext{
 10177  				ShareProcessNamespace: &[]bool{true}[0],
 10178  			},
 10179  		},
 10180  		"populate RuntimeClassName": {
 10181  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10182  			RestartPolicy:    core.RestartPolicyAlways,
 10183  			DNSPolicy:        core.DNSClusterFirst,
 10184  			RuntimeClassName: utilpointer.String("valid-sandbox"),
 10185  		},
 10186  		"populate Overhead": {
 10187  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10188  			RestartPolicy:    core.RestartPolicyAlways,
 10189  			DNSPolicy:        core.DNSClusterFirst,
 10190  			RuntimeClassName: utilpointer.String("valid-sandbox"),
 10191  			Overhead:         core.ResourceList{},
 10192  		},
 10193  		"populate DNSPolicy": {
 10194  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10195  			SecurityContext: &core.PodSecurityContext{
 10196  				FSGroupChangePolicy: &goodfsGroupChangePolicy,
 10197  			},
 10198  			RestartPolicy: core.RestartPolicyAlways,
 10199  			DNSPolicy:     core.DNSClusterFirst,
 10200  		},
 10201  	}
 10202  	for k, v := range successCases {
 10203  		t.Run(k, func(t *testing.T) {
 10204  			opts := PodValidationOptions{
 10205  				ResourceIsPod: true,
 10206  			}
 10207  			if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 {
 10208  				t.Errorf("expected success: %v", errs)
 10209  			}
 10210  		})
 10211  	}
 10212  
 10213  	activeDeadlineSeconds = int64(0)
 10214  	activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1)
 10215  
 10216  	minUserID = int64(-1)
 10217  	maxUserID = int64(2147483648)
 10218  	minGroupID = int64(-1)
 10219  	maxGroupID = int64(2147483648)
 10220  
 10221  	failureCases := map[string]core.PodSpec{
 10222  		"bad volume": {
 10223  			Volumes:       []core.Volume{{}},
 10224  			RestartPolicy: core.RestartPolicyAlways,
 10225  			DNSPolicy:     core.DNSClusterFirst,
 10226  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10227  		},
 10228  		"no containers": {
 10229  			RestartPolicy: core.RestartPolicyAlways,
 10230  			DNSPolicy:     core.DNSClusterFirst,
 10231  		},
 10232  		"bad container": {
 10233  			Containers:    []core.Container{{}},
 10234  			RestartPolicy: core.RestartPolicyAlways,
 10235  			DNSPolicy:     core.DNSClusterFirst,
 10236  		},
 10237  		"bad init container": {
 10238  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10239  			InitContainers: []core.Container{{}},
 10240  			RestartPolicy:  core.RestartPolicyAlways,
 10241  			DNSPolicy:      core.DNSClusterFirst,
 10242  		},
 10243  		"bad DNS policy": {
 10244  			DNSPolicy:     core.DNSPolicy("invalid"),
 10245  			RestartPolicy: core.RestartPolicyAlways,
 10246  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10247  		},
 10248  		"bad service account name": {
 10249  			Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10250  			RestartPolicy:      core.RestartPolicyAlways,
 10251  			DNSPolicy:          core.DNSClusterFirst,
 10252  			ServiceAccountName: "invalidName",
 10253  		},
 10254  		"bad restart policy": {
 10255  			RestartPolicy: "UnknowPolicy",
 10256  			DNSPolicy:     core.DNSClusterFirst,
 10257  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10258  		},
 10259  		"with hostNetwork hostPort unspecified": {
 10260  			Containers: []core.Container{
 10261  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
 10262  					{HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}},
 10263  				},
 10264  			},
 10265  			SecurityContext: &core.PodSecurityContext{
 10266  				HostNetwork: true,
 10267  			},
 10268  			RestartPolicy: core.RestartPolicyAlways,
 10269  			DNSPolicy:     core.DNSClusterFirst,
 10270  		},
 10271  		"with hostNetwork hostPort not equal to containerPort": {
 10272  			Containers: []core.Container{
 10273  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
 10274  					{HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}},
 10275  				},
 10276  			},
 10277  			SecurityContext: &core.PodSecurityContext{
 10278  				HostNetwork: true,
 10279  			},
 10280  			RestartPolicy: core.RestartPolicyAlways,
 10281  			DNSPolicy:     core.DNSClusterFirst,
 10282  		},
 10283  		"with hostAliases with invalid IP": {
 10284  			SecurityContext: &core.PodSecurityContext{
 10285  				HostNetwork: false,
 10286  			},
 10287  			HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}},
 10288  		},
 10289  		"with hostAliases with invalid hostname": {
 10290  			SecurityContext: &core.PodSecurityContext{
 10291  				HostNetwork: false,
 10292  			},
 10293  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}},
 10294  		},
 10295  		"bad supplementalGroups large than math.MaxInt32": {
 10296  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10297  			SecurityContext: &core.PodSecurityContext{
 10298  				SupplementalGroups: []int64{maxGroupID, 1234},
 10299  			},
 10300  			RestartPolicy: core.RestartPolicyAlways,
 10301  			DNSPolicy:     core.DNSClusterFirst,
 10302  		},
 10303  		"bad supplementalGroups less than 0": {
 10304  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10305  			SecurityContext: &core.PodSecurityContext{
 10306  				SupplementalGroups: []int64{minGroupID, 1234},
 10307  			},
 10308  			RestartPolicy: core.RestartPolicyAlways,
 10309  			DNSPolicy:     core.DNSClusterFirst,
 10310  		},
 10311  		"bad runAsUser large than math.MaxInt32": {
 10312  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10313  			SecurityContext: &core.PodSecurityContext{
 10314  				RunAsUser: &maxUserID,
 10315  			},
 10316  			RestartPolicy: core.RestartPolicyAlways,
 10317  			DNSPolicy:     core.DNSClusterFirst,
 10318  		},
 10319  		"bad runAsUser less than 0": {
 10320  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10321  			SecurityContext: &core.PodSecurityContext{
 10322  				RunAsUser: &minUserID,
 10323  			},
 10324  			RestartPolicy: core.RestartPolicyAlways,
 10325  			DNSPolicy:     core.DNSClusterFirst,
 10326  		},
 10327  		"bad fsGroup large than math.MaxInt32": {
 10328  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10329  			SecurityContext: &core.PodSecurityContext{
 10330  				FSGroup: &maxGroupID,
 10331  			},
 10332  			RestartPolicy: core.RestartPolicyAlways,
 10333  			DNSPolicy:     core.DNSClusterFirst,
 10334  		},
 10335  		"bad fsGroup less than 0": {
 10336  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10337  			SecurityContext: &core.PodSecurityContext{
 10338  				FSGroup: &minGroupID,
 10339  			},
 10340  			RestartPolicy: core.RestartPolicyAlways,
 10341  			DNSPolicy:     core.DNSClusterFirst,
 10342  		},
 10343  		"bad-active-deadline-seconds": {
 10344  			Volumes: []core.Volume{
 10345  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10346  			},
 10347  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10348  			RestartPolicy: core.RestartPolicyAlways,
 10349  			NodeSelector: map[string]string{
 10350  				"key": "value",
 10351  			},
 10352  			NodeName:              "foobar",
 10353  			DNSPolicy:             core.DNSClusterFirst,
 10354  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
 10355  		},
 10356  		"active-deadline-seconds-too-large": {
 10357  			Volumes: []core.Volume{
 10358  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10359  			},
 10360  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10361  			RestartPolicy: core.RestartPolicyAlways,
 10362  			NodeSelector: map[string]string{
 10363  				"key": "value",
 10364  			},
 10365  			NodeName:              "foobar",
 10366  			DNSPolicy:             core.DNSClusterFirst,
 10367  			ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge,
 10368  		},
 10369  		"bad nodeName": {
 10370  			NodeName:      "node name",
 10371  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10372  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10373  			RestartPolicy: core.RestartPolicyAlways,
 10374  			DNSPolicy:     core.DNSClusterFirst,
 10375  		},
 10376  		"bad PriorityClassName": {
 10377  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10378  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10379  			RestartPolicy:     core.RestartPolicyAlways,
 10380  			DNSPolicy:         core.DNSClusterFirst,
 10381  			PriorityClassName: "InvalidName",
 10382  		},
 10383  		"ShareProcessNamespace and HostPID both set": {
 10384  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10385  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10386  			RestartPolicy: core.RestartPolicyAlways,
 10387  			DNSPolicy:     core.DNSClusterFirst,
 10388  			SecurityContext: &core.PodSecurityContext{
 10389  				HostPID:               true,
 10390  				ShareProcessNamespace: &[]bool{true}[0],
 10391  			},
 10392  		},
 10393  		"bad RuntimeClassName": {
 10394  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10395  			RestartPolicy:    core.RestartPolicyAlways,
 10396  			DNSPolicy:        core.DNSClusterFirst,
 10397  			RuntimeClassName: utilpointer.String("invalid/sandbox"),
 10398  		},
 10399  		"bad empty fsGroupchangepolicy": {
 10400  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10401  			SecurityContext: &core.PodSecurityContext{
 10402  				FSGroupChangePolicy: &badfsGroupChangePolicy2,
 10403  			},
 10404  			RestartPolicy: core.RestartPolicyAlways,
 10405  			DNSPolicy:     core.DNSClusterFirst,
 10406  		},
 10407  		"bad invalid fsgroupchangepolicy": {
 10408  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10409  			SecurityContext: &core.PodSecurityContext{
 10410  				FSGroupChangePolicy: &badfsGroupChangePolicy1,
 10411  			},
 10412  			RestartPolicy: core.RestartPolicyAlways,
 10413  			DNSPolicy:     core.DNSClusterFirst,
 10414  		},
 10415  		"disallowed resources resize policy for init containers": {
 10416  			InitContainers: []core.Container{{
 10417  				Name:  "initctr",
 10418  				Image: "initimage",
 10419  				ResizePolicy: []core.ContainerResizePolicy{
 10420  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
 10421  				},
 10422  				ImagePullPolicy:          "IfNotPresent",
 10423  				TerminationMessagePolicy: "File",
 10424  			}},
 10425  			Containers: []core.Container{{
 10426  				Name:  "ctr",
 10427  				Image: "image",
 10428  				ResizePolicy: []core.ContainerResizePolicy{
 10429  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
 10430  				},
 10431  				ImagePullPolicy:          "IfNotPresent",
 10432  				TerminationMessagePolicy: "File",
 10433  			}},
 10434  			RestartPolicy: core.RestartPolicyAlways,
 10435  			DNSPolicy:     core.DNSClusterFirst,
 10436  		},
 10437  	}
 10438  	for k, v := range failureCases {
 10439  		opts := PodValidationOptions{
 10440  			ResourceIsPod: true,
 10441  		}
 10442  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 {
 10443  			t.Errorf("expected failure for %q", k)
 10444  		}
 10445  	}
 10446  }
 10447  
 10448  func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
 10449  	var out core.PodSpec
 10450  	out.Containers = in.Containers
 10451  	out.RestartPolicy = in.RestartPolicy
 10452  	out.DNSPolicy = in.DNSPolicy
 10453  	out.Tolerations = tolerations
 10454  	return out
 10455  }
 10456  
 10457  func TestValidatePod(t *testing.T) {
 10458  	validPodSpec := func(affinity *core.Affinity) core.PodSpec {
 10459  		spec := core.PodSpec{
 10460  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10461  			RestartPolicy: core.RestartPolicyAlways,
 10462  			DNSPolicy:     core.DNSClusterFirst,
 10463  		}
 10464  		if affinity != nil {
 10465  			spec.Affinity = affinity
 10466  		}
 10467  		return spec
 10468  	}
 10469  	validPVCSpec := core.PersistentVolumeClaimSpec{
 10470  		AccessModes: []core.PersistentVolumeAccessMode{
 10471  			core.ReadWriteOnce,
 10472  		},
 10473  		Resources: core.VolumeResourceRequirements{
 10474  			Requests: core.ResourceList{
 10475  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 10476  			},
 10477  		},
 10478  	}
 10479  	validPVCTemplate := core.PersistentVolumeClaimTemplate{
 10480  		Spec: validPVCSpec,
 10481  	}
 10482  	longPodName := strings.Repeat("a", 200)
 10483  	longVolName := strings.Repeat("b", 60)
 10484  
 10485  	successCases := map[string]core.Pod{
 10486  		"basic fields": {
 10487  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10488  			Spec: core.PodSpec{
 10489  				Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
 10490  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10491  				RestartPolicy: core.RestartPolicyAlways,
 10492  				DNSPolicy:     core.DNSClusterFirst,
 10493  			},
 10494  		},
 10495  		"just about everything": {
 10496  			ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
 10497  			Spec: core.PodSpec{
 10498  				Volumes: []core.Volume{
 10499  					{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
 10500  				},
 10501  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10502  				RestartPolicy: core.RestartPolicyAlways,
 10503  				DNSPolicy:     core.DNSClusterFirst,
 10504  				NodeSelector: map[string]string{
 10505  					"key": "value",
 10506  				},
 10507  				NodeName: "foobar",
 10508  			},
 10509  		},
 10510  		"serialized node affinity requirements": {
 10511  			ObjectMeta: metav1.ObjectMeta{
 10512  				Name:      "123",
 10513  				Namespace: "ns",
 10514  			},
 10515  			Spec: validPodSpec(
 10516  				// TODO: Uncomment and move this block and move inside NodeAffinity once
 10517  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10518  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
 10519  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
 10520  				//				{
 10521  				//					MatchExpressions: []core.NodeSelectorRequirement{
 10522  				//						{
 10523  				//							Key: "key1",
 10524  				//							Operator: core.NodeSelectorOpExists
 10525  				//						},
 10526  				//					},
 10527  				//				},
 10528  				//			},
 10529  				//		},
 10530  				&core.Affinity{
 10531  					NodeAffinity: &core.NodeAffinity{
 10532  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10533  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10534  								MatchExpressions: []core.NodeSelectorRequirement{{
 10535  									Key:      "key2",
 10536  									Operator: core.NodeSelectorOpIn,
 10537  									Values:   []string{"value1", "value2"},
 10538  								}},
 10539  								MatchFields: []core.NodeSelectorRequirement{{
 10540  									Key:      "metadata.name",
 10541  									Operator: core.NodeSelectorOpIn,
 10542  									Values:   []string{"host1"},
 10543  								}},
 10544  							}},
 10545  						},
 10546  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 10547  							Weight: 10,
 10548  							Preference: core.NodeSelectorTerm{
 10549  								MatchExpressions: []core.NodeSelectorRequirement{{
 10550  									Key:      "foo",
 10551  									Operator: core.NodeSelectorOpIn,
 10552  									Values:   []string{"bar"},
 10553  								}},
 10554  							},
 10555  						}},
 10556  					},
 10557  				},
 10558  			),
 10559  		},
 10560  		"serialized node affinity requirements, II": {
 10561  			ObjectMeta: metav1.ObjectMeta{
 10562  				Name:      "123",
 10563  				Namespace: "ns",
 10564  			},
 10565  			Spec: validPodSpec(
 10566  				// TODO: Uncomment and move this block and move inside NodeAffinity once
 10567  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10568  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
 10569  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
 10570  				//				{
 10571  				//					MatchExpressions: []core.NodeSelectorRequirement{
 10572  				//						{
 10573  				//							Key: "key1",
 10574  				//							Operator: core.NodeSelectorOpExists
 10575  				//						},
 10576  				//					},
 10577  				//				},
 10578  				//			},
 10579  				//		},
 10580  				&core.Affinity{
 10581  					NodeAffinity: &core.NodeAffinity{
 10582  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10583  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10584  								MatchExpressions: []core.NodeSelectorRequirement{},
 10585  							}},
 10586  						},
 10587  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 10588  							Weight: 10,
 10589  							Preference: core.NodeSelectorTerm{
 10590  								MatchExpressions: []core.NodeSelectorRequirement{},
 10591  							},
 10592  						}},
 10593  					},
 10594  				},
 10595  			),
 10596  		},
 10597  		"serialized pod affinity in affinity requirements in annotations": {
 10598  			ObjectMeta: metav1.ObjectMeta{
 10599  				Name:      "123",
 10600  				Namespace: "ns",
 10601  				// TODO: Uncomment and move this block into Annotations map once
 10602  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10603  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
 10604  				//			"labelSelector": {
 10605  				//				"matchExpressions": [{
 10606  				//					"key": "key2",
 10607  				//					"operator": "In",
 10608  				//					"values": ["value1", "value2"]
 10609  				//				}]
 10610  				//			},
 10611  				//			"namespaces":["ns"],
 10612  				//			"topologyKey": "zone"
 10613  				//		}]
 10614  			},
 10615  			Spec: validPodSpec(&core.Affinity{
 10616  				PodAffinity: &core.PodAffinity{
 10617  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10618  						LabelSelector: &metav1.LabelSelector{
 10619  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10620  								Key:      "key2",
 10621  								Operator: metav1.LabelSelectorOpIn,
 10622  								Values:   []string{"value1", "value2"},
 10623  							}},
 10624  						},
 10625  						TopologyKey: "zone",
 10626  						Namespaces:  []string{"ns"},
 10627  						NamespaceSelector: &metav1.LabelSelector{
 10628  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10629  								Key:      "key",
 10630  								Operator: metav1.LabelSelectorOpIn,
 10631  								Values:   []string{"value1", "value2"},
 10632  							}},
 10633  						},
 10634  					}},
 10635  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10636  						Weight: 10,
 10637  						PodAffinityTerm: core.PodAffinityTerm{
 10638  							LabelSelector: &metav1.LabelSelector{
 10639  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10640  									Key:      "key2",
 10641  									Operator: metav1.LabelSelectorOpNotIn,
 10642  									Values:   []string{"value1", "value2"},
 10643  								}},
 10644  							},
 10645  							Namespaces:  []string{"ns"},
 10646  							TopologyKey: "region",
 10647  						},
 10648  					}},
 10649  				},
 10650  			}),
 10651  		},
 10652  		"serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
 10653  			ObjectMeta: metav1.ObjectMeta{
 10654  				Name:      "123",
 10655  				Namespace: "ns",
 10656  				// TODO: Uncomment and move this block into Annotations map once
 10657  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10658  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
 10659  				//			"labelSelector": {
 10660  				//				"matchExpressions": [{
 10661  				//					"key": "key2",
 10662  				//					"operator": "In",
 10663  				//					"values": ["value1", "value2"]
 10664  				//				}]
 10665  				//			},
 10666  				//			"namespaces":["ns"],
 10667  				//			"topologyKey": "zone"
 10668  				//		}]
 10669  			},
 10670  			Spec: validPodSpec(&core.Affinity{
 10671  				PodAntiAffinity: &core.PodAntiAffinity{
 10672  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10673  						LabelSelector: &metav1.LabelSelector{
 10674  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10675  								Key:      "key2",
 10676  								Operator: metav1.LabelSelectorOpExists,
 10677  							}},
 10678  						},
 10679  						TopologyKey: "zone",
 10680  						Namespaces:  []string{"ns"},
 10681  					}},
 10682  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10683  						Weight: 10,
 10684  						PodAffinityTerm: core.PodAffinityTerm{
 10685  							LabelSelector: &metav1.LabelSelector{
 10686  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10687  									Key:      "key2",
 10688  									Operator: metav1.LabelSelectorOpDoesNotExist,
 10689  								}},
 10690  							},
 10691  							Namespaces:  []string{"ns"},
 10692  							TopologyKey: "region",
 10693  						},
 10694  					}},
 10695  				},
 10696  			}),
 10697  		},
 10698  		"populate forgiveness tolerations with exists operator in annotations.": {
 10699  			ObjectMeta: metav1.ObjectMeta{
 10700  				Name:      "123",
 10701  				Namespace: "ns",
 10702  			},
 10703  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10704  		},
 10705  		"populate forgiveness tolerations with equal operator in annotations.": {
 10706  			ObjectMeta: metav1.ObjectMeta{
 10707  				Name:      "123",
 10708  				Namespace: "ns",
 10709  			},
 10710  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10711  		},
 10712  		"populate tolerations equal operator in annotations.": {
 10713  			ObjectMeta: metav1.ObjectMeta{
 10714  				Name:      "123",
 10715  				Namespace: "ns",
 10716  			},
 10717  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 10718  		},
 10719  		"populate tolerations exists operator in annotations.": {
 10720  			ObjectMeta: metav1.ObjectMeta{
 10721  				Name:      "123",
 10722  				Namespace: "ns",
 10723  			},
 10724  			Spec: validPodSpec(nil),
 10725  		},
 10726  		"empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
 10727  			ObjectMeta: metav1.ObjectMeta{
 10728  				Name:      "123",
 10729  				Namespace: "ns",
 10730  			},
 10731  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
 10732  		},
 10733  		"empty operator is OK for toleration, defaults to Equal.": {
 10734  			ObjectMeta: metav1.ObjectMeta{
 10735  				Name:      "123",
 10736  				Namespace: "ns",
 10737  			},
 10738  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
 10739  		},
 10740  		"empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
 10741  			ObjectMeta: metav1.ObjectMeta{
 10742  				Name:      "123",
 10743  				Namespace: "ns",
 10744  			},
 10745  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
 10746  		},
 10747  		"negative tolerationSeconds is OK for toleration.": {
 10748  			ObjectMeta: metav1.ObjectMeta{
 10749  				Name:      "pod-forgiveness-invalid",
 10750  				Namespace: "ns",
 10751  			},
 10752  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
 10753  		},
 10754  		"runtime default seccomp profile": {
 10755  			ObjectMeta: metav1.ObjectMeta{
 10756  				Name:      "123",
 10757  				Namespace: "ns",
 10758  				Annotations: map[string]string{
 10759  					core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault,
 10760  				},
 10761  			},
 10762  			Spec: validPodSpec(nil),
 10763  		},
 10764  		"docker default seccomp profile": {
 10765  			ObjectMeta: metav1.ObjectMeta{
 10766  				Name:      "123",
 10767  				Namespace: "ns",
 10768  				Annotations: map[string]string{
 10769  					core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault,
 10770  				},
 10771  			},
 10772  			Spec: validPodSpec(nil),
 10773  		},
 10774  		"unconfined seccomp profile": {
 10775  			ObjectMeta: metav1.ObjectMeta{
 10776  				Name:      "123",
 10777  				Namespace: "ns",
 10778  				Annotations: map[string]string{
 10779  					core.SeccompPodAnnotationKey: "unconfined",
 10780  				},
 10781  			},
 10782  			Spec: validPodSpec(nil),
 10783  		},
 10784  		"localhost seccomp profile": {
 10785  			ObjectMeta: metav1.ObjectMeta{
 10786  				Name:      "123",
 10787  				Namespace: "ns",
 10788  				Annotations: map[string]string{
 10789  					core.SeccompPodAnnotationKey: "localhost/foo",
 10790  				},
 10791  			},
 10792  			Spec: validPodSpec(nil),
 10793  		},
 10794  		"localhost seccomp profile for a container": {
 10795  			ObjectMeta: metav1.ObjectMeta{
 10796  				Name:      "123",
 10797  				Namespace: "ns",
 10798  				Annotations: map[string]string{
 10799  					core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo",
 10800  				},
 10801  			},
 10802  			Spec: validPodSpec(nil),
 10803  		},
 10804  		"runtime default seccomp profile for a pod": {
 10805  			ObjectMeta: metav1.ObjectMeta{
 10806  				Name:      "123",
 10807  				Namespace: "ns",
 10808  			},
 10809  			Spec: core.PodSpec{
 10810  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10811  				RestartPolicy: core.RestartPolicyAlways,
 10812  				DNSPolicy:     core.DNSDefault,
 10813  				SecurityContext: &core.PodSecurityContext{
 10814  					SeccompProfile: &core.SeccompProfile{
 10815  						Type: core.SeccompProfileTypeRuntimeDefault,
 10816  					},
 10817  				},
 10818  			},
 10819  		},
 10820  		"runtime default seccomp profile for a container": {
 10821  			ObjectMeta: metav1.ObjectMeta{
 10822  				Name:      "123",
 10823  				Namespace: "ns",
 10824  			},
 10825  			Spec: core.PodSpec{
 10826  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10827  					SecurityContext: &core.SecurityContext{
 10828  						SeccompProfile: &core.SeccompProfile{
 10829  							Type: core.SeccompProfileTypeRuntimeDefault,
 10830  						},
 10831  					},
 10832  				}},
 10833  				RestartPolicy: core.RestartPolicyAlways,
 10834  				DNSPolicy:     core.DNSDefault,
 10835  			},
 10836  		},
 10837  		"unconfined seccomp profile for a pod": {
 10838  			ObjectMeta: metav1.ObjectMeta{
 10839  				Name:      "123",
 10840  				Namespace: "ns",
 10841  			},
 10842  			Spec: core.PodSpec{
 10843  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10844  				RestartPolicy: core.RestartPolicyAlways,
 10845  				DNSPolicy:     core.DNSDefault,
 10846  				SecurityContext: &core.PodSecurityContext{
 10847  					SeccompProfile: &core.SeccompProfile{
 10848  						Type: core.SeccompProfileTypeUnconfined,
 10849  					},
 10850  				},
 10851  			},
 10852  		},
 10853  		"unconfined seccomp profile for a container": {
 10854  			ObjectMeta: metav1.ObjectMeta{
 10855  				Name:      "123",
 10856  				Namespace: "ns",
 10857  			},
 10858  			Spec: core.PodSpec{
 10859  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10860  					SecurityContext: &core.SecurityContext{
 10861  						SeccompProfile: &core.SeccompProfile{
 10862  							Type: core.SeccompProfileTypeUnconfined,
 10863  						},
 10864  					},
 10865  				}},
 10866  				RestartPolicy: core.RestartPolicyAlways,
 10867  				DNSPolicy:     core.DNSDefault,
 10868  			},
 10869  		},
 10870  		"localhost seccomp profile for a pod": {
 10871  			ObjectMeta: metav1.ObjectMeta{
 10872  				Name:      "123",
 10873  				Namespace: "ns",
 10874  			},
 10875  			Spec: core.PodSpec{
 10876  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10877  				RestartPolicy: core.RestartPolicyAlways,
 10878  				DNSPolicy:     core.DNSDefault,
 10879  				SecurityContext: &core.PodSecurityContext{
 10880  					SeccompProfile: &core.SeccompProfile{
 10881  						Type:             core.SeccompProfileTypeLocalhost,
 10882  						LocalhostProfile: utilpointer.String("filename.json"),
 10883  					},
 10884  				},
 10885  			},
 10886  		},
 10887  		"localhost seccomp profile for a container, II": {
 10888  			ObjectMeta: metav1.ObjectMeta{
 10889  				Name:      "123",
 10890  				Namespace: "ns",
 10891  			},
 10892  			Spec: core.PodSpec{
 10893  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10894  					SecurityContext: &core.SecurityContext{
 10895  						SeccompProfile: &core.SeccompProfile{
 10896  							Type:             core.SeccompProfileTypeLocalhost,
 10897  							LocalhostProfile: utilpointer.String("filename.json"),
 10898  						},
 10899  					},
 10900  				}},
 10901  				RestartPolicy: core.RestartPolicyAlways,
 10902  				DNSPolicy:     core.DNSDefault,
 10903  			},
 10904  		},
 10905  		"default AppArmor annotation for a container": {
 10906  			ObjectMeta: metav1.ObjectMeta{
 10907  				Name:      "123",
 10908  				Namespace: "ns",
 10909  				Annotations: map[string]string{
 10910  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 10911  				},
 10912  			},
 10913  			Spec: validPodSpec(nil),
 10914  		},
 10915  		"default AppArmor annotation for an init container": {
 10916  			ObjectMeta: metav1.ObjectMeta{
 10917  				Name:      "123",
 10918  				Namespace: "ns",
 10919  				Annotations: map[string]string{
 10920  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 10921  				},
 10922  			},
 10923  			Spec: core.PodSpec{
 10924  				InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10925  				Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10926  				RestartPolicy:  core.RestartPolicyAlways,
 10927  				DNSPolicy:      core.DNSClusterFirst,
 10928  			},
 10929  		},
 10930  		"localhost AppArmor annotation for a container": {
 10931  			ObjectMeta: metav1.ObjectMeta{
 10932  				Name:      "123",
 10933  				Namespace: "ns",
 10934  				Annotations: map[string]string{
 10935  					v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo",
 10936  				},
 10937  			},
 10938  			Spec: validPodSpec(nil),
 10939  		},
 10940  		"runtime default AppArmor profile for a pod": {
 10941  			ObjectMeta: metav1.ObjectMeta{
 10942  				Name:      "123",
 10943  				Namespace: "ns",
 10944  			},
 10945  			Spec: core.PodSpec{
 10946  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10947  				RestartPolicy: core.RestartPolicyAlways,
 10948  				DNSPolicy:     core.DNSDefault,
 10949  				SecurityContext: &core.PodSecurityContext{
 10950  					AppArmorProfile: &core.AppArmorProfile{
 10951  						Type: core.AppArmorProfileTypeRuntimeDefault,
 10952  					},
 10953  				},
 10954  			},
 10955  		},
 10956  		"runtime default AppArmor profile for a container": {
 10957  			ObjectMeta: metav1.ObjectMeta{
 10958  				Name:      "123",
 10959  				Namespace: "ns",
 10960  			},
 10961  			Spec: core.PodSpec{
 10962  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10963  					SecurityContext: &core.SecurityContext{
 10964  						AppArmorProfile: &core.AppArmorProfile{
 10965  							Type: core.AppArmorProfileTypeRuntimeDefault,
 10966  						},
 10967  					},
 10968  				}},
 10969  				RestartPolicy: core.RestartPolicyAlways,
 10970  				DNSPolicy:     core.DNSDefault,
 10971  			},
 10972  		},
 10973  		"unconfined AppArmor profile for a pod": {
 10974  			ObjectMeta: metav1.ObjectMeta{
 10975  				Name:      "123",
 10976  				Namespace: "ns",
 10977  			},
 10978  			Spec: core.PodSpec{
 10979  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10980  				RestartPolicy: core.RestartPolicyAlways,
 10981  				DNSPolicy:     core.DNSDefault,
 10982  				SecurityContext: &core.PodSecurityContext{
 10983  					AppArmorProfile: &core.AppArmorProfile{
 10984  						Type: core.AppArmorProfileTypeUnconfined,
 10985  					},
 10986  				},
 10987  			},
 10988  		},
 10989  		"unconfined AppArmor profile for a container": {
 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  					SecurityContext: &core.SecurityContext{
 10997  						AppArmorProfile: &core.AppArmorProfile{
 10998  							Type: core.AppArmorProfileTypeUnconfined,
 10999  						},
 11000  					},
 11001  				}},
 11002  				RestartPolicy: core.RestartPolicyAlways,
 11003  				DNSPolicy:     core.DNSDefault,
 11004  			},
 11005  		},
 11006  		"localhost AppArmor profile for a pod": {
 11007  			ObjectMeta: metav1.ObjectMeta{
 11008  				Name:      "123",
 11009  				Namespace: "ns",
 11010  			},
 11011  			Spec: core.PodSpec{
 11012  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11013  				RestartPolicy: core.RestartPolicyAlways,
 11014  				DNSPolicy:     core.DNSDefault,
 11015  				SecurityContext: &core.PodSecurityContext{
 11016  					AppArmorProfile: &core.AppArmorProfile{
 11017  						Type:             core.AppArmorProfileTypeLocalhost,
 11018  						LocalhostProfile: ptr.To("example-org/application-foo"),
 11019  					},
 11020  				},
 11021  			},
 11022  		},
 11023  		"localhost AppArmor profile for a container field": {
 11024  			ObjectMeta: metav1.ObjectMeta{
 11025  				Name:      "123",
 11026  				Namespace: "ns",
 11027  			},
 11028  			Spec: core.PodSpec{
 11029  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 11030  					SecurityContext: &core.SecurityContext{
 11031  						AppArmorProfile: &core.AppArmorProfile{
 11032  							Type:             core.AppArmorProfileTypeLocalhost,
 11033  							LocalhostProfile: ptr.To("example-org/application-foo"),
 11034  						},
 11035  					},
 11036  				}},
 11037  				RestartPolicy: core.RestartPolicyAlways,
 11038  				DNSPolicy:     core.DNSDefault,
 11039  			},
 11040  		},
 11041  		"matching AppArmor fields and annotations": {
 11042  			ObjectMeta: metav1.ObjectMeta{
 11043  				Name:      "123",
 11044  				Namespace: "ns",
 11045  				Annotations: map[string]string{
 11046  					core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 11047  				},
 11048  			},
 11049  			Spec: core.PodSpec{
 11050  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 11051  					SecurityContext: &core.SecurityContext{
 11052  						AppArmorProfile: &core.AppArmorProfile{
 11053  							Type:             core.AppArmorProfileTypeLocalhost,
 11054  							LocalhostProfile: ptr.To("foo"),
 11055  						},
 11056  					},
 11057  				}},
 11058  				RestartPolicy: core.RestartPolicyAlways,
 11059  				DNSPolicy:     core.DNSDefault,
 11060  			},
 11061  		},
 11062  		"matching AppArmor pod field and annotations": {
 11063  			ObjectMeta: metav1.ObjectMeta{
 11064  				Name:      "123",
 11065  				Namespace: "ns",
 11066  				Annotations: map[string]string{
 11067  					core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 11068  				},
 11069  			},
 11070  			Spec: core.PodSpec{
 11071  				SecurityContext: &core.PodSecurityContext{
 11072  					AppArmorProfile: &core.AppArmorProfile{
 11073  						Type:             core.AppArmorProfileTypeLocalhost,
 11074  						LocalhostProfile: ptr.To("foo"),
 11075  					},
 11076  				},
 11077  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11078  				RestartPolicy: core.RestartPolicyAlways,
 11079  				DNSPolicy:     core.DNSDefault,
 11080  			},
 11081  		},
 11082  		"syntactically valid sysctls": {
 11083  			ObjectMeta: metav1.ObjectMeta{
 11084  				Name:      "123",
 11085  				Namespace: "ns",
 11086  			},
 11087  			Spec: core.PodSpec{
 11088  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11089  				RestartPolicy: core.RestartPolicyAlways,
 11090  				DNSPolicy:     core.DNSClusterFirst,
 11091  				SecurityContext: &core.PodSecurityContext{
 11092  					Sysctls: []core.Sysctl{{
 11093  						Name:  "kernel.shmmni",
 11094  						Value: "32768",
 11095  					}, {
 11096  						Name:  "kernel.shmmax",
 11097  						Value: "1000000000",
 11098  					}, {
 11099  						Name:  "knet.ipv4.route.min_pmtu",
 11100  						Value: "1000",
 11101  					}},
 11102  				},
 11103  			},
 11104  		},
 11105  		"valid extended resources for init container": {
 11106  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11107  			Spec: core.PodSpec{
 11108  				InitContainers: []core.Container{{
 11109  					Name:            "valid-extended",
 11110  					Image:           "image",
 11111  					ImagePullPolicy: "IfNotPresent",
 11112  					Resources: core.ResourceRequirements{
 11113  						Requests: core.ResourceList{
 11114  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11115  						},
 11116  						Limits: core.ResourceList{
 11117  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11118  						},
 11119  					},
 11120  					TerminationMessagePolicy: "File",
 11121  				}},
 11122  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11123  				RestartPolicy: core.RestartPolicyAlways,
 11124  				DNSPolicy:     core.DNSClusterFirst,
 11125  			},
 11126  		},
 11127  		"valid extended resources for regular container": {
 11128  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11129  			Spec: core.PodSpec{
 11130  				InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11131  				Containers: []core.Container{{
 11132  					Name:            "valid-extended",
 11133  					Image:           "image",
 11134  					ImagePullPolicy: "IfNotPresent",
 11135  					Resources: core.ResourceRequirements{
 11136  						Requests: core.ResourceList{
 11137  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11138  						},
 11139  						Limits: core.ResourceList{
 11140  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 11141  						},
 11142  					},
 11143  					TerminationMessagePolicy: "File",
 11144  				}},
 11145  				RestartPolicy: core.RestartPolicyAlways,
 11146  				DNSPolicy:     core.DNSClusterFirst,
 11147  			},
 11148  		},
 11149  		"valid serviceaccount token projected volume with serviceaccount name specified": {
 11150  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11151  			Spec: core.PodSpec{
 11152  				ServiceAccountName: "some-service-account",
 11153  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11154  				RestartPolicy:      core.RestartPolicyAlways,
 11155  				DNSPolicy:          core.DNSClusterFirst,
 11156  				Volumes: []core.Volume{{
 11157  					Name: "projected-volume",
 11158  					VolumeSource: core.VolumeSource{
 11159  						Projected: &core.ProjectedVolumeSource{
 11160  							Sources: []core.VolumeProjection{{
 11161  								ServiceAccountToken: &core.ServiceAccountTokenProjection{
 11162  									Audience:          "foo-audience",
 11163  									ExpirationSeconds: 6000,
 11164  									Path:              "foo-path",
 11165  								},
 11166  							}},
 11167  						},
 11168  					},
 11169  				}},
 11170  			},
 11171  		},
 11172  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by name": {
 11173  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11174  			Spec: core.PodSpec{
 11175  				ServiceAccountName: "some-service-account",
 11176  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11177  				RestartPolicy:      core.RestartPolicyAlways,
 11178  				DNSPolicy:          core.DNSClusterFirst,
 11179  				Volumes: []core.Volume{
 11180  					{
 11181  						Name: "projected-volume",
 11182  						VolumeSource: core.VolumeSource{
 11183  							Projected: &core.ProjectedVolumeSource{
 11184  								Sources: []core.VolumeProjection{
 11185  									{
 11186  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 11187  											Path: "foo-path",
 11188  											Name: utilpointer.String("foo"),
 11189  										},
 11190  									},
 11191  								},
 11192  							},
 11193  						},
 11194  					},
 11195  				},
 11196  			},
 11197  		},
 11198  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by signer name": {
 11199  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 11200  			Spec: core.PodSpec{
 11201  				ServiceAccountName: "some-service-account",
 11202  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11203  				RestartPolicy:      core.RestartPolicyAlways,
 11204  				DNSPolicy:          core.DNSClusterFirst,
 11205  				Volumes: []core.Volume{
 11206  					{
 11207  						Name: "projected-volume",
 11208  						VolumeSource: core.VolumeSource{
 11209  							Projected: &core.ProjectedVolumeSource{
 11210  								Sources: []core.VolumeProjection{
 11211  									{
 11212  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 11213  											Path:       "foo-path",
 11214  											SignerName: utilpointer.String("example.com/foo"),
 11215  											LabelSelector: &metav1.LabelSelector{
 11216  												MatchLabels: map[string]string{
 11217  													"version": "live",
 11218  												},
 11219  											},
 11220  										},
 11221  									},
 11222  								},
 11223  							},
 11224  						},
 11225  					},
 11226  				},
 11227  			},
 11228  		},
 11229  		"ephemeral volume + PVC, no conflict between them": {
 11230  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11231  			Spec: core.PodSpec{
 11232  				Volumes: []core.Volume{
 11233  					{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 11234  					{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 11235  				},
 11236  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11237  				RestartPolicy: core.RestartPolicyAlways,
 11238  				DNSPolicy:     core.DNSClusterFirst,
 11239  			},
 11240  		},
 11241  		"negative pod-deletion-cost": {
 11242  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "-100"}},
 11243  			Spec: core.PodSpec{
 11244  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11245  				RestartPolicy: core.RestartPolicyAlways,
 11246  				DNSPolicy:     core.DNSClusterFirst,
 11247  			},
 11248  		},
 11249  		"positive pod-deletion-cost": {
 11250  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "100"}},
 11251  			Spec: core.PodSpec{
 11252  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11253  				RestartPolicy: core.RestartPolicyAlways,
 11254  				DNSPolicy:     core.DNSClusterFirst,
 11255  			},
 11256  		},
 11257  		"MatchLabelKeys/MismatchLabelKeys in required PodAffinity": {
 11258  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11259  			Spec: core.PodSpec{
 11260  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11261  				RestartPolicy: core.RestartPolicyAlways,
 11262  				DNSPolicy:     core.DNSClusterFirst,
 11263  				Affinity: &core.Affinity{
 11264  					PodAffinity: &core.PodAffinity{
 11265  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11266  							{
 11267  								LabelSelector: &metav1.LabelSelector{
 11268  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11269  										{
 11270  											Key:      "key",
 11271  											Operator: metav1.LabelSelectorOpNotIn,
 11272  											Values:   []string{"value1", "value2"},
 11273  										},
 11274  										{
 11275  											Key:      "key2",
 11276  											Operator: metav1.LabelSelectorOpIn,
 11277  											Values:   []string{"value1"},
 11278  										},
 11279  										{
 11280  											Key:      "key3",
 11281  											Operator: metav1.LabelSelectorOpNotIn,
 11282  											Values:   []string{"value1"},
 11283  										},
 11284  									},
 11285  								},
 11286  								TopologyKey:       "k8s.io/zone",
 11287  								MatchLabelKeys:    []string{"key2"},
 11288  								MismatchLabelKeys: []string{"key3"},
 11289  							},
 11290  						},
 11291  					},
 11292  				},
 11293  			},
 11294  		},
 11295  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": {
 11296  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11297  			Spec: core.PodSpec{
 11298  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11299  				RestartPolicy: core.RestartPolicyAlways,
 11300  				DNSPolicy:     core.DNSClusterFirst,
 11301  				Affinity: &core.Affinity{
 11302  					PodAffinity: &core.PodAffinity{
 11303  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11304  							{
 11305  								Weight: 10,
 11306  								PodAffinityTerm: core.PodAffinityTerm{
 11307  									LabelSelector: &metav1.LabelSelector{
 11308  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11309  											{
 11310  												Key:      "key",
 11311  												Operator: metav1.LabelSelectorOpNotIn,
 11312  												Values:   []string{"value1", "value2"},
 11313  											},
 11314  											{
 11315  												Key:      "key2",
 11316  												Operator: metav1.LabelSelectorOpIn,
 11317  												Values:   []string{"value1"},
 11318  											},
 11319  											{
 11320  												Key:      "key3",
 11321  												Operator: metav1.LabelSelectorOpNotIn,
 11322  												Values:   []string{"value1"},
 11323  											},
 11324  										},
 11325  									},
 11326  									TopologyKey:       "k8s.io/zone",
 11327  									MatchLabelKeys:    []string{"key2"},
 11328  									MismatchLabelKeys: []string{"key3"},
 11329  								},
 11330  							},
 11331  						},
 11332  					},
 11333  				},
 11334  			},
 11335  		},
 11336  		"MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": {
 11337  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11338  			Spec: core.PodSpec{
 11339  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11340  				RestartPolicy: core.RestartPolicyAlways,
 11341  				DNSPolicy:     core.DNSClusterFirst,
 11342  				Affinity: &core.Affinity{
 11343  					PodAntiAffinity: &core.PodAntiAffinity{
 11344  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11345  							{
 11346  								LabelSelector: &metav1.LabelSelector{
 11347  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11348  										{
 11349  											Key:      "key",
 11350  											Operator: metav1.LabelSelectorOpNotIn,
 11351  											Values:   []string{"value1", "value2"},
 11352  										},
 11353  										{
 11354  											Key:      "key2",
 11355  											Operator: metav1.LabelSelectorOpIn,
 11356  											Values:   []string{"value1"},
 11357  										},
 11358  										{
 11359  											Key:      "key3",
 11360  											Operator: metav1.LabelSelectorOpNotIn,
 11361  											Values:   []string{"value1"},
 11362  										},
 11363  									},
 11364  								},
 11365  								TopologyKey:       "k8s.io/zone",
 11366  								MatchLabelKeys:    []string{"key2"},
 11367  								MismatchLabelKeys: []string{"key3"},
 11368  							},
 11369  						},
 11370  					},
 11371  				},
 11372  			},
 11373  		},
 11374  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": {
 11375  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11376  			Spec: core.PodSpec{
 11377  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11378  				RestartPolicy: core.RestartPolicyAlways,
 11379  				DNSPolicy:     core.DNSClusterFirst,
 11380  				Affinity: &core.Affinity{
 11381  					PodAntiAffinity: &core.PodAntiAffinity{
 11382  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11383  							{
 11384  								Weight: 10,
 11385  								PodAffinityTerm: core.PodAffinityTerm{
 11386  									LabelSelector: &metav1.LabelSelector{
 11387  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11388  											{
 11389  												Key:      "key",
 11390  												Operator: metav1.LabelSelectorOpNotIn,
 11391  												Values:   []string{"value1", "value2"},
 11392  											},
 11393  											{
 11394  												Key:      "key2",
 11395  												Operator: metav1.LabelSelectorOpIn,
 11396  												Values:   []string{"value1"},
 11397  											},
 11398  											{
 11399  												Key:      "key3",
 11400  												Operator: metav1.LabelSelectorOpNotIn,
 11401  												Values:   []string{"value1"},
 11402  											},
 11403  										},
 11404  									},
 11405  									TopologyKey:       "k8s.io/zone",
 11406  									MatchLabelKeys:    []string{"key2"},
 11407  									MismatchLabelKeys: []string{"key3"},
 11408  								},
 11409  							},
 11410  						},
 11411  					},
 11412  				},
 11413  			},
 11414  		},
 11415  		"LabelSelector can have the same key as MismatchLabelKeys": {
 11416  			// Note: On the contrary, in case of matchLabelKeys, keys in matchLabelKeys are not allowed to be specified in labelSelector by users.
 11417  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11418  			Spec: core.PodSpec{
 11419  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11420  				RestartPolicy: core.RestartPolicyAlways,
 11421  				DNSPolicy:     core.DNSClusterFirst,
 11422  				Affinity: &core.Affinity{
 11423  					PodAffinity: &core.PodAffinity{
 11424  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11425  							{
 11426  								LabelSelector: &metav1.LabelSelector{
 11427  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11428  										{
 11429  											Key:      "key",
 11430  											Operator: metav1.LabelSelectorOpNotIn,
 11431  											Values:   []string{"value1", "value2"},
 11432  										},
 11433  										{
 11434  											// This is the same key as in MismatchLabelKeys
 11435  											// but it's allowed.
 11436  											Key:      "key2",
 11437  											Operator: metav1.LabelSelectorOpIn,
 11438  											Values:   []string{"value1"},
 11439  										},
 11440  										{
 11441  											Key:      "key2",
 11442  											Operator: metav1.LabelSelectorOpNotIn,
 11443  											Values:   []string{"value1"},
 11444  										},
 11445  									},
 11446  								},
 11447  								TopologyKey:       "k8s.io/zone",
 11448  								MismatchLabelKeys: []string{"key2"},
 11449  							},
 11450  						},
 11451  					},
 11452  				},
 11453  			},
 11454  		},
 11455  	}
 11456  
 11457  	for k, v := range successCases {
 11458  		t.Run(k, func(t *testing.T) {
 11459  			if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
 11460  				t.Errorf("expected success: %v", errs)
 11461  			}
 11462  		})
 11463  	}
 11464  
 11465  	errorCases := map[string]struct {
 11466  		spec          core.Pod
 11467  		expectedError string
 11468  	}{
 11469  		"bad name": {
 11470  			expectedError: "metadata.name",
 11471  			spec: core.Pod{
 11472  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"},
 11473  				Spec: core.PodSpec{
 11474  					RestartPolicy: core.RestartPolicyAlways,
 11475  					DNSPolicy:     core.DNSClusterFirst,
 11476  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11477  				},
 11478  			},
 11479  		},
 11480  		"image whitespace": {
 11481  			expectedError: "spec.containers[0].image",
 11482  			spec: core.Pod{
 11483  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11484  				Spec: core.PodSpec{
 11485  					RestartPolicy: core.RestartPolicyAlways,
 11486  					DNSPolicy:     core.DNSClusterFirst,
 11487  					Containers:    []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11488  				},
 11489  			},
 11490  		},
 11491  		"image leading and trailing whitespace": {
 11492  			expectedError: "spec.containers[0].image",
 11493  			spec: core.Pod{
 11494  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11495  				Spec: core.PodSpec{
 11496  					RestartPolicy: core.RestartPolicyAlways,
 11497  					DNSPolicy:     core.DNSClusterFirst,
 11498  					Containers:    []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11499  				},
 11500  			},
 11501  		},
 11502  		"bad namespace": {
 11503  			expectedError: "metadata.namespace",
 11504  			spec: core.Pod{
 11505  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""},
 11506  				Spec: core.PodSpec{
 11507  					RestartPolicy: core.RestartPolicyAlways,
 11508  					DNSPolicy:     core.DNSClusterFirst,
 11509  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11510  				},
 11511  			},
 11512  		},
 11513  		"bad spec": {
 11514  			expectedError: "spec.containers[0].name",
 11515  			spec: core.Pod{
 11516  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 11517  				Spec: core.PodSpec{
 11518  					Containers: []core.Container{{}},
 11519  				},
 11520  			},
 11521  		},
 11522  		"bad label": {
 11523  			expectedError: "NoUppercaseOrSpecialCharsLike=Equals",
 11524  			spec: core.Pod{
 11525  				ObjectMeta: metav1.ObjectMeta{
 11526  					Name:      "abc",
 11527  					Namespace: "ns",
 11528  					Labels: map[string]string{
 11529  						"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 11530  					},
 11531  				},
 11532  				Spec: core.PodSpec{
 11533  					RestartPolicy: core.RestartPolicyAlways,
 11534  					DNSPolicy:     core.DNSClusterFirst,
 11535  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11536  				},
 11537  			},
 11538  		},
 11539  		"invalid node selector requirement in node affinity, operator can't be null": {
 11540  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator",
 11541  			spec: core.Pod{
 11542  				ObjectMeta: metav1.ObjectMeta{
 11543  					Name:      "123",
 11544  					Namespace: "ns",
 11545  				},
 11546  				Spec: validPodSpec(&core.Affinity{
 11547  					NodeAffinity: &core.NodeAffinity{
 11548  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11549  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11550  								MatchExpressions: []core.NodeSelectorRequirement{{
 11551  									Key: "key1",
 11552  								}},
 11553  							}},
 11554  						},
 11555  					},
 11556  				}),
 11557  			},
 11558  		},
 11559  		"invalid node selector requirement in node affinity, key is invalid": {
 11560  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key",
 11561  			spec: core.Pod{
 11562  				ObjectMeta: metav1.ObjectMeta{
 11563  					Name:      "123",
 11564  					Namespace: "ns",
 11565  				},
 11566  				Spec: validPodSpec(&core.Affinity{
 11567  					NodeAffinity: &core.NodeAffinity{
 11568  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11569  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11570  								MatchExpressions: []core.NodeSelectorRequirement{{
 11571  									Key:      "invalid key ___@#",
 11572  									Operator: core.NodeSelectorOpExists,
 11573  								}},
 11574  							}},
 11575  						},
 11576  					},
 11577  				}),
 11578  			},
 11579  		},
 11580  		"invalid node field selector requirement in node affinity, more values for field selector": {
 11581  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values",
 11582  			spec: core.Pod{
 11583  				ObjectMeta: metav1.ObjectMeta{
 11584  					Name:      "123",
 11585  					Namespace: "ns",
 11586  				},
 11587  				Spec: validPodSpec(&core.Affinity{
 11588  					NodeAffinity: &core.NodeAffinity{
 11589  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11590  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11591  								MatchFields: []core.NodeSelectorRequirement{{
 11592  									Key:      "metadata.name",
 11593  									Operator: core.NodeSelectorOpIn,
 11594  									Values:   []string{"host1", "host2"},
 11595  								}},
 11596  							}},
 11597  						},
 11598  					},
 11599  				}),
 11600  			},
 11601  		},
 11602  		"invalid node field selector requirement in node affinity, invalid operator": {
 11603  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator",
 11604  			spec: core.Pod{
 11605  				ObjectMeta: metav1.ObjectMeta{
 11606  					Name:      "123",
 11607  					Namespace: "ns",
 11608  				},
 11609  				Spec: validPodSpec(&core.Affinity{
 11610  					NodeAffinity: &core.NodeAffinity{
 11611  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11612  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11613  								MatchFields: []core.NodeSelectorRequirement{{
 11614  									Key:      "metadata.name",
 11615  									Operator: core.NodeSelectorOpExists,
 11616  								}},
 11617  							}},
 11618  						},
 11619  					},
 11620  				}),
 11621  			},
 11622  		},
 11623  		"invalid node field selector requirement in node affinity, invalid key": {
 11624  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key",
 11625  			spec: core.Pod{
 11626  				ObjectMeta: metav1.ObjectMeta{
 11627  					Name:      "123",
 11628  					Namespace: "ns",
 11629  				},
 11630  				Spec: validPodSpec(&core.Affinity{
 11631  					NodeAffinity: &core.NodeAffinity{
 11632  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11633  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 11634  								MatchFields: []core.NodeSelectorRequirement{{
 11635  									Key:      "metadata.namespace",
 11636  									Operator: core.NodeSelectorOpIn,
 11637  									Values:   []string{"ns1"},
 11638  								}},
 11639  							}},
 11640  						},
 11641  					},
 11642  				}),
 11643  			},
 11644  		},
 11645  		"invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": {
 11646  			expectedError: "must be in the range 1-100",
 11647  			spec: core.Pod{
 11648  				ObjectMeta: metav1.ObjectMeta{
 11649  					Name:      "123",
 11650  					Namespace: "ns",
 11651  				},
 11652  				Spec: validPodSpec(&core.Affinity{
 11653  					NodeAffinity: &core.NodeAffinity{
 11654  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 11655  							Weight: 199,
 11656  							Preference: core.NodeSelectorTerm{
 11657  								MatchExpressions: []core.NodeSelectorRequirement{{
 11658  									Key:      "foo",
 11659  									Operator: core.NodeSelectorOpIn,
 11660  									Values:   []string{"bar"},
 11661  								}},
 11662  							},
 11663  						}},
 11664  					},
 11665  				}),
 11666  			},
 11667  		},
 11668  		"invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": {
 11669  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms",
 11670  			spec: core.Pod{
 11671  				ObjectMeta: metav1.ObjectMeta{
 11672  					Name:      "123",
 11673  					Namespace: "ns",
 11674  				},
 11675  				Spec: validPodSpec(&core.Affinity{
 11676  					NodeAffinity: &core.NodeAffinity{
 11677  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 11678  							NodeSelectorTerms: []core.NodeSelectorTerm{},
 11679  						},
 11680  					},
 11681  				}),
 11682  			},
 11683  		},
 11684  		"invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
 11685  			expectedError: "must be in the range 1-100",
 11686  			spec: core.Pod{
 11687  				ObjectMeta: metav1.ObjectMeta{
 11688  					Name:      "123",
 11689  					Namespace: "ns",
 11690  				},
 11691  				Spec: validPodSpec(&core.Affinity{
 11692  					PodAffinity: &core.PodAffinity{
 11693  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11694  							Weight: 109,
 11695  							PodAffinityTerm: core.PodAffinityTerm{
 11696  								LabelSelector: &metav1.LabelSelector{
 11697  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11698  										Key:      "key2",
 11699  										Operator: metav1.LabelSelectorOpNotIn,
 11700  										Values:   []string{"value1", "value2"},
 11701  									}},
 11702  								},
 11703  								Namespaces:  []string{"ns"},
 11704  								TopologyKey: "region",
 11705  							},
 11706  						}},
 11707  					},
 11708  				}),
 11709  			},
 11710  		},
 11711  		"invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
 11712  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values",
 11713  			spec: core.Pod{
 11714  				ObjectMeta: metav1.ObjectMeta{
 11715  					Name:      "123",
 11716  					Namespace: "ns",
 11717  				},
 11718  				Spec: validPodSpec(&core.Affinity{
 11719  					PodAntiAffinity: &core.PodAntiAffinity{
 11720  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11721  							Weight: 10,
 11722  							PodAffinityTerm: core.PodAffinityTerm{
 11723  								LabelSelector: &metav1.LabelSelector{
 11724  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11725  										Key:      "key2",
 11726  										Operator: metav1.LabelSelectorOpExists,
 11727  										Values:   []string{"value1", "value2"},
 11728  									}},
 11729  								},
 11730  								Namespaces:  []string{"ns"},
 11731  								TopologyKey: "region",
 11732  							},
 11733  						}},
 11734  					},
 11735  				}),
 11736  			},
 11737  		},
 11738  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, In operator must include Values": {
 11739  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 11740  			spec: core.Pod{
 11741  				ObjectMeta: metav1.ObjectMeta{
 11742  					Name:      "123",
 11743  					Namespace: "ns",
 11744  				},
 11745  				Spec: validPodSpec(&core.Affinity{
 11746  					PodAntiAffinity: &core.PodAntiAffinity{
 11747  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11748  							Weight: 10,
 11749  							PodAffinityTerm: core.PodAffinityTerm{
 11750  								NamespaceSelector: &metav1.LabelSelector{
 11751  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11752  										Key:      "key2",
 11753  										Operator: metav1.LabelSelectorOpIn,
 11754  									}},
 11755  								},
 11756  								Namespaces:  []string{"ns"},
 11757  								TopologyKey: "region",
 11758  							},
 11759  						}},
 11760  					},
 11761  				}),
 11762  			},
 11763  		},
 11764  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, Exists operator can not have values": {
 11765  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 11766  			spec: core.Pod{
 11767  				ObjectMeta: metav1.ObjectMeta{
 11768  					Name:      "123",
 11769  					Namespace: "ns",
 11770  				},
 11771  				Spec: validPodSpec(&core.Affinity{
 11772  					PodAntiAffinity: &core.PodAntiAffinity{
 11773  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11774  							Weight: 10,
 11775  							PodAffinityTerm: core.PodAffinityTerm{
 11776  								NamespaceSelector: &metav1.LabelSelector{
 11777  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11778  										Key:      "key2",
 11779  										Operator: metav1.LabelSelectorOpExists,
 11780  										Values:   []string{"value1", "value2"},
 11781  									}},
 11782  								},
 11783  								Namespaces:  []string{"ns"},
 11784  								TopologyKey: "region",
 11785  							},
 11786  						}},
 11787  					},
 11788  				}),
 11789  			},
 11790  		},
 11791  		"invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, namespace should be valid": {
 11792  			expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace",
 11793  			spec: core.Pod{
 11794  				ObjectMeta: metav1.ObjectMeta{
 11795  					Name:      "123",
 11796  					Namespace: "ns",
 11797  				},
 11798  				Spec: validPodSpec(&core.Affinity{
 11799  					PodAffinity: &core.PodAffinity{
 11800  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11801  							Weight: 10,
 11802  							PodAffinityTerm: core.PodAffinityTerm{
 11803  								LabelSelector: &metav1.LabelSelector{
 11804  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11805  										Key:      "key2",
 11806  										Operator: metav1.LabelSelectorOpExists,
 11807  									}},
 11808  								},
 11809  								Namespaces:  []string{"INVALID_NAMESPACE"},
 11810  								TopologyKey: "region",
 11811  							},
 11812  						}},
 11813  					},
 11814  				}),
 11815  			},
 11816  		},
 11817  		"invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": {
 11818  			expectedError: "can not be empty",
 11819  			spec: core.Pod{
 11820  				ObjectMeta: metav1.ObjectMeta{
 11821  					Name:      "123",
 11822  					Namespace: "ns",
 11823  				},
 11824  				Spec: validPodSpec(&core.Affinity{
 11825  					PodAffinity: &core.PodAffinity{
 11826  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11827  							LabelSelector: &metav1.LabelSelector{
 11828  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11829  									Key:      "key2",
 11830  									Operator: metav1.LabelSelectorOpIn,
 11831  									Values:   []string{"value1", "value2"},
 11832  								}},
 11833  							},
 11834  							Namespaces: []string{"ns"},
 11835  						}},
 11836  					},
 11837  				}),
 11838  			},
 11839  		},
 11840  		"invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
 11841  			expectedError: "can not be empty",
 11842  			spec: core.Pod{
 11843  				ObjectMeta: metav1.ObjectMeta{
 11844  					Name:      "123",
 11845  					Namespace: "ns",
 11846  				},
 11847  				Spec: validPodSpec(&core.Affinity{
 11848  					PodAntiAffinity: &core.PodAntiAffinity{
 11849  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11850  							LabelSelector: &metav1.LabelSelector{
 11851  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11852  									Key:      "key2",
 11853  									Operator: metav1.LabelSelectorOpIn,
 11854  									Values:   []string{"value1", "value2"},
 11855  								}},
 11856  							},
 11857  							Namespaces: []string{"ns"},
 11858  						}},
 11859  					},
 11860  				}),
 11861  			},
 11862  		},
 11863  		"invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": {
 11864  			expectedError: "can not be empty",
 11865  			spec: core.Pod{
 11866  				ObjectMeta: metav1.ObjectMeta{
 11867  					Name:      "123",
 11868  					Namespace: "ns",
 11869  				},
 11870  				Spec: validPodSpec(&core.Affinity{
 11871  					PodAffinity: &core.PodAffinity{
 11872  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11873  							Weight: 10,
 11874  							PodAffinityTerm: core.PodAffinityTerm{
 11875  								LabelSelector: &metav1.LabelSelector{
 11876  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11877  										Key:      "key2",
 11878  										Operator: metav1.LabelSelectorOpNotIn,
 11879  										Values:   []string{"value1", "value2"},
 11880  									}},
 11881  								},
 11882  								Namespaces: []string{"ns"},
 11883  							},
 11884  						}},
 11885  					},
 11886  				}),
 11887  			},
 11888  		},
 11889  		"invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": {
 11890  			expectedError: "can not be empty",
 11891  			spec: core.Pod{
 11892  				ObjectMeta: metav1.ObjectMeta{
 11893  					Name:      "123",
 11894  					Namespace: "ns",
 11895  				},
 11896  				Spec: validPodSpec(&core.Affinity{
 11897  					PodAntiAffinity: &core.PodAntiAffinity{
 11898  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11899  							Weight: 10,
 11900  							PodAffinityTerm: core.PodAffinityTerm{
 11901  								LabelSelector: &metav1.LabelSelector{
 11902  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11903  										Key:      "key2",
 11904  										Operator: metav1.LabelSelectorOpNotIn,
 11905  										Values:   []string{"value1", "value2"},
 11906  									}},
 11907  								},
 11908  								Namespaces: []string{"ns"},
 11909  							},
 11910  						}},
 11911  					},
 11912  				}),
 11913  			},
 11914  		},
 11915  		"invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11916  			expectedError: "prefix part must be non-empty",
 11917  			spec: core.Pod{
 11918  				ObjectMeta: metav1.ObjectMeta{
 11919  					Name:      "123",
 11920  					Namespace: "ns",
 11921  				},
 11922  				Spec: validPodSpec(&core.Affinity{
 11923  					PodAffinity: &core.PodAffinity{
 11924  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11925  							{
 11926  								Weight: 10,
 11927  								PodAffinityTerm: core.PodAffinityTerm{
 11928  									LabelSelector: &metav1.LabelSelector{
 11929  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11930  											{
 11931  												Key:      "key",
 11932  												Operator: metav1.LabelSelectorOpNotIn,
 11933  												Values:   []string{"value1", "value2"},
 11934  											},
 11935  										},
 11936  									},
 11937  									TopologyKey:    "k8s.io/zone",
 11938  									MatchLabelKeys: []string{"/simple"},
 11939  								},
 11940  							},
 11941  						},
 11942  					},
 11943  				}),
 11944  			},
 11945  		},
 11946  		"invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11947  			expectedError: "prefix part must be non-empty",
 11948  			spec: core.Pod{
 11949  				ObjectMeta: metav1.ObjectMeta{
 11950  					Name:      "123",
 11951  					Namespace: "ns",
 11952  				},
 11953  				Spec: validPodSpec(&core.Affinity{
 11954  					PodAffinity: &core.PodAffinity{
 11955  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11956  							{
 11957  								LabelSelector: &metav1.LabelSelector{
 11958  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11959  										{
 11960  											Key:      "key",
 11961  											Operator: metav1.LabelSelectorOpNotIn,
 11962  											Values:   []string{"value1", "value2"},
 11963  										},
 11964  									},
 11965  								},
 11966  								TopologyKey:    "k8s.io/zone",
 11967  								MatchLabelKeys: []string{"/simple"},
 11968  							},
 11969  						},
 11970  					},
 11971  				}),
 11972  			},
 11973  		},
 11974  		"invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 11975  			expectedError: "prefix part must be non-empty",
 11976  			spec: core.Pod{
 11977  				ObjectMeta: metav1.ObjectMeta{
 11978  					Name:      "123",
 11979  					Namespace: "ns",
 11980  				},
 11981  				Spec: validPodSpec(&core.Affinity{
 11982  					PodAntiAffinity: &core.PodAntiAffinity{
 11983  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11984  							{
 11985  								Weight: 10,
 11986  								PodAffinityTerm: core.PodAffinityTerm{
 11987  									LabelSelector: &metav1.LabelSelector{
 11988  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11989  											{
 11990  												Key:      "key",
 11991  												Operator: metav1.LabelSelectorOpNotIn,
 11992  												Values:   []string{"value1", "value2"},
 11993  											},
 11994  										},
 11995  									},
 11996  									TopologyKey:    "k8s.io/zone",
 11997  									MatchLabelKeys: []string{"/simple"},
 11998  								},
 11999  							},
 12000  						},
 12001  					},
 12002  				}),
 12003  			},
 12004  		},
 12005  		"invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 12006  			expectedError: "prefix part must be non-empty",
 12007  			spec: core.Pod{
 12008  				ObjectMeta: metav1.ObjectMeta{
 12009  					Name:      "123",
 12010  					Namespace: "ns",
 12011  				},
 12012  				Spec: validPodSpec(&core.Affinity{
 12013  					PodAntiAffinity: &core.PodAntiAffinity{
 12014  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12015  							{
 12016  								LabelSelector: &metav1.LabelSelector{
 12017  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12018  										{
 12019  											Key:      "key",
 12020  											Operator: metav1.LabelSelectorOpNotIn,
 12021  											Values:   []string{"value1", "value2"},
 12022  										},
 12023  									},
 12024  								},
 12025  								TopologyKey:    "k8s.io/zone",
 12026  								MatchLabelKeys: []string{"/simple"},
 12027  							},
 12028  						},
 12029  					},
 12030  				}),
 12031  			},
 12032  		},
 12033  		"invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 12034  			expectedError: "prefix part must be non-empty",
 12035  			spec: core.Pod{
 12036  				ObjectMeta: metav1.ObjectMeta{
 12037  					Name:      "123",
 12038  					Namespace: "ns",
 12039  				},
 12040  				Spec: validPodSpec(&core.Affinity{
 12041  					PodAffinity: &core.PodAffinity{
 12042  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12043  							{
 12044  								Weight: 10,
 12045  								PodAffinityTerm: core.PodAffinityTerm{
 12046  									LabelSelector: &metav1.LabelSelector{
 12047  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12048  											{
 12049  												Key:      "key",
 12050  												Operator: metav1.LabelSelectorOpNotIn,
 12051  												Values:   []string{"value1", "value2"},
 12052  											},
 12053  										},
 12054  									},
 12055  									TopologyKey:       "k8s.io/zone",
 12056  									MismatchLabelKeys: []string{"/simple"},
 12057  								},
 12058  							},
 12059  						},
 12060  					},
 12061  				}),
 12062  			},
 12063  		},
 12064  		"invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 12065  			expectedError: "prefix part must be non-empty",
 12066  			spec: core.Pod{
 12067  				ObjectMeta: metav1.ObjectMeta{
 12068  					Name:      "123",
 12069  					Namespace: "ns",
 12070  				},
 12071  				Spec: validPodSpec(&core.Affinity{
 12072  					PodAffinity: &core.PodAffinity{
 12073  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12074  							{
 12075  								LabelSelector: &metav1.LabelSelector{
 12076  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12077  										{
 12078  											Key:      "key",
 12079  											Operator: metav1.LabelSelectorOpNotIn,
 12080  											Values:   []string{"value1", "value2"},
 12081  										},
 12082  									},
 12083  								},
 12084  								TopologyKey:       "k8s.io/zone",
 12085  								MismatchLabelKeys: []string{"/simple"},
 12086  							},
 12087  						},
 12088  					},
 12089  				}),
 12090  			},
 12091  		},
 12092  		"invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 12093  			expectedError: "prefix part must be non-empty",
 12094  			spec: core.Pod{
 12095  				ObjectMeta: metav1.ObjectMeta{
 12096  					Name:      "123",
 12097  					Namespace: "ns",
 12098  				},
 12099  				Spec: validPodSpec(&core.Affinity{
 12100  					PodAntiAffinity: &core.PodAntiAffinity{
 12101  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12102  							{
 12103  								Weight: 10,
 12104  								PodAffinityTerm: core.PodAffinityTerm{
 12105  									LabelSelector: &metav1.LabelSelector{
 12106  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12107  											{
 12108  												Key:      "key",
 12109  												Operator: metav1.LabelSelectorOpNotIn,
 12110  												Values:   []string{"value1", "value2"},
 12111  											},
 12112  										},
 12113  									},
 12114  									TopologyKey:       "k8s.io/zone",
 12115  									MismatchLabelKeys: []string{"/simple"},
 12116  								},
 12117  							},
 12118  						},
 12119  					},
 12120  				}),
 12121  			},
 12122  		},
 12123  		"invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 12124  			expectedError: "prefix part must be non-empty",
 12125  			spec: core.Pod{
 12126  				ObjectMeta: metav1.ObjectMeta{
 12127  					Name:      "123",
 12128  					Namespace: "ns",
 12129  				},
 12130  				Spec: validPodSpec(&core.Affinity{
 12131  					PodAntiAffinity: &core.PodAntiAffinity{
 12132  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12133  							{
 12134  								LabelSelector: &metav1.LabelSelector{
 12135  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12136  										{
 12137  											Key:      "key",
 12138  											Operator: metav1.LabelSelectorOpNotIn,
 12139  											Values:   []string{"value1", "value2"},
 12140  										},
 12141  									},
 12142  								},
 12143  								TopologyKey:       "k8s.io/zone",
 12144  								MismatchLabelKeys: []string{"/simple"},
 12145  							},
 12146  						},
 12147  					},
 12148  				}),
 12149  			},
 12150  		},
 12151  		"invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": {
 12152  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12153  			spec: core.Pod{
 12154  				ObjectMeta: metav1.ObjectMeta{
 12155  					Name:      "123",
 12156  					Namespace: "ns",
 12157  					Labels:    map[string]string{"key": "value1"},
 12158  				},
 12159  				Spec: validPodSpec(&core.Affinity{
 12160  					PodAffinity: &core.PodAffinity{
 12161  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12162  							{
 12163  								Weight: 10,
 12164  								PodAffinityTerm: core.PodAffinityTerm{
 12165  									LabelSelector: &metav1.LabelSelector{
 12166  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12167  											// This one should be created from MatchLabelKeys.
 12168  											{
 12169  												Key:      "key",
 12170  												Operator: metav1.LabelSelectorOpIn,
 12171  												Values:   []string{"value1"},
 12172  											},
 12173  											{
 12174  												Key:      "key",
 12175  												Operator: metav1.LabelSelectorOpNotIn,
 12176  												Values:   []string{"value2"},
 12177  											},
 12178  										},
 12179  									},
 12180  									TopologyKey:    "k8s.io/zone",
 12181  									MatchLabelKeys: []string{"key"},
 12182  								},
 12183  							},
 12184  						},
 12185  					},
 12186  				}),
 12187  			},
 12188  		},
 12189  		"invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": {
 12190  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12191  			spec: core.Pod{
 12192  				ObjectMeta: metav1.ObjectMeta{
 12193  					Name:      "123",
 12194  					Namespace: "ns",
 12195  					Labels:    map[string]string{"key": "value1"},
 12196  				},
 12197  				Spec: validPodSpec(&core.Affinity{
 12198  					PodAffinity: &core.PodAffinity{
 12199  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12200  							{
 12201  								LabelSelector: &metav1.LabelSelector{
 12202  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12203  										// This one should be created from MatchLabelKeys.
 12204  										{
 12205  											Key:      "key",
 12206  											Operator: metav1.LabelSelectorOpIn,
 12207  											Values:   []string{"value1"},
 12208  										},
 12209  										{
 12210  											Key:      "key",
 12211  											Operator: metav1.LabelSelectorOpNotIn,
 12212  											Values:   []string{"value2"},
 12213  										},
 12214  									},
 12215  								},
 12216  								TopologyKey:    "k8s.io/zone",
 12217  								MatchLabelKeys: []string{"key"},
 12218  							},
 12219  						},
 12220  					},
 12221  				}),
 12222  			},
 12223  		},
 12224  		"invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 12225  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12226  			spec: core.Pod{
 12227  				ObjectMeta: metav1.ObjectMeta{
 12228  					Name:      "123",
 12229  					Namespace: "ns",
 12230  					Labels:    map[string]string{"key": "value1"},
 12231  				},
 12232  				Spec: validPodSpec(&core.Affinity{
 12233  					PodAntiAffinity: &core.PodAntiAffinity{
 12234  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12235  							{
 12236  								Weight: 10,
 12237  								PodAffinityTerm: core.PodAffinityTerm{
 12238  									LabelSelector: &metav1.LabelSelector{
 12239  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12240  											// This one should be created from MatchLabelKeys.
 12241  											{
 12242  												Key:      "key",
 12243  												Operator: metav1.LabelSelectorOpIn,
 12244  												Values:   []string{"value1"},
 12245  											},
 12246  											{
 12247  												Key:      "key",
 12248  												Operator: metav1.LabelSelectorOpNotIn,
 12249  												Values:   []string{"value2"},
 12250  											},
 12251  										},
 12252  									},
 12253  									TopologyKey:    "k8s.io/zone",
 12254  									MatchLabelKeys: []string{"key"},
 12255  								},
 12256  							},
 12257  						},
 12258  					},
 12259  				}),
 12260  			},
 12261  		},
 12262  		"invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 12263  			expectedError: "exists in both matchLabelKeys and labelSelector",
 12264  			spec: core.Pod{
 12265  				ObjectMeta: metav1.ObjectMeta{
 12266  					Name:      "123",
 12267  					Namespace: "ns",
 12268  					Labels:    map[string]string{"key": "value1"},
 12269  				},
 12270  				Spec: validPodSpec(&core.Affinity{
 12271  					PodAntiAffinity: &core.PodAntiAffinity{
 12272  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12273  							{
 12274  								LabelSelector: &metav1.LabelSelector{
 12275  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12276  										// This one should be created from MatchLabelKeys.
 12277  										{
 12278  											Key:      "key",
 12279  											Operator: metav1.LabelSelectorOpIn,
 12280  											Values:   []string{"value1"},
 12281  										},
 12282  										{
 12283  											Key:      "key",
 12284  											Operator: metav1.LabelSelectorOpNotIn,
 12285  											Values:   []string{"value2"},
 12286  										},
 12287  									},
 12288  								},
 12289  								TopologyKey:    "k8s.io/zone",
 12290  								MatchLabelKeys: []string{"key"},
 12291  							},
 12292  						},
 12293  					},
 12294  				}),
 12295  			},
 12296  		},
 12297  		"invalid soft pod 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  					PodAffinity: &core.PodAffinity{
 12306  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12307  							{
 12308  								Weight: 10,
 12309  								PodAffinityTerm: core.PodAffinityTerm{
 12310  									LabelSelector: &metav1.LabelSelector{
 12311  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12312  											{
 12313  												Key:      "key",
 12314  												Operator: metav1.LabelSelectorOpNotIn,
 12315  												Values:   []string{"value1", "value2"},
 12316  											},
 12317  										},
 12318  									},
 12319  									TopologyKey:       "k8s.io/zone",
 12320  									MatchLabelKeys:    []string{"samekey"},
 12321  									MismatchLabelKeys: []string{"samekey"},
 12322  								},
 12323  							},
 12324  						},
 12325  					},
 12326  				}),
 12327  			},
 12328  		},
 12329  		"invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12330  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12331  			spec: core.Pod{
 12332  				ObjectMeta: metav1.ObjectMeta{
 12333  					Name:      "123",
 12334  					Namespace: "ns",
 12335  				},
 12336  				Spec: validPodSpec(&core.Affinity{
 12337  					PodAffinity: &core.PodAffinity{
 12338  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12339  							{
 12340  								LabelSelector: &metav1.LabelSelector{
 12341  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12342  										{
 12343  											Key:      "key",
 12344  											Operator: metav1.LabelSelectorOpNotIn,
 12345  											Values:   []string{"value1", "value2"},
 12346  										},
 12347  									},
 12348  								},
 12349  								TopologyKey:       "k8s.io/zone",
 12350  								MatchLabelKeys:    []string{"samekey"},
 12351  								MismatchLabelKeys: []string{"samekey"},
 12352  							},
 12353  						},
 12354  					},
 12355  				}),
 12356  			},
 12357  		},
 12358  		"invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12359  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12360  			spec: core.Pod{
 12361  				ObjectMeta: metav1.ObjectMeta{
 12362  					Name:      "123",
 12363  					Namespace: "ns",
 12364  				},
 12365  				Spec: validPodSpec(&core.Affinity{
 12366  					PodAntiAffinity: &core.PodAntiAffinity{
 12367  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 12368  							{
 12369  								Weight: 10,
 12370  								PodAffinityTerm: core.PodAffinityTerm{
 12371  									LabelSelector: &metav1.LabelSelector{
 12372  										MatchExpressions: []metav1.LabelSelectorRequirement{
 12373  											{
 12374  												Key:      "key",
 12375  												Operator: metav1.LabelSelectorOpNotIn,
 12376  												Values:   []string{"value1", "value2"},
 12377  											},
 12378  										},
 12379  									},
 12380  									TopologyKey:       "k8s.io/zone",
 12381  									MatchLabelKeys:    []string{"samekey"},
 12382  									MismatchLabelKeys: []string{"samekey"},
 12383  								},
 12384  							},
 12385  						},
 12386  					},
 12387  				}),
 12388  			},
 12389  		},
 12390  		"invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 12391  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 12392  			spec: core.Pod{
 12393  				ObjectMeta: metav1.ObjectMeta{
 12394  					Name:      "123",
 12395  					Namespace: "ns",
 12396  				},
 12397  				Spec: validPodSpec(&core.Affinity{
 12398  					PodAntiAffinity: &core.PodAntiAffinity{
 12399  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 12400  							{
 12401  								LabelSelector: &metav1.LabelSelector{
 12402  									MatchExpressions: []metav1.LabelSelectorRequirement{
 12403  										{
 12404  											Key:      "key",
 12405  											Operator: metav1.LabelSelectorOpNotIn,
 12406  											Values:   []string{"value1", "value2"},
 12407  										},
 12408  									},
 12409  								},
 12410  								TopologyKey:       "k8s.io/zone",
 12411  								MatchLabelKeys:    []string{"samekey"},
 12412  								MismatchLabelKeys: []string{"samekey"},
 12413  							},
 12414  						},
 12415  					},
 12416  				}),
 12417  			},
 12418  		},
 12419  		"invalid toleration key": {
 12420  			expectedError: "spec.tolerations[0].key",
 12421  			spec: core.Pod{
 12422  				ObjectMeta: metav1.ObjectMeta{
 12423  					Name:      "123",
 12424  					Namespace: "ns",
 12425  				},
 12426  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 12427  			},
 12428  		},
 12429  		"invalid toleration operator": {
 12430  			expectedError: "spec.tolerations[0].operator",
 12431  			spec: core.Pod{
 12432  				ObjectMeta: metav1.ObjectMeta{
 12433  					Name:      "123",
 12434  					Namespace: "ns",
 12435  				},
 12436  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}),
 12437  			},
 12438  		},
 12439  		"value must be empty when `operator` is 'Exists'": {
 12440  			expectedError: "spec.tolerations[0].operator",
 12441  			spec: core.Pod{
 12442  				ObjectMeta: metav1.ObjectMeta{
 12443  					Name:      "123",
 12444  					Namespace: "ns",
 12445  				},
 12446  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}),
 12447  			},
 12448  		},
 12449  
 12450  		"operator must be 'Exists' when `key` is empty": {
 12451  			expectedError: "spec.tolerations[0].operator",
 12452  			spec: core.Pod{
 12453  				ObjectMeta: metav1.ObjectMeta{
 12454  					Name:      "123",
 12455  					Namespace: "ns",
 12456  				},
 12457  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 12458  			},
 12459  		},
 12460  		"effect must be 'NoExecute' when `TolerationSeconds` is set": {
 12461  			expectedError: "spec.tolerations[0].effect",
 12462  			spec: core.Pod{
 12463  				ObjectMeta: metav1.ObjectMeta{
 12464  					Name:      "pod-forgiveness-invalid",
 12465  					Namespace: "ns",
 12466  				},
 12467  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}),
 12468  			},
 12469  		},
 12470  		"must be a valid pod seccomp profile": {
 12471  			expectedError: "must be a valid seccomp profile",
 12472  			spec: core.Pod{
 12473  				ObjectMeta: metav1.ObjectMeta{
 12474  					Name:      "123",
 12475  					Namespace: "ns",
 12476  					Annotations: map[string]string{
 12477  						core.SeccompPodAnnotationKey: "foo",
 12478  					},
 12479  				},
 12480  				Spec: validPodSpec(nil),
 12481  			},
 12482  		},
 12483  		"must be a valid container seccomp profile": {
 12484  			expectedError: "must be a valid seccomp profile",
 12485  			spec: core.Pod{
 12486  				ObjectMeta: metav1.ObjectMeta{
 12487  					Name:      "123",
 12488  					Namespace: "ns",
 12489  					Annotations: map[string]string{
 12490  						core.SeccompContainerAnnotationKeyPrefix + "foo": "foo",
 12491  					},
 12492  				},
 12493  				Spec: validPodSpec(nil),
 12494  			},
 12495  		},
 12496  		"must be a non-empty container name in seccomp annotation": {
 12497  			expectedError: "name part must be non-empty",
 12498  			spec: core.Pod{
 12499  				ObjectMeta: metav1.ObjectMeta{
 12500  					Name:      "123",
 12501  					Namespace: "ns",
 12502  					Annotations: map[string]string{
 12503  						core.SeccompContainerAnnotationKeyPrefix: "foo",
 12504  					},
 12505  				},
 12506  				Spec: validPodSpec(nil),
 12507  			},
 12508  		},
 12509  		"must be a non-empty container profile in seccomp annotation": {
 12510  			expectedError: "must be a valid seccomp profile",
 12511  			spec: core.Pod{
 12512  				ObjectMeta: metav1.ObjectMeta{
 12513  					Name:      "123",
 12514  					Namespace: "ns",
 12515  					Annotations: map[string]string{
 12516  						core.SeccompContainerAnnotationKeyPrefix + "foo": "",
 12517  					},
 12518  				},
 12519  				Spec: validPodSpec(nil),
 12520  			},
 12521  		},
 12522  		"must match seccomp profile type and pod annotation": {
 12523  			expectedError: "seccomp type in annotation and field must match",
 12524  			spec: core.Pod{
 12525  				ObjectMeta: metav1.ObjectMeta{
 12526  					Name:      "123",
 12527  					Namespace: "ns",
 12528  					Annotations: map[string]string{
 12529  						core.SeccompPodAnnotationKey: "unconfined",
 12530  					},
 12531  				},
 12532  				Spec: core.PodSpec{
 12533  					SecurityContext: &core.PodSecurityContext{
 12534  						SeccompProfile: &core.SeccompProfile{
 12535  							Type: core.SeccompProfileTypeRuntimeDefault,
 12536  						},
 12537  					},
 12538  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12539  					RestartPolicy: core.RestartPolicyAlways,
 12540  					DNSPolicy:     core.DNSClusterFirst,
 12541  				},
 12542  			},
 12543  		},
 12544  		"must match seccomp profile type and container annotation": {
 12545  			expectedError: "seccomp type in annotation and field must match",
 12546  			spec: core.Pod{
 12547  				ObjectMeta: metav1.ObjectMeta{
 12548  					Name:      "123",
 12549  					Namespace: "ns",
 12550  					Annotations: map[string]string{
 12551  						core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined",
 12552  					},
 12553  				},
 12554  				Spec: core.PodSpec{
 12555  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12556  						SecurityContext: &core.SecurityContext{
 12557  							SeccompProfile: &core.SeccompProfile{
 12558  								Type: core.SeccompProfileTypeRuntimeDefault,
 12559  							},
 12560  						}}},
 12561  					RestartPolicy: core.RestartPolicyAlways,
 12562  					DNSPolicy:     core.DNSClusterFirst,
 12563  				},
 12564  			},
 12565  		},
 12566  		"must be a relative path in a node-local seccomp profile annotation": {
 12567  			expectedError: "must be a relative path",
 12568  			spec: core.Pod{
 12569  				ObjectMeta: metav1.ObjectMeta{
 12570  					Name:      "123",
 12571  					Namespace: "ns",
 12572  					Annotations: map[string]string{
 12573  						core.SeccompPodAnnotationKey: "localhost//foo",
 12574  					},
 12575  				},
 12576  				Spec: validPodSpec(nil),
 12577  			},
 12578  		},
 12579  		"must not start with '../'": {
 12580  			expectedError: "must not contain '..'",
 12581  			spec: core.Pod{
 12582  				ObjectMeta: metav1.ObjectMeta{
 12583  					Name:      "123",
 12584  					Namespace: "ns",
 12585  					Annotations: map[string]string{
 12586  						core.SeccompPodAnnotationKey: "localhost/../foo",
 12587  					},
 12588  				},
 12589  				Spec: validPodSpec(nil),
 12590  			},
 12591  		},
 12592  		"AppArmor profile must apply to a container": {
 12593  			expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]",
 12594  			spec: core.Pod{
 12595  				ObjectMeta: metav1.ObjectMeta{
 12596  					Name:      "123",
 12597  					Namespace: "ns",
 12598  					Annotations: map[string]string{
 12599  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr":      v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12600  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12601  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.DeprecatedAppArmorBetaProfileRuntimeDefault,
 12602  					},
 12603  				},
 12604  				Spec: core.PodSpec{
 12605  					InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12606  					Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12607  					RestartPolicy:  core.RestartPolicyAlways,
 12608  					DNSPolicy:      core.DNSClusterFirst,
 12609  				},
 12610  			},
 12611  		},
 12612  		"AppArmor profile format must be valid": {
 12613  			expectedError: "invalid AppArmor profile name",
 12614  			spec: core.Pod{
 12615  				ObjectMeta: metav1.ObjectMeta{
 12616  					Name:      "123",
 12617  					Namespace: "ns",
 12618  					Annotations: map[string]string{
 12619  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
 12620  					},
 12621  				},
 12622  				Spec: validPodSpec(nil),
 12623  			},
 12624  		},
 12625  		"only default AppArmor profile may start with runtime/": {
 12626  			expectedError: "invalid AppArmor profile name",
 12627  			spec: core.Pod{
 12628  				ObjectMeta: metav1.ObjectMeta{
 12629  					Name:      "123",
 12630  					Namespace: "ns",
 12631  					Annotations: map[string]string{
 12632  						v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
 12633  					},
 12634  				},
 12635  				Spec: validPodSpec(nil),
 12636  			},
 12637  		},
 12638  		"unsupported pod AppArmor profile type": {
 12639  			expectedError: `Unsupported value: "test"`,
 12640  			spec: core.Pod{
 12641  				ObjectMeta: metav1.ObjectMeta{
 12642  					Name:      "123",
 12643  					Namespace: "ns",
 12644  				},
 12645  				Spec: core.PodSpec{
 12646  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12647  					RestartPolicy: core.RestartPolicyAlways,
 12648  					DNSPolicy:     core.DNSDefault,
 12649  					SecurityContext: &core.PodSecurityContext{
 12650  						AppArmorProfile: &core.AppArmorProfile{
 12651  							Type: "test",
 12652  						},
 12653  					},
 12654  				},
 12655  			},
 12656  		},
 12657  		"unsupported container AppArmor profile type": {
 12658  			expectedError: `Unsupported value: "test"`,
 12659  			spec: core.Pod{
 12660  				ObjectMeta: metav1.ObjectMeta{
 12661  					Name:      "123",
 12662  					Namespace: "ns",
 12663  				},
 12664  				Spec: core.PodSpec{
 12665  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12666  						SecurityContext: &core.SecurityContext{
 12667  							AppArmorProfile: &core.AppArmorProfile{
 12668  								Type: "test",
 12669  							},
 12670  						},
 12671  					}},
 12672  					RestartPolicy: core.RestartPolicyAlways,
 12673  					DNSPolicy:     core.DNSDefault,
 12674  				},
 12675  			},
 12676  		},
 12677  		"missing pod AppArmor profile type": {
 12678  			expectedError: "Required value: type is required when appArmorProfile is set",
 12679  			spec: core.Pod{
 12680  				ObjectMeta: metav1.ObjectMeta{
 12681  					Name:      "123",
 12682  					Namespace: "ns",
 12683  				},
 12684  				Spec: core.PodSpec{
 12685  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12686  					RestartPolicy: core.RestartPolicyAlways,
 12687  					DNSPolicy:     core.DNSDefault,
 12688  					SecurityContext: &core.PodSecurityContext{
 12689  						AppArmorProfile: &core.AppArmorProfile{
 12690  							Type: "",
 12691  						},
 12692  					},
 12693  				},
 12694  			},
 12695  		},
 12696  		"missing AppArmor localhost profile": {
 12697  			expectedError: "Required value: must be set when AppArmor type is Localhost",
 12698  			spec: core.Pod{
 12699  				ObjectMeta: metav1.ObjectMeta{
 12700  					Name:      "123",
 12701  					Namespace: "ns",
 12702  				},
 12703  				Spec: core.PodSpec{
 12704  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12705  					RestartPolicy: core.RestartPolicyAlways,
 12706  					DNSPolicy:     core.DNSDefault,
 12707  					SecurityContext: &core.PodSecurityContext{
 12708  						AppArmorProfile: &core.AppArmorProfile{
 12709  							Type: core.AppArmorProfileTypeLocalhost,
 12710  						},
 12711  					},
 12712  				},
 12713  			},
 12714  		},
 12715  		"empty AppArmor localhost profile": {
 12716  			expectedError: "Required value: must be set when AppArmor type is Localhost",
 12717  			spec: core.Pod{
 12718  				ObjectMeta: metav1.ObjectMeta{
 12719  					Name:      "123",
 12720  					Namespace: "ns",
 12721  				},
 12722  				Spec: core.PodSpec{
 12723  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12724  					RestartPolicy: core.RestartPolicyAlways,
 12725  					DNSPolicy:     core.DNSDefault,
 12726  					SecurityContext: &core.PodSecurityContext{
 12727  						AppArmorProfile: &core.AppArmorProfile{
 12728  							Type:             core.AppArmorProfileTypeLocalhost,
 12729  							LocalhostProfile: ptr.To(""),
 12730  						},
 12731  					},
 12732  				},
 12733  			},
 12734  		},
 12735  		"invalid AppArmor localhost profile type": {
 12736  			expectedError: `Invalid value: "foo-bar"`,
 12737  			spec: core.Pod{
 12738  				ObjectMeta: metav1.ObjectMeta{
 12739  					Name:      "123",
 12740  					Namespace: "ns",
 12741  				},
 12742  				Spec: core.PodSpec{
 12743  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12744  					RestartPolicy: core.RestartPolicyAlways,
 12745  					DNSPolicy:     core.DNSDefault,
 12746  					SecurityContext: &core.PodSecurityContext{
 12747  						AppArmorProfile: &core.AppArmorProfile{
 12748  							Type:             core.AppArmorProfileTypeRuntimeDefault,
 12749  							LocalhostProfile: ptr.To("foo-bar"),
 12750  						},
 12751  					},
 12752  				},
 12753  			},
 12754  		},
 12755  		"invalid AppArmor localhost profile": {
 12756  			expectedError: `Invalid value: "foo-bar "`,
 12757  			spec: core.Pod{
 12758  				ObjectMeta: metav1.ObjectMeta{
 12759  					Name:      "123",
 12760  					Namespace: "ns",
 12761  				},
 12762  				Spec: core.PodSpec{
 12763  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12764  					RestartPolicy: core.RestartPolicyAlways,
 12765  					DNSPolicy:     core.DNSDefault,
 12766  					SecurityContext: &core.PodSecurityContext{
 12767  						AppArmorProfile: &core.AppArmorProfile{
 12768  							Type:             core.AppArmorProfileTypeLocalhost,
 12769  							LocalhostProfile: ptr.To("foo-bar "),
 12770  						},
 12771  					},
 12772  				},
 12773  			},
 12774  		},
 12775  		"too long AppArmor localhost profile": {
 12776  			expectedError: "Too long: may not be longer than 4095",
 12777  			spec: core.Pod{
 12778  				ObjectMeta: metav1.ObjectMeta{
 12779  					Name:      "123",
 12780  					Namespace: "ns",
 12781  				},
 12782  				Spec: core.PodSpec{
 12783  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12784  					RestartPolicy: core.RestartPolicyAlways,
 12785  					DNSPolicy:     core.DNSDefault,
 12786  					SecurityContext: &core.PodSecurityContext{
 12787  						AppArmorProfile: &core.AppArmorProfile{
 12788  							Type:             core.AppArmorProfileTypeLocalhost,
 12789  							LocalhostProfile: ptr.To(strings.Repeat("a", 4096)),
 12790  						},
 12791  					},
 12792  				},
 12793  			},
 12794  		},
 12795  		"mismatched AppArmor field and annotation types": {
 12796  			expectedError: "Forbidden: apparmor type in annotation and field must match",
 12797  			spec: core.Pod{
 12798  				ObjectMeta: metav1.ObjectMeta{
 12799  					Name:      "123",
 12800  					Namespace: "ns",
 12801  					Annotations: map[string]string{
 12802  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
 12803  					},
 12804  				},
 12805  				Spec: core.PodSpec{
 12806  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12807  						SecurityContext: &core.SecurityContext{
 12808  							AppArmorProfile: &core.AppArmorProfile{
 12809  								Type: core.AppArmorProfileTypeUnconfined,
 12810  							},
 12811  						},
 12812  					}},
 12813  					RestartPolicy: core.RestartPolicyAlways,
 12814  					DNSPolicy:     core.DNSDefault,
 12815  				},
 12816  			},
 12817  		},
 12818  		"mismatched AppArmor pod field and annotation types": {
 12819  			expectedError: "Forbidden: apparmor type in annotation and field must match",
 12820  			spec: core.Pod{
 12821  				ObjectMeta: metav1.ObjectMeta{
 12822  					Name:      "123",
 12823  					Namespace: "ns",
 12824  					Annotations: map[string]string{
 12825  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueRuntimeDefault,
 12826  					},
 12827  				},
 12828  				Spec: core.PodSpec{
 12829  					SecurityContext: &core.PodSecurityContext{
 12830  						AppArmorProfile: &core.AppArmorProfile{
 12831  							Type: core.AppArmorProfileTypeUnconfined,
 12832  						},
 12833  					},
 12834  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12835  					RestartPolicy: core.RestartPolicyAlways,
 12836  					DNSPolicy:     core.DNSDefault,
 12837  				},
 12838  			},
 12839  		},
 12840  		"mismatched AppArmor localhost profiles": {
 12841  			expectedError: "Forbidden: apparmor profile in annotation and field must match",
 12842  			spec: core.Pod{
 12843  				ObjectMeta: metav1.ObjectMeta{
 12844  					Name:      "123",
 12845  					Namespace: "ns",
 12846  					Annotations: map[string]string{
 12847  						core.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": core.DeprecatedAppArmorAnnotationValueLocalhostPrefix + "foo",
 12848  					},
 12849  				},
 12850  				Spec: core.PodSpec{
 12851  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 12852  						SecurityContext: &core.SecurityContext{
 12853  							AppArmorProfile: &core.AppArmorProfile{
 12854  								Type:             core.AppArmorProfileTypeLocalhost,
 12855  								LocalhostProfile: ptr.To("bar"),
 12856  							},
 12857  						},
 12858  					}},
 12859  					RestartPolicy: core.RestartPolicyAlways,
 12860  					DNSPolicy:     core.DNSDefault,
 12861  				},
 12862  			},
 12863  		},
 12864  		"invalid extended resource name in container request": {
 12865  			expectedError: "must be a standard resource for containers",
 12866  			spec: core.Pod{
 12867  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12868  				Spec: core.PodSpec{
 12869  					Containers: []core.Container{{
 12870  						Name:            "invalid",
 12871  						Image:           "image",
 12872  						ImagePullPolicy: "IfNotPresent",
 12873  						Resources: core.ResourceRequirements{
 12874  							Requests: core.ResourceList{
 12875  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 12876  							},
 12877  							Limits: core.ResourceList{
 12878  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 12879  							},
 12880  						},
 12881  					}},
 12882  					RestartPolicy: core.RestartPolicyAlways,
 12883  					DNSPolicy:     core.DNSClusterFirst,
 12884  				},
 12885  			},
 12886  		},
 12887  		"invalid extended resource requirement: request must be == limit": {
 12888  			expectedError: "must be equal to example.com/a",
 12889  			spec: core.Pod{
 12890  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12891  				Spec: core.PodSpec{
 12892  					Containers: []core.Container{{
 12893  						Name:            "invalid",
 12894  						Image:           "image",
 12895  						ImagePullPolicy: "IfNotPresent",
 12896  						Resources: core.ResourceRequirements{
 12897  							Requests: core.ResourceList{
 12898  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 12899  							},
 12900  							Limits: core.ResourceList{
 12901  								core.ResourceName("example.com/a"): resource.MustParse("1"),
 12902  							},
 12903  						},
 12904  					}},
 12905  					RestartPolicy: core.RestartPolicyAlways,
 12906  					DNSPolicy:     core.DNSClusterFirst,
 12907  				},
 12908  			},
 12909  		},
 12910  		"invalid extended resource requirement without limit": {
 12911  			expectedError: "Limit must be set",
 12912  			spec: core.Pod{
 12913  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12914  				Spec: core.PodSpec{
 12915  					Containers: []core.Container{{
 12916  						Name:            "invalid",
 12917  						Image:           "image",
 12918  						ImagePullPolicy: "IfNotPresent",
 12919  						Resources: core.ResourceRequirements{
 12920  							Requests: core.ResourceList{
 12921  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 12922  							},
 12923  						},
 12924  					}},
 12925  					RestartPolicy: core.RestartPolicyAlways,
 12926  					DNSPolicy:     core.DNSClusterFirst,
 12927  				},
 12928  			},
 12929  		},
 12930  		"invalid fractional extended resource in container request": {
 12931  			expectedError: "must be an integer",
 12932  			spec: core.Pod{
 12933  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12934  				Spec: core.PodSpec{
 12935  					Containers: []core.Container{{
 12936  						Name:            "invalid",
 12937  						Image:           "image",
 12938  						ImagePullPolicy: "IfNotPresent",
 12939  						Resources: core.ResourceRequirements{
 12940  							Requests: core.ResourceList{
 12941  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 12942  							},
 12943  						},
 12944  					}},
 12945  					RestartPolicy: core.RestartPolicyAlways,
 12946  					DNSPolicy:     core.DNSClusterFirst,
 12947  				},
 12948  			},
 12949  		},
 12950  		"invalid fractional extended resource in init container request": {
 12951  			expectedError: "must be an integer",
 12952  			spec: core.Pod{
 12953  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12954  				Spec: core.PodSpec{
 12955  					InitContainers: []core.Container{{
 12956  						Name:            "invalid",
 12957  						Image:           "image",
 12958  						ImagePullPolicy: "IfNotPresent",
 12959  						Resources: core.ResourceRequirements{
 12960  							Requests: core.ResourceList{
 12961  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 12962  							},
 12963  						},
 12964  					}},
 12965  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12966  					RestartPolicy: core.RestartPolicyAlways,
 12967  					DNSPolicy:     core.DNSClusterFirst,
 12968  				},
 12969  			},
 12970  		},
 12971  		"invalid fractional extended resource in container limit": {
 12972  			expectedError: "must be an integer",
 12973  			spec: core.Pod{
 12974  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12975  				Spec: core.PodSpec{
 12976  					Containers: []core.Container{{
 12977  						Name:            "invalid",
 12978  						Image:           "image",
 12979  						ImagePullPolicy: "IfNotPresent",
 12980  						Resources: core.ResourceRequirements{
 12981  							Requests: core.ResourceList{
 12982  								core.ResourceName("example.com/a"): resource.MustParse("5"),
 12983  							},
 12984  							Limits: core.ResourceList{
 12985  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12986  							},
 12987  						},
 12988  					}},
 12989  					RestartPolicy: core.RestartPolicyAlways,
 12990  					DNSPolicy:     core.DNSClusterFirst,
 12991  				},
 12992  			},
 12993  		},
 12994  		"invalid fractional extended resource in init container limit": {
 12995  			expectedError: "must be an integer",
 12996  			spec: core.Pod{
 12997  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12998  				Spec: core.PodSpec{
 12999  					InitContainers: []core.Container{{
 13000  						Name:            "invalid",
 13001  						Image:           "image",
 13002  						ImagePullPolicy: "IfNotPresent",
 13003  						Resources: core.ResourceRequirements{
 13004  							Requests: core.ResourceList{
 13005  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 13006  							},
 13007  							Limits: core.ResourceList{
 13008  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 13009  							},
 13010  						},
 13011  					}},
 13012  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13013  					RestartPolicy: core.RestartPolicyAlways,
 13014  					DNSPolicy:     core.DNSClusterFirst,
 13015  				},
 13016  			},
 13017  		},
 13018  		"mirror-pod present without nodeName": {
 13019  			expectedError: "mirror",
 13020  			spec: core.Pod{
 13021  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}},
 13022  				Spec: core.PodSpec{
 13023  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13024  					RestartPolicy: core.RestartPolicyAlways,
 13025  					DNSPolicy:     core.DNSClusterFirst,
 13026  				},
 13027  			},
 13028  		},
 13029  		"mirror-pod populated without nodeName": {
 13030  			expectedError: "mirror",
 13031  			spec: core.Pod{
 13032  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}},
 13033  				Spec: core.PodSpec{
 13034  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13035  					RestartPolicy: core.RestartPolicyAlways,
 13036  					DNSPolicy:     core.DNSClusterFirst,
 13037  				},
 13038  			},
 13039  		},
 13040  		"serviceaccount token projected volume with no serviceaccount name specified": {
 13041  			expectedError: "must not be specified when serviceAccountName is not set",
 13042  			spec: core.Pod{
 13043  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 13044  				Spec: core.PodSpec{
 13045  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13046  					RestartPolicy: core.RestartPolicyAlways,
 13047  					DNSPolicy:     core.DNSClusterFirst,
 13048  					Volumes: []core.Volume{{
 13049  						Name: "projected-volume",
 13050  						VolumeSource: core.VolumeSource{
 13051  							Projected: &core.ProjectedVolumeSource{
 13052  								Sources: []core.VolumeProjection{{
 13053  									ServiceAccountToken: &core.ServiceAccountTokenProjection{
 13054  										Audience:          "foo-audience",
 13055  										ExpirationSeconds: 6000,
 13056  										Path:              "foo-path",
 13057  									},
 13058  								}},
 13059  							},
 13060  						},
 13061  					}},
 13062  				},
 13063  			},
 13064  		},
 13065  		"ClusterTrustBundlePEM projected volume using both byName and bySigner": {
 13066  			expectedError: "only one of name and signerName may be used",
 13067  			spec: core.Pod{
 13068  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13069  				Spec: core.PodSpec{
 13070  					ServiceAccountName: "some-service-account",
 13071  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13072  					RestartPolicy:      core.RestartPolicyAlways,
 13073  					DNSPolicy:          core.DNSClusterFirst,
 13074  					Volumes: []core.Volume{
 13075  						{
 13076  							Name: "projected-volume",
 13077  							VolumeSource: core.VolumeSource{
 13078  								Projected: &core.ProjectedVolumeSource{
 13079  									Sources: []core.VolumeProjection{
 13080  										{
 13081  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13082  												Path:       "foo-path",
 13083  												SignerName: utilpointer.String("example.com/foo"),
 13084  												LabelSelector: &metav1.LabelSelector{
 13085  													MatchLabels: map[string]string{
 13086  														"version": "live",
 13087  													},
 13088  												},
 13089  												Name: utilpointer.String("foo"),
 13090  											},
 13091  										},
 13092  									},
 13093  								},
 13094  							},
 13095  						},
 13096  					},
 13097  				},
 13098  			},
 13099  		},
 13100  		"ClusterTrustBundlePEM projected volume byName with no name": {
 13101  			expectedError: "must be a valid object name",
 13102  			spec: core.Pod{
 13103  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13104  				Spec: core.PodSpec{
 13105  					ServiceAccountName: "some-service-account",
 13106  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13107  					RestartPolicy:      core.RestartPolicyAlways,
 13108  					DNSPolicy:          core.DNSClusterFirst,
 13109  					Volumes: []core.Volume{
 13110  						{
 13111  							Name: "projected-volume",
 13112  							VolumeSource: core.VolumeSource{
 13113  								Projected: &core.ProjectedVolumeSource{
 13114  									Sources: []core.VolumeProjection{
 13115  										{
 13116  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13117  												Path: "foo-path",
 13118  												Name: utilpointer.String(""),
 13119  											},
 13120  										},
 13121  									},
 13122  								},
 13123  							},
 13124  						},
 13125  					},
 13126  				},
 13127  			},
 13128  		},
 13129  		"ClusterTrustBundlePEM projected volume bySigner with no signer name": {
 13130  			expectedError: "must be a valid signer name",
 13131  			spec: core.Pod{
 13132  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13133  				Spec: core.PodSpec{
 13134  					ServiceAccountName: "some-service-account",
 13135  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13136  					RestartPolicy:      core.RestartPolicyAlways,
 13137  					DNSPolicy:          core.DNSClusterFirst,
 13138  					Volumes: []core.Volume{
 13139  						{
 13140  							Name: "projected-volume",
 13141  							VolumeSource: core.VolumeSource{
 13142  								Projected: &core.ProjectedVolumeSource{
 13143  									Sources: []core.VolumeProjection{
 13144  										{
 13145  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13146  												Path:       "foo-path",
 13147  												SignerName: utilpointer.String(""),
 13148  												LabelSelector: &metav1.LabelSelector{
 13149  													MatchLabels: map[string]string{
 13150  														"foo": "bar",
 13151  													},
 13152  												},
 13153  											},
 13154  										},
 13155  									},
 13156  								},
 13157  							},
 13158  						},
 13159  					},
 13160  				},
 13161  			},
 13162  		},
 13163  		"ClusterTrustBundlePEM projected volume bySigner with invalid signer name": {
 13164  			expectedError: "must be a fully qualified domain and path of the form",
 13165  			spec: core.Pod{
 13166  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 13167  				Spec: core.PodSpec{
 13168  					ServiceAccountName: "some-service-account",
 13169  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13170  					RestartPolicy:      core.RestartPolicyAlways,
 13171  					DNSPolicy:          core.DNSClusterFirst,
 13172  					Volumes: []core.Volume{
 13173  						{
 13174  							Name: "projected-volume",
 13175  							VolumeSource: core.VolumeSource{
 13176  								Projected: &core.ProjectedVolumeSource{
 13177  									Sources: []core.VolumeProjection{
 13178  										{
 13179  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 13180  												Path:       "foo-path",
 13181  												SignerName: utilpointer.String("example.com/foo/invalid"),
 13182  											},
 13183  										},
 13184  									},
 13185  								},
 13186  							},
 13187  						},
 13188  					},
 13189  				},
 13190  			},
 13191  		},
 13192  		"final PVC name for ephemeral volume must be valid": {
 13193  			expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
 13194  			spec: core.Pod{
 13195  				ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
 13196  				Spec: core.PodSpec{
 13197  					Volumes: []core.Volume{
 13198  						{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 13199  						{Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 13200  					},
 13201  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13202  					RestartPolicy: core.RestartPolicyAlways,
 13203  					DNSPolicy:     core.DNSClusterFirst,
 13204  				},
 13205  			},
 13206  		},
 13207  		"PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
 13208  			expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
 13209  			spec: core.Pod{
 13210  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 13211  				Spec: core.PodSpec{
 13212  					Volumes: []core.Volume{
 13213  						{Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
 13214  						{Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 13215  					},
 13216  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13217  					RestartPolicy: core.RestartPolicyAlways,
 13218  					DNSPolicy:     core.DNSClusterFirst,
 13219  				},
 13220  			},
 13221  		},
 13222  		"invalid pod-deletion-cost": {
 13223  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"text\": must be a 32bit integer",
 13224  			spec: core.Pod{
 13225  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "text"}},
 13226  				Spec: core.PodSpec{
 13227  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13228  					RestartPolicy: core.RestartPolicyAlways,
 13229  					DNSPolicy:     core.DNSClusterFirst,
 13230  				},
 13231  			},
 13232  		},
 13233  		"invalid leading zeros pod-deletion-cost": {
 13234  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"008\": must be a 32bit integer",
 13235  			spec: core.Pod{
 13236  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "008"}},
 13237  				Spec: core.PodSpec{
 13238  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13239  					RestartPolicy: core.RestartPolicyAlways,
 13240  					DNSPolicy:     core.DNSClusterFirst,
 13241  				},
 13242  			},
 13243  		},
 13244  		"invalid leading plus sign pod-deletion-cost": {
 13245  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"+10\": must be a 32bit integer",
 13246  			spec: core.Pod{
 13247  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "+10"}},
 13248  				Spec: core.PodSpec{
 13249  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 13250  					RestartPolicy: core.RestartPolicyAlways,
 13251  					DNSPolicy:     core.DNSClusterFirst,
 13252  				},
 13253  			},
 13254  		},
 13255  	}
 13256  	for k, v := range errorCases {
 13257  		t.Run(k, func(t *testing.T) {
 13258  			if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
 13259  				t.Errorf("expected failure")
 13260  			} else if v.expectedError == "" {
 13261  				t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
 13262  			} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
 13263  				t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
 13264  			}
 13265  		})
 13266  	}
 13267  }
 13268  
 13269  func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
 13270  	applyEssentials := func(pod *core.Pod) {
 13271  		pod.Spec.Containers = []core.Container{
 13272  			{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
 13273  		}
 13274  		pod.Spec.RestartPolicy = core.RestartPolicyAlways
 13275  		pod.Spec.DNSPolicy = core.DNSClusterFirst
 13276  	}
 13277  	fldPath := field.NewPath("spec")
 13278  
 13279  	tests := []struct {
 13280  		name            string
 13281  		pod             *core.Pod
 13282  		wantFieldErrors field.ErrorList
 13283  	}{{
 13284  		name: "create a Pod with nodeName and schedulingGates, feature enabled",
 13285  		pod: &core.Pod{
 13286  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 13287  			Spec: core.PodSpec{
 13288  				NodeName: "node",
 13289  				SchedulingGates: []core.PodSchedulingGate{
 13290  					{Name: "foo"},
 13291  				},
 13292  			},
 13293  		},
 13294  		wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
 13295  	}, {
 13296  		name: "create a Pod with schedulingGates, feature enabled",
 13297  		pod: &core.Pod{
 13298  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 13299  			Spec: core.PodSpec{
 13300  				SchedulingGates: []core.PodSchedulingGate{
 13301  					{Name: "foo"},
 13302  				},
 13303  			},
 13304  		},
 13305  		wantFieldErrors: nil,
 13306  	},
 13307  	}
 13308  
 13309  	for _, tt := range tests {
 13310  		t.Run(tt.name, func(t *testing.T) {
 13311  			applyEssentials(tt.pod)
 13312  			errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
 13313  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 13314  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 13315  			}
 13316  		})
 13317  	}
 13318  }
 13319  
 13320  func TestValidatePodUpdate(t *testing.T) {
 13321  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
 13322  	var (
 13323  		activeDeadlineSecondsZero     = int64(0)
 13324  		activeDeadlineSecondsNegative = int64(-30)
 13325  		activeDeadlineSecondsPositive = int64(30)
 13326  		activeDeadlineSecondsLarger   = int64(31)
 13327  		validfsGroupChangePolicy      = core.FSGroupChangeOnRootMismatch
 13328  
 13329  		now    = metav1.Now()
 13330  		grace  = int64(30)
 13331  		grace2 = int64(31)
 13332  	)
 13333  
 13334  	tests := []struct {
 13335  		new  core.Pod
 13336  		old  core.Pod
 13337  		err  string
 13338  		test string
 13339  	}{
 13340  		{new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, {
 13341  			new: core.Pod{
 13342  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13343  			},
 13344  			old: core.Pod{
 13345  				ObjectMeta: metav1.ObjectMeta{Name: "bar"},
 13346  			},
 13347  			err:  "metadata.name",
 13348  			test: "ids",
 13349  		}, {
 13350  			new: core.Pod{
 13351  				ObjectMeta: metav1.ObjectMeta{
 13352  					Name: "foo",
 13353  					Labels: map[string]string{
 13354  						"foo": "bar",
 13355  					},
 13356  				},
 13357  			},
 13358  			old: core.Pod{
 13359  				ObjectMeta: metav1.ObjectMeta{
 13360  					Name: "foo",
 13361  					Labels: map[string]string{
 13362  						"bar": "foo",
 13363  					},
 13364  				},
 13365  			},
 13366  			err:  "",
 13367  			test: "labels",
 13368  		}, {
 13369  			new: core.Pod{
 13370  				ObjectMeta: metav1.ObjectMeta{
 13371  					Name: "foo",
 13372  					Annotations: map[string]string{
 13373  						"foo": "bar",
 13374  					},
 13375  				},
 13376  			},
 13377  			old: core.Pod{
 13378  				ObjectMeta: metav1.ObjectMeta{
 13379  					Name: "foo",
 13380  					Annotations: map[string]string{
 13381  						"bar": "foo",
 13382  					},
 13383  				},
 13384  			},
 13385  			err:  "",
 13386  			test: "annotations",
 13387  		}, {
 13388  			new: core.Pod{
 13389  				ObjectMeta: metav1.ObjectMeta{
 13390  					Name: "foo",
 13391  				},
 13392  				Spec: core.PodSpec{
 13393  					Containers: []core.Container{{
 13394  						Image: "foo:V1",
 13395  					}},
 13396  				},
 13397  			},
 13398  			old: core.Pod{
 13399  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13400  				Spec: core.PodSpec{
 13401  					Containers: []core.Container{{
 13402  						Image: "foo:V2",
 13403  					}, {
 13404  						Image: "bar:V2",
 13405  					}},
 13406  				},
 13407  			},
 13408  			err:  "may not add or remove containers",
 13409  			test: "less containers",
 13410  		}, {
 13411  			new: core.Pod{
 13412  				ObjectMeta: metav1.ObjectMeta{
 13413  					Name: "foo",
 13414  				},
 13415  				Spec: core.PodSpec{
 13416  					Containers: []core.Container{{
 13417  						Image: "foo:V1",
 13418  					}, {
 13419  						Image: "bar:V2",
 13420  					}},
 13421  				},
 13422  			},
 13423  			old: core.Pod{
 13424  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13425  				Spec: core.PodSpec{
 13426  					Containers: []core.Container{{
 13427  						Image: "foo:V2",
 13428  					}},
 13429  				},
 13430  			},
 13431  			err:  "may not add or remove containers",
 13432  			test: "more containers",
 13433  		}, {
 13434  			new: core.Pod{
 13435  				ObjectMeta: metav1.ObjectMeta{
 13436  					Name: "foo",
 13437  				},
 13438  				Spec: core.PodSpec{
 13439  					InitContainers: []core.Container{{
 13440  						Image: "foo:V1",
 13441  					}},
 13442  				},
 13443  			},
 13444  			old: core.Pod{
 13445  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13446  				Spec: core.PodSpec{
 13447  					InitContainers: []core.Container{{
 13448  						Image: "foo:V2",
 13449  					}, {
 13450  						Image: "bar:V2",
 13451  					}},
 13452  				},
 13453  			},
 13454  			err:  "may not add or remove containers",
 13455  			test: "more init containers",
 13456  		}, {
 13457  			new: core.Pod{
 13458  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13459  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13460  			},
 13461  			old: core.Pod{
 13462  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 13463  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13464  			},
 13465  			err:  "metadata.deletionTimestamp",
 13466  			test: "deletion timestamp removed",
 13467  		}, {
 13468  			new: core.Pod{
 13469  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 13470  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13471  			},
 13472  			old: core.Pod{
 13473  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13474  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13475  			},
 13476  			err:  "metadata.deletionTimestamp",
 13477  			test: "deletion timestamp added",
 13478  		}, {
 13479  			new: core.Pod{
 13480  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace},
 13481  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13482  			},
 13483  			old: core.Pod{
 13484  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2},
 13485  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 13486  			},
 13487  			err:  "metadata.deletionGracePeriodSeconds",
 13488  			test: "deletion grace period seconds changed",
 13489  		}, {
 13490  			new: core.Pod{
 13491  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13492  				Spec: core.PodSpec{
 13493  					Containers: []core.Container{{
 13494  						Name:                     "container",
 13495  						Image:                    "foo:V1",
 13496  						TerminationMessagePolicy: "File",
 13497  						ImagePullPolicy:          "Always",
 13498  					}},
 13499  				},
 13500  			},
 13501  			old: core.Pod{
 13502  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13503  				Spec: core.PodSpec{
 13504  					Containers: []core.Container{{
 13505  						Name:                     "container",
 13506  						Image:                    "foo:V2",
 13507  						TerminationMessagePolicy: "File",
 13508  						ImagePullPolicy:          "Always",
 13509  					}},
 13510  				},
 13511  			},
 13512  			err:  "",
 13513  			test: "image change",
 13514  		}, {
 13515  			new: core.Pod{
 13516  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13517  				Spec: core.PodSpec{
 13518  					InitContainers: []core.Container{{
 13519  						Name:                     "container",
 13520  						Image:                    "foo:V1",
 13521  						TerminationMessagePolicy: "File",
 13522  						ImagePullPolicy:          "Always",
 13523  					}},
 13524  				},
 13525  			},
 13526  			old: core.Pod{
 13527  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13528  				Spec: core.PodSpec{
 13529  					InitContainers: []core.Container{{
 13530  						Name:                     "container",
 13531  						Image:                    "foo:V2",
 13532  						TerminationMessagePolicy: "File",
 13533  						ImagePullPolicy:          "Always",
 13534  					}},
 13535  				},
 13536  			},
 13537  			err:  "",
 13538  			test: "init container image change",
 13539  		}, {
 13540  			new: core.Pod{
 13541  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13542  				Spec: core.PodSpec{
 13543  					Containers: []core.Container{{
 13544  						Name:                     "container",
 13545  						TerminationMessagePolicy: "File",
 13546  						ImagePullPolicy:          "Always",
 13547  					}},
 13548  				},
 13549  			},
 13550  			old: core.Pod{
 13551  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13552  				Spec: core.PodSpec{
 13553  					Containers: []core.Container{{
 13554  						Name:                     "container",
 13555  						Image:                    "foo:V2",
 13556  						TerminationMessagePolicy: "File",
 13557  						ImagePullPolicy:          "Always",
 13558  					}},
 13559  				},
 13560  			},
 13561  			err:  "spec.containers[0].image",
 13562  			test: "image change to empty",
 13563  		}, {
 13564  			new: core.Pod{
 13565  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13566  				Spec: core.PodSpec{
 13567  					InitContainers: []core.Container{{
 13568  						Name:                     "container",
 13569  						TerminationMessagePolicy: "File",
 13570  						ImagePullPolicy:          "Always",
 13571  					}},
 13572  				},
 13573  			},
 13574  			old: core.Pod{
 13575  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13576  				Spec: core.PodSpec{
 13577  					InitContainers: []core.Container{{
 13578  						Name:                     "container",
 13579  						Image:                    "foo:V2",
 13580  						TerminationMessagePolicy: "File",
 13581  						ImagePullPolicy:          "Always",
 13582  					}},
 13583  				},
 13584  			},
 13585  			err:  "spec.initContainers[0].image",
 13586  			test: "init container image change to empty",
 13587  		}, {
 13588  			new: core.Pod{
 13589  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13590  				Spec: core.PodSpec{
 13591  					EphemeralContainers: []core.EphemeralContainer{{
 13592  						EphemeralContainerCommon: core.EphemeralContainerCommon{
 13593  							Name:  "ephemeral",
 13594  							Image: "busybox",
 13595  						},
 13596  					}},
 13597  				},
 13598  			},
 13599  			old: core.Pod{
 13600  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13601  				Spec:       core.PodSpec{},
 13602  			},
 13603  			err:  "Forbidden: pod updates may not change fields other than",
 13604  			test: "ephemeralContainer changes are not allowed via normal pod update",
 13605  		}, {
 13606  			new: core.Pod{
 13607  				Spec: core.PodSpec{},
 13608  			},
 13609  			old: core.Pod{
 13610  				Spec: core.PodSpec{},
 13611  			},
 13612  			err:  "",
 13613  			test: "activeDeadlineSeconds no change, nil",
 13614  		}, {
 13615  			new: core.Pod{
 13616  				Spec: core.PodSpec{
 13617  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13618  				},
 13619  			},
 13620  			old: core.Pod{
 13621  				Spec: core.PodSpec{
 13622  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13623  				},
 13624  			},
 13625  			err:  "",
 13626  			test: "activeDeadlineSeconds no change, set",
 13627  		}, {
 13628  			new: core.Pod{
 13629  				Spec: core.PodSpec{
 13630  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13631  				},
 13632  			},
 13633  			old:  core.Pod{},
 13634  			err:  "",
 13635  			test: "activeDeadlineSeconds change to positive from nil",
 13636  		}, {
 13637  			new: core.Pod{
 13638  				Spec: core.PodSpec{
 13639  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13640  				},
 13641  			},
 13642  			old: core.Pod{
 13643  				Spec: core.PodSpec{
 13644  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 13645  				},
 13646  			},
 13647  			err:  "",
 13648  			test: "activeDeadlineSeconds change to smaller positive",
 13649  		}, {
 13650  			new: core.Pod{
 13651  				Spec: core.PodSpec{
 13652  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 13653  				},
 13654  			},
 13655  			old: core.Pod{
 13656  				Spec: core.PodSpec{
 13657  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13658  				},
 13659  			},
 13660  			err:  "spec.activeDeadlineSeconds",
 13661  			test: "activeDeadlineSeconds change to larger positive",
 13662  		},
 13663  
 13664  		{
 13665  			new: core.Pod{
 13666  				Spec: core.PodSpec{
 13667  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 13668  				},
 13669  			},
 13670  			old:  core.Pod{},
 13671  			err:  "spec.activeDeadlineSeconds",
 13672  			test: "activeDeadlineSeconds change to negative from nil",
 13673  		}, {
 13674  			new: core.Pod{
 13675  				Spec: core.PodSpec{
 13676  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 13677  				},
 13678  			},
 13679  			old: core.Pod{
 13680  				Spec: core.PodSpec{
 13681  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13682  				},
 13683  			},
 13684  			err:  "spec.activeDeadlineSeconds",
 13685  			test: "activeDeadlineSeconds change to negative from positive",
 13686  		}, {
 13687  			new: core.Pod{
 13688  				Spec: core.PodSpec{
 13689  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 13690  				},
 13691  			},
 13692  			old: core.Pod{
 13693  				Spec: core.PodSpec{
 13694  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13695  				},
 13696  			},
 13697  			err:  "spec.activeDeadlineSeconds",
 13698  			test: "activeDeadlineSeconds change to zero from positive",
 13699  		}, {
 13700  			new: core.Pod{
 13701  				Spec: core.PodSpec{
 13702  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 13703  				},
 13704  			},
 13705  			old:  core.Pod{},
 13706  			err:  "spec.activeDeadlineSeconds",
 13707  			test: "activeDeadlineSeconds change to zero from nil",
 13708  		}, {
 13709  			new: core.Pod{},
 13710  			old: core.Pod{
 13711  				Spec: core.PodSpec{
 13712  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 13713  				},
 13714  			},
 13715  			err:  "spec.activeDeadlineSeconds",
 13716  			test: "activeDeadlineSeconds change to nil from positive",
 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:V2",
 13726  						Resources: core.ResourceRequirements{
 13727  							Limits: getResources("200m", "0", "1Gi"),
 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  							Limits: getResources("100m", "0", "1Gi"),
 13742  						},
 13743  					}},
 13744  				},
 13745  			},
 13746  			err:  "",
 13747  			test: "cpu limit 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  							Limits: getResourceLimits("100m", "100Mi"),
 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  							Limits: getResourceLimits("100m", "200Mi"),
 13773  						},
 13774  					}},
 13775  				},
 13776  			},
 13777  			err:  "",
 13778  			test: "memory limit 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  							Limits: getResources("100m", "100Mi", "1Gi"),
 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  							Limits: getResources("100m", "100Mi", "2Gi"),
 13804  						},
 13805  					}},
 13806  				},
 13807  			},
 13808  			err:  "Forbidden: pod updates may not change fields other than",
 13809  			test: "storage limit 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  							Requests: getResourceLimits("100m", "0"),
 13821  						},
 13822  					}},
 13823  				},
 13824  			},
 13825  			old: core.Pod{
 13826  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13827  				Spec: core.PodSpec{
 13828  					Containers: []core.Container{{
 13829  						Name:                     "container",
 13830  						TerminationMessagePolicy: "File",
 13831  						ImagePullPolicy:          "Always",
 13832  						Image:                    "foo:V2",
 13833  						Resources: core.ResourceRequirements{
 13834  							Requests: getResourceLimits("200m", "0"),
 13835  						},
 13836  					}},
 13837  				},
 13838  			},
 13839  			err:  "",
 13840  			test: "cpu request change",
 13841  		}, {
 13842  			new: core.Pod{
 13843  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13844  				Spec: core.PodSpec{
 13845  					Containers: []core.Container{{
 13846  						Name:                     "container",
 13847  						TerminationMessagePolicy: "File",
 13848  						ImagePullPolicy:          "Always",
 13849  						Image:                    "foo:V1",
 13850  						Resources: core.ResourceRequirements{
 13851  							Requests: getResourceLimits("0", "200Mi"),
 13852  						},
 13853  					}},
 13854  				},
 13855  			},
 13856  			old: core.Pod{
 13857  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13858  				Spec: core.PodSpec{
 13859  					Containers: []core.Container{{
 13860  						Name:                     "container",
 13861  						TerminationMessagePolicy: "File",
 13862  						ImagePullPolicy:          "Always",
 13863  						Image:                    "foo:V2",
 13864  						Resources: core.ResourceRequirements{
 13865  							Requests: getResourceLimits("0", "100Mi"),
 13866  						},
 13867  					}},
 13868  				},
 13869  			},
 13870  			err:  "",
 13871  			test: "memory request change",
 13872  		}, {
 13873  			new: core.Pod{
 13874  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13875  				Spec: core.PodSpec{
 13876  					Containers: []core.Container{{
 13877  						Name:                     "container",
 13878  						TerminationMessagePolicy: "File",
 13879  						ImagePullPolicy:          "Always",
 13880  						Image:                    "foo:V1",
 13881  						Resources: core.ResourceRequirements{
 13882  							Requests: getResources("100m", "0", "2Gi"),
 13883  						},
 13884  					}},
 13885  				},
 13886  			},
 13887  			old: core.Pod{
 13888  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13889  				Spec: core.PodSpec{
 13890  					Containers: []core.Container{{
 13891  						Name:                     "container",
 13892  						TerminationMessagePolicy: "File",
 13893  						ImagePullPolicy:          "Always",
 13894  						Image:                    "foo:V2",
 13895  						Resources: core.ResourceRequirements{
 13896  							Requests: getResources("100m", "0", "1Gi"),
 13897  						},
 13898  					}},
 13899  				},
 13900  			},
 13901  			err:  "Forbidden: pod updates may not change fields other than",
 13902  			test: "storage request change",
 13903  		}, {
 13904  			new: core.Pod{
 13905  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13906  				Spec: core.PodSpec{
 13907  					Containers: []core.Container{{
 13908  						Name:                     "container",
 13909  						TerminationMessagePolicy: "File",
 13910  						ImagePullPolicy:          "Always",
 13911  						Image:                    "foo:V1",
 13912  						Resources: core.ResourceRequirements{
 13913  							Limits:   getResources("200m", "400Mi", "1Gi"),
 13914  							Requests: getResources("200m", "400Mi", "1Gi"),
 13915  						},
 13916  					}},
 13917  				},
 13918  			},
 13919  			old: core.Pod{
 13920  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13921  				Spec: core.PodSpec{
 13922  					Containers: []core.Container{{
 13923  						Name:                     "container",
 13924  						TerminationMessagePolicy: "File",
 13925  						ImagePullPolicy:          "Always",
 13926  						Image:                    "foo:V1",
 13927  						Resources: core.ResourceRequirements{
 13928  							Limits:   getResources("100m", "100Mi", "1Gi"),
 13929  							Requests: getResources("100m", "100Mi", "1Gi"),
 13930  						},
 13931  					}},
 13932  				},
 13933  			},
 13934  			err:  "",
 13935  			test: "Pod QoS unchanged, guaranteed -> guaranteed",
 13936  		}, {
 13937  			new: core.Pod{
 13938  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13939  				Spec: core.PodSpec{
 13940  					Containers: []core.Container{{
 13941  						Name:                     "container",
 13942  						TerminationMessagePolicy: "File",
 13943  						ImagePullPolicy:          "Always",
 13944  						Image:                    "foo:V1",
 13945  						Resources: core.ResourceRequirements{
 13946  							Limits:   getResources("200m", "200Mi", "2Gi"),
 13947  							Requests: getResources("100m", "100Mi", "1Gi"),
 13948  						},
 13949  					}},
 13950  				},
 13951  			},
 13952  			old: core.Pod{
 13953  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13954  				Spec: core.PodSpec{
 13955  					Containers: []core.Container{{
 13956  						Name:                     "container",
 13957  						TerminationMessagePolicy: "File",
 13958  						ImagePullPolicy:          "Always",
 13959  						Image:                    "foo:V1",
 13960  						Resources: core.ResourceRequirements{
 13961  							Limits:   getResources("400m", "400Mi", "2Gi"),
 13962  							Requests: getResources("200m", "200Mi", "1Gi"),
 13963  						},
 13964  					}},
 13965  				},
 13966  			},
 13967  			err:  "",
 13968  			test: "Pod QoS unchanged, burstable -> burstable",
 13969  		}, {
 13970  			new: core.Pod{
 13971  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13972  				Spec: core.PodSpec{
 13973  					Containers: []core.Container{{
 13974  						Name:                     "container",
 13975  						TerminationMessagePolicy: "File",
 13976  						ImagePullPolicy:          "Always",
 13977  						Image:                    "foo:V2",
 13978  						Resources: core.ResourceRequirements{
 13979  							Limits:   getResourceLimits("200m", "200Mi"),
 13980  							Requests: getResourceLimits("100m", "100Mi"),
 13981  						},
 13982  					}},
 13983  				},
 13984  			},
 13985  			old: core.Pod{
 13986  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13987  				Spec: core.PodSpec{
 13988  					Containers: []core.Container{{
 13989  						Name:                     "container",
 13990  						TerminationMessagePolicy: "File",
 13991  						ImagePullPolicy:          "Always",
 13992  						Image:                    "foo:V2",
 13993  						Resources: core.ResourceRequirements{
 13994  							Requests: getResourceLimits("100m", "100Mi"),
 13995  						},
 13996  					}},
 13997  				},
 13998  			},
 13999  			err:  "",
 14000  			test: "Pod QoS unchanged, burstable -> burstable, add limits",
 14001  		}, {
 14002  			new: core.Pod{
 14003  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14004  				Spec: core.PodSpec{
 14005  					Containers: []core.Container{{
 14006  						Name:                     "container",
 14007  						TerminationMessagePolicy: "File",
 14008  						ImagePullPolicy:          "Always",
 14009  						Image:                    "foo:V2",
 14010  						Resources: core.ResourceRequirements{
 14011  							Requests: getResourceLimits("100m", "100Mi"),
 14012  						},
 14013  					}},
 14014  				},
 14015  			},
 14016  			old: core.Pod{
 14017  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14018  				Spec: core.PodSpec{
 14019  					Containers: []core.Container{{
 14020  						Name:                     "container",
 14021  						TerminationMessagePolicy: "File",
 14022  						ImagePullPolicy:          "Always",
 14023  						Image:                    "foo:V2",
 14024  						Resources: core.ResourceRequirements{
 14025  							Limits:   getResourceLimits("200m", "200Mi"),
 14026  							Requests: getResourceLimits("100m", "100Mi"),
 14027  						},
 14028  					}},
 14029  				},
 14030  			},
 14031  			err:  "",
 14032  			test: "Pod QoS unchanged, burstable -> burstable, remove limits",
 14033  		}, {
 14034  			new: core.Pod{
 14035  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14036  				Spec: core.PodSpec{
 14037  					Containers: []core.Container{{
 14038  						Name:                     "container",
 14039  						TerminationMessagePolicy: "File",
 14040  						ImagePullPolicy:          "Always",
 14041  						Image:                    "foo:V2",
 14042  						Resources: core.ResourceRequirements{
 14043  							Limits:   getResources("400m", "", "1Gi"),
 14044  							Requests: getResources("300m", "", "1Gi"),
 14045  						},
 14046  					}},
 14047  				},
 14048  			},
 14049  			old: core.Pod{
 14050  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14051  				Spec: core.PodSpec{
 14052  					Containers: []core.Container{{
 14053  						Name:                     "container",
 14054  						TerminationMessagePolicy: "File",
 14055  						ImagePullPolicy:          "Always",
 14056  						Image:                    "foo:V2",
 14057  						Resources: core.ResourceRequirements{
 14058  							Limits: getResources("200m", "500Mi", "1Gi"),
 14059  						},
 14060  					}},
 14061  				},
 14062  			},
 14063  			err:  "",
 14064  			test: "Pod QoS unchanged, burstable -> burstable, add requests",
 14065  		}, {
 14066  			new: core.Pod{
 14067  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14068  				Spec: core.PodSpec{
 14069  					Containers: []core.Container{{
 14070  						Name:                     "container",
 14071  						TerminationMessagePolicy: "File",
 14072  						ImagePullPolicy:          "Always",
 14073  						Image:                    "foo:V2",
 14074  						Resources: core.ResourceRequirements{
 14075  							Limits: getResources("400m", "500Mi", "2Gi"),
 14076  						},
 14077  					}},
 14078  				},
 14079  			},
 14080  			old: core.Pod{
 14081  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14082  				Spec: core.PodSpec{
 14083  					Containers: []core.Container{{
 14084  						Name:                     "container",
 14085  						TerminationMessagePolicy: "File",
 14086  						ImagePullPolicy:          "Always",
 14087  						Image:                    "foo:V2",
 14088  						Resources: core.ResourceRequirements{
 14089  							Limits:   getResources("200m", "300Mi", "2Gi"),
 14090  							Requests: getResourceLimits("100m", "200Mi"),
 14091  						},
 14092  					}},
 14093  				},
 14094  			},
 14095  			err:  "",
 14096  			test: "Pod QoS unchanged, burstable -> burstable, remove requests",
 14097  		}, {
 14098  			new: core.Pod{
 14099  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14100  				Spec: core.PodSpec{
 14101  					Containers: []core.Container{{
 14102  						Name:                     "container",
 14103  						TerminationMessagePolicy: "File",
 14104  						ImagePullPolicy:          "Always",
 14105  						Image:                    "foo:V2",
 14106  						Resources: core.ResourceRequirements{
 14107  							Limits:   getResourceLimits("200m", "200Mi"),
 14108  							Requests: getResourceLimits("100m", "100Mi"),
 14109  						},
 14110  					}},
 14111  				},
 14112  			},
 14113  			old: core.Pod{
 14114  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14115  				Spec: core.PodSpec{
 14116  					Containers: []core.Container{{
 14117  						Name:                     "container",
 14118  						TerminationMessagePolicy: "File",
 14119  						ImagePullPolicy:          "Always",
 14120  						Image:                    "foo:V2",
 14121  						Resources: core.ResourceRequirements{
 14122  							Limits:   getResourceLimits("100m", "100Mi"),
 14123  							Requests: getResourceLimits("100m", "100Mi"),
 14124  						},
 14125  					}},
 14126  				},
 14127  			},
 14128  			err:  "Pod QoS is immutable",
 14129  			test: "Pod QoS change, guaranteed -> burstable",
 14130  		}, {
 14131  			new: core.Pod{
 14132  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14133  				Spec: core.PodSpec{
 14134  					Containers: []core.Container{{
 14135  						Name:                     "container",
 14136  						TerminationMessagePolicy: "File",
 14137  						ImagePullPolicy:          "Always",
 14138  						Image:                    "foo:V2",
 14139  						Resources: core.ResourceRequirements{
 14140  							Limits:   getResourceLimits("100m", "100Mi"),
 14141  							Requests: getResourceLimits("100m", "100Mi"),
 14142  						},
 14143  					}},
 14144  				},
 14145  			},
 14146  			old: core.Pod{
 14147  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14148  				Spec: core.PodSpec{
 14149  					Containers: []core.Container{{
 14150  						Name:                     "container",
 14151  						TerminationMessagePolicy: "File",
 14152  						ImagePullPolicy:          "Always",
 14153  						Image:                    "foo:V2",
 14154  						Resources: core.ResourceRequirements{
 14155  							Requests: getResourceLimits("100m", "100Mi"),
 14156  						},
 14157  					}},
 14158  				},
 14159  			},
 14160  			err:  "Pod QoS is immutable",
 14161  			test: "Pod QoS change, burstable -> guaranteed",
 14162  		}, {
 14163  			new: core.Pod{
 14164  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14165  				Spec: core.PodSpec{
 14166  					Containers: []core.Container{{
 14167  						Name:                     "container",
 14168  						TerminationMessagePolicy: "File",
 14169  						ImagePullPolicy:          "Always",
 14170  						Image:                    "foo:V2",
 14171  						Resources: core.ResourceRequirements{
 14172  							Limits:   getResourceLimits("200m", "200Mi"),
 14173  							Requests: getResourceLimits("100m", "100Mi"),
 14174  						},
 14175  					}},
 14176  				},
 14177  			},
 14178  			old: core.Pod{
 14179  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14180  				Spec: core.PodSpec{
 14181  					Containers: []core.Container{{
 14182  						Name:                     "container",
 14183  						TerminationMessagePolicy: "File",
 14184  						ImagePullPolicy:          "Always",
 14185  						Image:                    "foo:V2",
 14186  					}},
 14187  				},
 14188  			},
 14189  			err:  "Pod QoS is immutable",
 14190  			test: "Pod QoS change, besteffort -> burstable",
 14191  		}, {
 14192  			new: core.Pod{
 14193  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14194  				Spec: core.PodSpec{
 14195  					Containers: []core.Container{{
 14196  						Name:                     "container",
 14197  						TerminationMessagePolicy: "File",
 14198  						ImagePullPolicy:          "Always",
 14199  						Image:                    "foo:V2",
 14200  					}},
 14201  				},
 14202  			},
 14203  			old: core.Pod{
 14204  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 14205  				Spec: core.PodSpec{
 14206  					Containers: []core.Container{{
 14207  						Name:                     "container",
 14208  						TerminationMessagePolicy: "File",
 14209  						ImagePullPolicy:          "Always",
 14210  						Image:                    "foo:V2",
 14211  						Resources: core.ResourceRequirements{
 14212  							Limits:   getResourceLimits("200m", "200Mi"),
 14213  							Requests: getResourceLimits("100m", "100Mi"),
 14214  						},
 14215  					}},
 14216  				},
 14217  			},
 14218  			err:  "Pod QoS is immutable",
 14219  			test: "Pod QoS change, burstable -> besteffort",
 14220  		}, {
 14221  			new: core.Pod{
 14222  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14223  				Spec: core.PodSpec{
 14224  					Containers: []core.Container{{
 14225  						Image: "foo:V1",
 14226  					}},
 14227  					SecurityContext: &core.PodSecurityContext{
 14228  						FSGroupChangePolicy: &validfsGroupChangePolicy,
 14229  					},
 14230  				},
 14231  			},
 14232  			old: core.Pod{
 14233  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14234  				Spec: core.PodSpec{
 14235  					Containers: []core.Container{{
 14236  						Image: "foo:V2",
 14237  					}},
 14238  					SecurityContext: &core.PodSecurityContext{
 14239  						FSGroupChangePolicy: nil,
 14240  					},
 14241  				},
 14242  			},
 14243  			err:  "spec: Forbidden: pod updates may not change fields",
 14244  			test: "fsGroupChangePolicy change",
 14245  		}, {
 14246  			new: core.Pod{
 14247  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14248  				Spec: core.PodSpec{
 14249  					Containers: []core.Container{{
 14250  						Image: "foo:V1",
 14251  						Ports: []core.ContainerPort{
 14252  							{HostPort: 8080, ContainerPort: 80},
 14253  						},
 14254  					}},
 14255  				},
 14256  			},
 14257  			old: core.Pod{
 14258  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 14259  				Spec: core.PodSpec{
 14260  					Containers: []core.Container{{
 14261  						Image: "foo:V2",
 14262  						Ports: []core.ContainerPort{
 14263  							{HostPort: 8000, ContainerPort: 80},
 14264  						},
 14265  					}},
 14266  				},
 14267  			},
 14268  			err:  "spec: Forbidden: pod updates may not change fields",
 14269  			test: "port change",
 14270  		}, {
 14271  			new: core.Pod{
 14272  				ObjectMeta: metav1.ObjectMeta{
 14273  					Name: "foo",
 14274  					Labels: map[string]string{
 14275  						"foo": "bar",
 14276  					},
 14277  				},
 14278  			},
 14279  			old: core.Pod{
 14280  				ObjectMeta: metav1.ObjectMeta{
 14281  					Name: "foo",
 14282  					Labels: map[string]string{
 14283  						"Bar": "foo",
 14284  					},
 14285  				},
 14286  			},
 14287  			err:  "",
 14288  			test: "bad label change",
 14289  		}, {
 14290  			new: core.Pod{
 14291  				ObjectMeta: metav1.ObjectMeta{
 14292  					Name: "foo",
 14293  				},
 14294  				Spec: core.PodSpec{
 14295  					NodeName:    "node1",
 14296  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 14297  				},
 14298  			},
 14299  			old: core.Pod{
 14300  				ObjectMeta: metav1.ObjectMeta{
 14301  					Name: "foo",
 14302  				},
 14303  				Spec: core.PodSpec{
 14304  					NodeName:    "node1",
 14305  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14306  				},
 14307  			},
 14308  			err:  "spec.tolerations: Forbidden",
 14309  			test: "existing toleration value modified in pod spec updates",
 14310  		}, {
 14311  			new: core.Pod{
 14312  				ObjectMeta: metav1.ObjectMeta{
 14313  					Name: "foo",
 14314  				},
 14315  				Spec: core.PodSpec{
 14316  					NodeName:    "node1",
 14317  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}},
 14318  				},
 14319  			},
 14320  			old: core.Pod{
 14321  				ObjectMeta: metav1.ObjectMeta{
 14322  					Name: "foo",
 14323  				},
 14324  				Spec: core.PodSpec{
 14325  					NodeName:    "node1",
 14326  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14327  				},
 14328  			},
 14329  			err:  "spec.tolerations: Forbidden",
 14330  			test: "existing toleration value modified in pod spec updates with modified tolerationSeconds",
 14331  		}, {
 14332  			new: core.Pod{
 14333  				ObjectMeta: metav1.ObjectMeta{
 14334  					Name: "foo",
 14335  				},
 14336  				Spec: core.PodSpec{
 14337  					NodeName:    "node1",
 14338  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14339  				},
 14340  			},
 14341  			old: core.Pod{
 14342  				ObjectMeta: metav1.ObjectMeta{
 14343  					Name: "foo",
 14344  				},
 14345  				Spec: core.PodSpec{
 14346  					NodeName:    "node1",
 14347  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}},
 14348  				}},
 14349  			err:  "",
 14350  			test: "modified tolerationSeconds in existing toleration value in pod spec updates",
 14351  		}, {
 14352  			new: core.Pod{
 14353  				ObjectMeta: metav1.ObjectMeta{
 14354  					Name: "foo",
 14355  				},
 14356  				Spec: core.PodSpec{
 14357  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 14358  				},
 14359  			},
 14360  			old: core.Pod{
 14361  				ObjectMeta: metav1.ObjectMeta{
 14362  					Name: "foo",
 14363  				},
 14364  				Spec: core.PodSpec{
 14365  					NodeName:    "",
 14366  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14367  				},
 14368  			},
 14369  			err:  "spec.tolerations: Forbidden",
 14370  			test: "toleration modified in updates to an unscheduled pod",
 14371  		}, {
 14372  			new: core.Pod{
 14373  				ObjectMeta: metav1.ObjectMeta{
 14374  					Name: "foo",
 14375  				},
 14376  				Spec: core.PodSpec{
 14377  					NodeName:    "node1",
 14378  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14379  				},
 14380  			},
 14381  			old: core.Pod{
 14382  				ObjectMeta: metav1.ObjectMeta{
 14383  					Name: "foo",
 14384  				},
 14385  				Spec: core.PodSpec{
 14386  					NodeName:    "node1",
 14387  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 14388  				},
 14389  			},
 14390  			err:  "",
 14391  			test: "tolerations unmodified in updates to a scheduled pod",
 14392  		}, {
 14393  			new: core.Pod{
 14394  				ObjectMeta: metav1.ObjectMeta{
 14395  					Name: "foo",
 14396  				},
 14397  				Spec: core.PodSpec{
 14398  					NodeName: "node1",
 14399  					Tolerations: []core.Toleration{
 14400  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 14401  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]},
 14402  					},
 14403  				}},
 14404  			old: core.Pod{
 14405  				ObjectMeta: metav1.ObjectMeta{
 14406  					Name: "foo",
 14407  				},
 14408  				Spec: core.PodSpec{
 14409  					NodeName:    "node1",
 14410  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14411  				},
 14412  			},
 14413  			err:  "",
 14414  			test: "added valid new toleration to existing tolerations in pod spec updates",
 14415  		}, {
 14416  			new: core.Pod{
 14417  				ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{
 14418  					NodeName: "node1",
 14419  					Tolerations: []core.Toleration{
 14420  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 14421  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]},
 14422  					},
 14423  				}},
 14424  			old: core.Pod{
 14425  				ObjectMeta: metav1.ObjectMeta{
 14426  					Name: "foo",
 14427  				},
 14428  				Spec: core.PodSpec{
 14429  					NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 14430  				}},
 14431  			err:  "spec.tolerations[1].effect",
 14432  			test: "added invalid new toleration to existing tolerations in pod spec updates",
 14433  		}, {
 14434  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14435  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
 14436  			err:  "spec: Forbidden: pod updates may not change fields",
 14437  			test: "removed nodeName from pod spec",
 14438  		}, {
 14439  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 14440  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14441  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14442  			test: "added mirror pod annotation",
 14443  		}, {
 14444  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 14445  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 14446  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14447  			test: "removed mirror pod annotation",
 14448  		}, {
 14449  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}},
 14450  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}},
 14451  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 14452  			test: "changed mirror pod annotation",
 14453  		}, {
 14454  			new: core.Pod{
 14455  				ObjectMeta: metav1.ObjectMeta{
 14456  					Name: "foo",
 14457  				},
 14458  				Spec: core.PodSpec{
 14459  					NodeName:          "node1",
 14460  					PriorityClassName: "bar-priority",
 14461  				},
 14462  			},
 14463  			old: core.Pod{
 14464  				ObjectMeta: metav1.ObjectMeta{
 14465  					Name: "foo",
 14466  				},
 14467  				Spec: core.PodSpec{
 14468  					NodeName:          "node1",
 14469  					PriorityClassName: "foo-priority",
 14470  				},
 14471  			},
 14472  			err:  "spec: Forbidden: pod updates",
 14473  			test: "changed priority class name",
 14474  		}, {
 14475  			new: core.Pod{
 14476  				ObjectMeta: metav1.ObjectMeta{
 14477  					Name: "foo",
 14478  				},
 14479  				Spec: core.PodSpec{
 14480  					NodeName:          "node1",
 14481  					PriorityClassName: "",
 14482  				},
 14483  			},
 14484  			old: core.Pod{
 14485  				ObjectMeta: metav1.ObjectMeta{
 14486  					Name: "foo",
 14487  				},
 14488  				Spec: core.PodSpec{
 14489  					NodeName:          "node1",
 14490  					PriorityClassName: "foo-priority",
 14491  				},
 14492  			},
 14493  			err:  "spec: Forbidden: pod updates",
 14494  			test: "removed priority class name",
 14495  		}, {
 14496  			new: core.Pod{
 14497  				ObjectMeta: metav1.ObjectMeta{
 14498  					Name: "foo",
 14499  				},
 14500  				Spec: core.PodSpec{
 14501  					TerminationGracePeriodSeconds: utilpointer.Int64(1),
 14502  				},
 14503  			},
 14504  			old: core.Pod{
 14505  				ObjectMeta: metav1.ObjectMeta{
 14506  					Name: "foo",
 14507  				},
 14508  				Spec: core.PodSpec{
 14509  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 14510  				},
 14511  			},
 14512  			err:  "",
 14513  			test: "update termination grace period seconds",
 14514  		}, {
 14515  			new: core.Pod{
 14516  				ObjectMeta: metav1.ObjectMeta{
 14517  					Name: "foo",
 14518  				},
 14519  				Spec: core.PodSpec{
 14520  					TerminationGracePeriodSeconds: utilpointer.Int64(0),
 14521  				},
 14522  			},
 14523  			old: core.Pod{
 14524  				ObjectMeta: metav1.ObjectMeta{
 14525  					Name: "foo",
 14526  				},
 14527  				Spec: core.PodSpec{
 14528  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 14529  				},
 14530  			},
 14531  			err:  "spec: Forbidden: pod updates",
 14532  			test: "update termination grace period seconds not 1",
 14533  		}, {
 14534  			new: core.Pod{
 14535  				ObjectMeta: metav1.ObjectMeta{
 14536  					Name: "foo",
 14537  				},
 14538  				Spec: core.PodSpec{
 14539  					OS:              &core.PodOS{Name: core.Windows},
 14540  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14541  				},
 14542  			},
 14543  			old: core.Pod{
 14544  				ObjectMeta: metav1.ObjectMeta{
 14545  					Name: "foo",
 14546  				},
 14547  				Spec: core.PodSpec{
 14548  					OS:              &core.PodOS{Name: core.Linux},
 14549  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14550  				},
 14551  			},
 14552  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14553  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set",
 14554  		}, {
 14555  			new: core.Pod{
 14556  				ObjectMeta: metav1.ObjectMeta{
 14557  					Name: "foo",
 14558  				},
 14559  				Spec: core.PodSpec{
 14560  					OS:              &core.PodOS{Name: core.Windows},
 14561  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14562  				},
 14563  			},
 14564  			old: core.Pod{
 14565  				ObjectMeta: metav1.ObjectMeta{
 14566  					Name: "foo",
 14567  				},
 14568  				Spec: core.PodSpec{
 14569  					OS:              &core.PodOS{Name: core.Linux},
 14570  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 14571  				},
 14572  			},
 14573  			err:  "spec.securityContext.seLinuxOptions: Forbidden",
 14574  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well",
 14575  		}, {
 14576  			new: core.Pod{
 14577  				ObjectMeta: metav1.ObjectMeta{
 14578  					Name: "foo",
 14579  				},
 14580  				Spec: core.PodSpec{
 14581  					OS: &core.PodOS{Name: "dummy"},
 14582  				},
 14583  			},
 14584  			old: core.Pod{
 14585  				ObjectMeta: metav1.ObjectMeta{
 14586  					Name: "foo",
 14587  				},
 14588  				Spec: core.PodSpec{},
 14589  			},
 14590  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14591  			test: "invalid PodOS update, IdentifyPodOS featuregate set",
 14592  		}, {
 14593  			new: core.Pod{
 14594  				ObjectMeta: metav1.ObjectMeta{
 14595  					Name: "foo",
 14596  				},
 14597  				Spec: core.PodSpec{
 14598  					OS: &core.PodOS{Name: core.Linux},
 14599  				},
 14600  			},
 14601  			old: core.Pod{
 14602  				ObjectMeta: metav1.ObjectMeta{
 14603  					Name: "foo",
 14604  				},
 14605  				Spec: core.PodSpec{
 14606  					OS: &core.PodOS{Name: core.Windows},
 14607  				},
 14608  			},
 14609  			err:  "Forbidden: pod updates may not change fields other than ",
 14610  			test: "update pod spec OS to a valid value, featuregate disabled",
 14611  		}, {
 14612  			new: core.Pod{
 14613  				Spec: core.PodSpec{
 14614  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14615  				},
 14616  			},
 14617  			old:  core.Pod{},
 14618  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
 14619  			test: "update pod spec schedulingGates: add new scheduling gate",
 14620  		}, {
 14621  			new: core.Pod{
 14622  				Spec: core.PodSpec{
 14623  					SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
 14624  				},
 14625  			},
 14626  			old: core.Pod{
 14627  				Spec: core.PodSpec{
 14628  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14629  				},
 14630  			},
 14631  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
 14632  			test: "update pod spec schedulingGates: mutating an existing scheduling gate",
 14633  		}, {
 14634  			new: core.Pod{
 14635  				Spec: core.PodSpec{
 14636  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14637  				},
 14638  			},
 14639  			old: core.Pod{
 14640  				Spec: core.PodSpec{
 14641  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
 14642  				},
 14643  			},
 14644  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
 14645  			test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
 14646  		}, {
 14647  			new: core.Pod{},
 14648  			old: core.Pod{
 14649  				Spec: core.PodSpec{
 14650  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 14651  				},
 14652  			},
 14653  			err:  "",
 14654  			test: "update pod spec schedulingGates: legal deletion",
 14655  		}, {
 14656  			old: core.Pod{
 14657  				Spec: core.PodSpec{
 14658  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14659  				},
 14660  			},
 14661  			new: core.Pod{
 14662  				Spec: core.PodSpec{
 14663  					NodeSelector: map[string]string{
 14664  						"foo": "bar",
 14665  					},
 14666  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14667  				},
 14668  			},
 14669  			test: "adding node selector is allowed for gated pods",
 14670  		}, {
 14671  			old: core.Pod{
 14672  				Spec: core.PodSpec{
 14673  					NodeSelector: map[string]string{
 14674  						"foo": "bar",
 14675  					},
 14676  				},
 14677  			},
 14678  			new: core.Pod{
 14679  				Spec: core.PodSpec{
 14680  					NodeSelector: map[string]string{
 14681  						"foo":  "bar",
 14682  						"foo2": "bar2",
 14683  					},
 14684  				},
 14685  			},
 14686  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14687  			test: "adding node selector is not allowed for non-gated pods",
 14688  		}, {
 14689  			old: core.Pod{
 14690  				Spec: core.PodSpec{
 14691  					NodeSelector: map[string]string{
 14692  						"foo": "bar",
 14693  					},
 14694  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14695  				},
 14696  			},
 14697  			new: core.Pod{
 14698  				Spec: core.PodSpec{
 14699  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14700  				},
 14701  			},
 14702  			err:  "spec.nodeSelector: Invalid value:",
 14703  			test: "removing node selector is not allowed for gated pods",
 14704  		}, {
 14705  			old: core.Pod{
 14706  				Spec: core.PodSpec{
 14707  					NodeSelector: map[string]string{
 14708  						"foo": "bar",
 14709  					},
 14710  				},
 14711  			},
 14712  			new:  core.Pod{},
 14713  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14714  			test: "removing node selector is not allowed for non-gated pods",
 14715  		}, {
 14716  			old: core.Pod{
 14717  				Spec: core.PodSpec{
 14718  					NodeSelector: map[string]string{
 14719  						"foo": "bar",
 14720  					},
 14721  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14722  				},
 14723  			},
 14724  			new: core.Pod{
 14725  				Spec: core.PodSpec{
 14726  					NodeSelector: map[string]string{
 14727  						"foo":  "bar",
 14728  						"foo2": "bar2",
 14729  					},
 14730  				},
 14731  			},
 14732  			test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added",
 14733  		}, {
 14734  			old: core.Pod{
 14735  				Spec: core.PodSpec{
 14736  					NodeSelector: map[string]string{
 14737  						"foo": "bar",
 14738  					},
 14739  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14740  				},
 14741  			},
 14742  			new: core.Pod{
 14743  				Spec: core.PodSpec{
 14744  					NodeSelector: map[string]string{
 14745  						"foo": "new value",
 14746  					},
 14747  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14748  				},
 14749  			},
 14750  			err:  "spec.nodeSelector: Invalid value:",
 14751  			test: "modifying value of existing node selector is not allowed",
 14752  		}, {
 14753  			old: core.Pod{
 14754  				Spec: core.PodSpec{
 14755  					Affinity: &core.Affinity{
 14756  						NodeAffinity: &core.NodeAffinity{
 14757  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14758  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14759  									MatchExpressions: []core.NodeSelectorRequirement{{
 14760  										Key:      "expr",
 14761  										Operator: core.NodeSelectorOpIn,
 14762  										Values:   []string{"foo"},
 14763  									}},
 14764  								}},
 14765  							},
 14766  						},
 14767  					},
 14768  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14769  				},
 14770  			},
 14771  			new: core.Pod{
 14772  				Spec: core.PodSpec{
 14773  					Affinity: &core.Affinity{
 14774  						NodeAffinity: &core.NodeAffinity{
 14775  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14776  								// Add 1 MatchExpression and 1 MatchField.
 14777  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14778  									MatchExpressions: []core.NodeSelectorRequirement{{
 14779  										Key:      "expr",
 14780  										Operator: core.NodeSelectorOpIn,
 14781  										Values:   []string{"foo"},
 14782  									}, {
 14783  										Key:      "expr2",
 14784  										Operator: core.NodeSelectorOpIn,
 14785  										Values:   []string{"foo2"},
 14786  									}},
 14787  									MatchFields: []core.NodeSelectorRequirement{{
 14788  										Key:      "metadata.name",
 14789  										Operator: core.NodeSelectorOpIn,
 14790  										Values:   []string{"foo"},
 14791  									}},
 14792  								}},
 14793  							},
 14794  						},
 14795  					},
 14796  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14797  				},
 14798  			},
 14799  			test: "addition to nodeAffinity is allowed for gated pods",
 14800  		}, {
 14801  			old: core.Pod{
 14802  				Spec: core.PodSpec{
 14803  					Affinity: &core.Affinity{
 14804  						NodeAffinity: &core.NodeAffinity{
 14805  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14806  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14807  									MatchExpressions: []core.NodeSelectorRequirement{{
 14808  										Key:      "expr",
 14809  										Operator: core.NodeSelectorOpIn,
 14810  										Values:   []string{"foo"},
 14811  									}},
 14812  								}},
 14813  							},
 14814  						},
 14815  					},
 14816  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14817  				},
 14818  			},
 14819  			new: core.Pod{
 14820  				Spec: core.PodSpec{
 14821  					Affinity: &core.Affinity{
 14822  						NodeAffinity: &core.NodeAffinity{},
 14823  					},
 14824  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14825  				},
 14826  			},
 14827  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 14828  			test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated",
 14829  		}, {
 14830  			old: core.Pod{
 14831  				Spec: core.PodSpec{
 14832  					Affinity: &core.Affinity{
 14833  						NodeAffinity: &core.NodeAffinity{
 14834  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14835  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14836  									MatchExpressions: []core.NodeSelectorRequirement{{
 14837  										Key:      "expr",
 14838  										Operator: core.NodeSelectorOpIn,
 14839  										Values:   []string{"foo"},
 14840  									}},
 14841  								}},
 14842  							},
 14843  						},
 14844  					},
 14845  				},
 14846  			},
 14847  			new: core.Pod{
 14848  				Spec: core.PodSpec{
 14849  					Affinity: &core.Affinity{
 14850  						NodeAffinity: &core.NodeAffinity{
 14851  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14852  								// Add 1 MatchExpression and 1 MatchField.
 14853  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14854  									MatchExpressions: []core.NodeSelectorRequirement{{
 14855  										Key:      "expr",
 14856  										Operator: core.NodeSelectorOpIn,
 14857  										Values:   []string{"foo"},
 14858  									}, {
 14859  										Key:      "expr2",
 14860  										Operator: core.NodeSelectorOpIn,
 14861  										Values:   []string{"foo2"},
 14862  									}},
 14863  									MatchFields: []core.NodeSelectorRequirement{{
 14864  										Key:      "metadata.name",
 14865  										Operator: core.NodeSelectorOpIn,
 14866  										Values:   []string{"foo"},
 14867  									}},
 14868  								}},
 14869  							},
 14870  						},
 14871  					},
 14872  				},
 14873  			},
 14874  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 14875  			test: "addition to nodeAffinity is not allowed for non-gated pods",
 14876  		}, {
 14877  			old: core.Pod{
 14878  				Spec: core.PodSpec{
 14879  					Affinity: &core.Affinity{
 14880  						NodeAffinity: &core.NodeAffinity{
 14881  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14882  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14883  									MatchExpressions: []core.NodeSelectorRequirement{{
 14884  										Key:      "expr",
 14885  										Operator: core.NodeSelectorOpIn,
 14886  										Values:   []string{"foo"},
 14887  									}},
 14888  								}},
 14889  							},
 14890  						},
 14891  					},
 14892  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14893  				},
 14894  			},
 14895  			new: core.Pod{
 14896  				Spec: core.PodSpec{
 14897  					Affinity: &core.Affinity{
 14898  						NodeAffinity: &core.NodeAffinity{
 14899  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14900  								// Add 1 MatchExpression and 1 MatchField.
 14901  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14902  									MatchExpressions: []core.NodeSelectorRequirement{{
 14903  										Key:      "expr",
 14904  										Operator: core.NodeSelectorOpIn,
 14905  										Values:   []string{"foo"},
 14906  									}, {
 14907  										Key:      "expr2",
 14908  										Operator: core.NodeSelectorOpIn,
 14909  										Values:   []string{"foo2"},
 14910  									}},
 14911  									MatchFields: []core.NodeSelectorRequirement{{
 14912  										Key:      "metadata.name",
 14913  										Operator: core.NodeSelectorOpIn,
 14914  										Values:   []string{"foo"},
 14915  									}},
 14916  								}},
 14917  							},
 14918  						},
 14919  					},
 14920  				},
 14921  			},
 14922  			test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs",
 14923  		}, {
 14924  			old: core.Pod{
 14925  				Spec: core.PodSpec{
 14926  					Affinity: &core.Affinity{
 14927  						NodeAffinity: &core.NodeAffinity{
 14928  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14929  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14930  									MatchExpressions: []core.NodeSelectorRequirement{{
 14931  										Key:      "expr",
 14932  										Operator: core.NodeSelectorOpIn,
 14933  										Values:   []string{"foo"},
 14934  									}},
 14935  								}},
 14936  							},
 14937  						},
 14938  					},
 14939  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14940  				},
 14941  			},
 14942  			new: core.Pod{
 14943  				Spec: core.PodSpec{
 14944  					Affinity: &core.Affinity{
 14945  						NodeAffinity: &core.NodeAffinity{
 14946  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14947  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14948  									MatchFields: []core.NodeSelectorRequirement{{
 14949  										Key:      "metadata.name",
 14950  										Operator: core.NodeSelectorOpIn,
 14951  										Values:   []string{"foo"},
 14952  									}},
 14953  								}},
 14954  							},
 14955  						},
 14956  					},
 14957  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14958  				},
 14959  			},
 14960  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14961  			test: "nodeAffinity deletion from MatchExpressions not allowed",
 14962  		}, {
 14963  			old: core.Pod{
 14964  				Spec: core.PodSpec{
 14965  					Affinity: &core.Affinity{
 14966  						NodeAffinity: &core.NodeAffinity{
 14967  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14968  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14969  									MatchExpressions: []core.NodeSelectorRequirement{{
 14970  										Key:      "expr",
 14971  										Operator: core.NodeSelectorOpIn,
 14972  										Values:   []string{"foo"},
 14973  									}},
 14974  									MatchFields: []core.NodeSelectorRequirement{{
 14975  										Key:      "metadata.name",
 14976  										Operator: core.NodeSelectorOpIn,
 14977  										Values:   []string{"foo"},
 14978  									}},
 14979  								}},
 14980  							},
 14981  						},
 14982  					},
 14983  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14984  				},
 14985  			},
 14986  			new: core.Pod{
 14987  				Spec: core.PodSpec{
 14988  					Affinity: &core.Affinity{
 14989  						NodeAffinity: &core.NodeAffinity{
 14990  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14991  								// Add 1 MatchExpression and 1 MatchField.
 14992  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14993  									MatchExpressions: []core.NodeSelectorRequirement{{
 14994  										Key:      "expr",
 14995  										Operator: core.NodeSelectorOpIn,
 14996  										Values:   []string{"foo"},
 14997  									}},
 14998  								}},
 14999  							},
 15000  						},
 15001  					},
 15002  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15003  				},
 15004  			},
 15005  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15006  			test: "nodeAffinity deletion from MatchFields not allowed",
 15007  		}, {
 15008  			old: core.Pod{
 15009  				Spec: core.PodSpec{
 15010  					Affinity: &core.Affinity{
 15011  						NodeAffinity: &core.NodeAffinity{
 15012  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15013  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15014  									MatchExpressions: []core.NodeSelectorRequirement{{
 15015  										Key:      "expr",
 15016  										Operator: core.NodeSelectorOpIn,
 15017  										Values:   []string{"foo"},
 15018  									}},
 15019  									MatchFields: []core.NodeSelectorRequirement{{
 15020  										Key:      "metadata.name",
 15021  										Operator: core.NodeSelectorOpIn,
 15022  										Values:   []string{"foo"},
 15023  									}},
 15024  								}},
 15025  							},
 15026  						},
 15027  					},
 15028  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15029  				},
 15030  			},
 15031  			new: core.Pod{
 15032  				Spec: core.PodSpec{
 15033  					Affinity: &core.Affinity{
 15034  						NodeAffinity: &core.NodeAffinity{
 15035  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15036  								// Add 1 MatchExpression and 1 MatchField.
 15037  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15038  									MatchExpressions: []core.NodeSelectorRequirement{{
 15039  										Key:      "expr",
 15040  										Operator: core.NodeSelectorOpIn,
 15041  										Values:   []string{"bar"},
 15042  									}},
 15043  									MatchFields: []core.NodeSelectorRequirement{{
 15044  										Key:      "metadata.name",
 15045  										Operator: core.NodeSelectorOpIn,
 15046  										Values:   []string{"foo"},
 15047  									}},
 15048  								}},
 15049  							},
 15050  						},
 15051  					},
 15052  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15053  				},
 15054  			},
 15055  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15056  			test: "nodeAffinity modification of item in MatchExpressions not allowed",
 15057  		}, {
 15058  			old: core.Pod{
 15059  				Spec: core.PodSpec{
 15060  					Affinity: &core.Affinity{
 15061  						NodeAffinity: &core.NodeAffinity{
 15062  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15063  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15064  									MatchExpressions: []core.NodeSelectorRequirement{{
 15065  										Key:      "expr",
 15066  										Operator: core.NodeSelectorOpIn,
 15067  										Values:   []string{"foo"},
 15068  									}},
 15069  									MatchFields: []core.NodeSelectorRequirement{{
 15070  										Key:      "metadata.name",
 15071  										Operator: core.NodeSelectorOpIn,
 15072  										Values:   []string{"foo"},
 15073  									}},
 15074  								}},
 15075  							},
 15076  						},
 15077  					},
 15078  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15079  				},
 15080  			},
 15081  			new: core.Pod{
 15082  				Spec: core.PodSpec{
 15083  					Affinity: &core.Affinity{
 15084  						NodeAffinity: &core.NodeAffinity{
 15085  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15086  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15087  									MatchExpressions: []core.NodeSelectorRequirement{{
 15088  										Key:      "expr",
 15089  										Operator: core.NodeSelectorOpIn,
 15090  										Values:   []string{"foo"},
 15091  									}},
 15092  									MatchFields: []core.NodeSelectorRequirement{{
 15093  										Key:      "metadata.name",
 15094  										Operator: core.NodeSelectorOpIn,
 15095  										Values:   []string{"bar"},
 15096  									}},
 15097  								}},
 15098  							},
 15099  						},
 15100  					},
 15101  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15102  				},
 15103  			},
 15104  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15105  			test: "nodeAffinity modification of item in MatchFields not allowed",
 15106  		}, {
 15107  			old: core.Pod{
 15108  				Spec: core.PodSpec{
 15109  					Affinity: &core.Affinity{
 15110  						NodeAffinity: &core.NodeAffinity{
 15111  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15112  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15113  									MatchExpressions: []core.NodeSelectorRequirement{{
 15114  										Key:      "expr",
 15115  										Operator: core.NodeSelectorOpIn,
 15116  										Values:   []string{"foo"},
 15117  									}},
 15118  									MatchFields: []core.NodeSelectorRequirement{{
 15119  										Key:      "metadata.name",
 15120  										Operator: core.NodeSelectorOpIn,
 15121  										Values:   []string{"foo"},
 15122  									}},
 15123  								}},
 15124  							},
 15125  						},
 15126  					},
 15127  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15128  				},
 15129  			},
 15130  			new: core.Pod{
 15131  				Spec: core.PodSpec{
 15132  					Affinity: &core.Affinity{
 15133  						NodeAffinity: &core.NodeAffinity{
 15134  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15135  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15136  									MatchExpressions: []core.NodeSelectorRequirement{{
 15137  										Key:      "expr",
 15138  										Operator: core.NodeSelectorOpIn,
 15139  										Values:   []string{"foo"},
 15140  									}},
 15141  									MatchFields: []core.NodeSelectorRequirement{{
 15142  										Key:      "metadata.name",
 15143  										Operator: core.NodeSelectorOpIn,
 15144  										Values:   []string{"bar"},
 15145  									}},
 15146  								}, {
 15147  									MatchExpressions: []core.NodeSelectorRequirement{{
 15148  										Key:      "expr",
 15149  										Operator: core.NodeSelectorOpIn,
 15150  										Values:   []string{"foo2"},
 15151  									}},
 15152  									MatchFields: []core.NodeSelectorRequirement{{
 15153  										Key:      "metadata.name",
 15154  										Operator: core.NodeSelectorOpIn,
 15155  										Values:   []string{"bar2"},
 15156  									}},
 15157  								}},
 15158  							},
 15159  						},
 15160  					},
 15161  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15162  				},
 15163  			},
 15164  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 15165  			test: "nodeSelectorTerms addition on gated pod should fail",
 15166  		}, {
 15167  			old: core.Pod{
 15168  				Spec: core.PodSpec{
 15169  					Affinity: &core.Affinity{
 15170  						NodeAffinity: &core.NodeAffinity{
 15171  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15172  								Weight: 1.0,
 15173  								Preference: core.NodeSelectorTerm{
 15174  									MatchExpressions: []core.NodeSelectorRequirement{{
 15175  										Key:      "expr",
 15176  										Operator: core.NodeSelectorOpIn,
 15177  										Values:   []string{"foo"},
 15178  									}},
 15179  								},
 15180  							}},
 15181  						},
 15182  					},
 15183  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15184  				},
 15185  			},
 15186  			new: core.Pod{
 15187  				Spec: core.PodSpec{
 15188  					Affinity: &core.Affinity{
 15189  						NodeAffinity: &core.NodeAffinity{
 15190  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15191  								Weight: 1.0,
 15192  								Preference: core.NodeSelectorTerm{
 15193  									MatchExpressions: []core.NodeSelectorRequirement{{
 15194  										Key:      "expr",
 15195  										Operator: core.NodeSelectorOpIn,
 15196  										Values:   []string{"foo2"},
 15197  									}},
 15198  								},
 15199  							}},
 15200  						},
 15201  					},
 15202  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15203  				},
 15204  			},
 15205  			test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods",
 15206  		}, {
 15207  			old: core.Pod{
 15208  				Spec: core.PodSpec{
 15209  					Affinity: &core.Affinity{
 15210  						NodeAffinity: &core.NodeAffinity{
 15211  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15212  								Weight: 1.0,
 15213  								Preference: core.NodeSelectorTerm{
 15214  									MatchExpressions: []core.NodeSelectorRequirement{{
 15215  										Key:      "expr",
 15216  										Operator: core.NodeSelectorOpIn,
 15217  										Values:   []string{"foo"},
 15218  									}},
 15219  								},
 15220  							}},
 15221  						},
 15222  					},
 15223  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15224  				},
 15225  			},
 15226  			new: core.Pod{
 15227  				Spec: core.PodSpec{
 15228  					Affinity: &core.Affinity{
 15229  						NodeAffinity: &core.NodeAffinity{
 15230  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15231  								Weight: 1.0,
 15232  								Preference: core.NodeSelectorTerm{
 15233  									MatchExpressions: []core.NodeSelectorRequirement{{
 15234  										Key:      "expr",
 15235  										Operator: core.NodeSelectorOpIn,
 15236  										Values:   []string{"foo"},
 15237  									}, {
 15238  										Key:      "expr2",
 15239  										Operator: core.NodeSelectorOpIn,
 15240  										Values:   []string{"foo2"},
 15241  									}},
 15242  									MatchFields: []core.NodeSelectorRequirement{{
 15243  										Key:      "metadata.name",
 15244  										Operator: core.NodeSelectorOpIn,
 15245  										Values:   []string{"bar"},
 15246  									}},
 15247  								},
 15248  							}},
 15249  						},
 15250  					},
 15251  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15252  				},
 15253  			},
 15254  			test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods",
 15255  		}, {
 15256  			old: core.Pod{
 15257  				Spec: core.PodSpec{
 15258  					Affinity: &core.Affinity{
 15259  						NodeAffinity: &core.NodeAffinity{
 15260  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15261  								Weight: 1.0,
 15262  								Preference: core.NodeSelectorTerm{
 15263  									MatchExpressions: []core.NodeSelectorRequirement{{
 15264  										Key:      "expr",
 15265  										Operator: core.NodeSelectorOpIn,
 15266  										Values:   []string{"foo"},
 15267  									}},
 15268  								},
 15269  							}},
 15270  						},
 15271  					},
 15272  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15273  				},
 15274  			},
 15275  			new: core.Pod{
 15276  				Spec: core.PodSpec{
 15277  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15278  				},
 15279  			},
 15280  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 15281  		}, {
 15282  			old: core.Pod{
 15283  				Spec: core.PodSpec{
 15284  					Affinity: &core.Affinity{
 15285  						NodeAffinity: &core.NodeAffinity{
 15286  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15287  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15288  									MatchExpressions: []core.NodeSelectorRequirement{{
 15289  										Key:      "expr",
 15290  										Operator: core.NodeSelectorOpIn,
 15291  										Values:   []string{"foo"},
 15292  									}},
 15293  								}},
 15294  							},
 15295  						},
 15296  					},
 15297  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15298  				},
 15299  			},
 15300  			new: core.Pod{
 15301  				Spec: core.PodSpec{
 15302  					Affinity:        &core.Affinity{},
 15303  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15304  				},
 15305  			},
 15306  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 15307  			test: "new node affinity is nil",
 15308  		}, {
 15309  			old: core.Pod{
 15310  				Spec: core.PodSpec{
 15311  					Affinity: &core.Affinity{
 15312  						NodeAffinity: &core.NodeAffinity{
 15313  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 15314  								Weight: 1.0,
 15315  								Preference: core.NodeSelectorTerm{
 15316  									MatchExpressions: []core.NodeSelectorRequirement{{
 15317  										Key:      "expr",
 15318  										Operator: core.NodeSelectorOpIn,
 15319  										Values:   []string{"foo"},
 15320  									}},
 15321  								},
 15322  							}},
 15323  						},
 15324  					},
 15325  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15326  				},
 15327  			},
 15328  			new: core.Pod{
 15329  				Spec: core.PodSpec{
 15330  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15331  				},
 15332  			},
 15333  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 15334  		}, {
 15335  			old: core.Pod{
 15336  				Spec: core.PodSpec{
 15337  					Affinity: &core.Affinity{
 15338  						NodeAffinity: &core.NodeAffinity{
 15339  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15340  								NodeSelectorTerms: []core.NodeSelectorTerm{
 15341  									{},
 15342  								},
 15343  							},
 15344  						},
 15345  					},
 15346  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15347  				},
 15348  			},
 15349  			new: core.Pod{
 15350  				Spec: core.PodSpec{
 15351  					Affinity: &core.Affinity{
 15352  						NodeAffinity: &core.NodeAffinity{
 15353  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15354  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15355  									MatchExpressions: []core.NodeSelectorRequirement{{
 15356  										Key:      "expr",
 15357  										Operator: core.NodeSelectorOpIn,
 15358  										Values:   []string{"foo"},
 15359  									}},
 15360  								}},
 15361  							},
 15362  						},
 15363  					},
 15364  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15365  				},
 15366  			},
 15367  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 15368  			test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)",
 15369  		}, {
 15370  			old: core.Pod{
 15371  				Spec: core.PodSpec{
 15372  					Affinity:        nil,
 15373  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15374  				},
 15375  			},
 15376  			new: core.Pod{
 15377  				Spec: core.PodSpec{
 15378  					Affinity: &core.Affinity{
 15379  						NodeAffinity: &core.NodeAffinity{
 15380  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15381  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15382  									MatchExpressions: []core.NodeSelectorRequirement{{
 15383  										Key:      "expr",
 15384  										Operator: core.NodeSelectorOpIn,
 15385  										Values:   []string{"foo"},
 15386  									}},
 15387  								}},
 15388  							},
 15389  						},
 15390  					},
 15391  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15392  				},
 15393  			},
 15394  			test: "nil affinity can be mutated for gated pods",
 15395  		},
 15396  		{
 15397  			old: core.Pod{
 15398  				Spec: core.PodSpec{
 15399  					Affinity:        nil,
 15400  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15401  				},
 15402  			},
 15403  			new: core.Pod{
 15404  				Spec: core.PodSpec{
 15405  					Affinity: &core.Affinity{
 15406  						NodeAffinity: &core.NodeAffinity{
 15407  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15408  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15409  									MatchExpressions: []core.NodeSelectorRequirement{{
 15410  										Key:      "expr",
 15411  										Operator: core.NodeSelectorOpIn,
 15412  										Values:   []string{"foo"},
 15413  									}},
 15414  								}},
 15415  							},
 15416  						},
 15417  						PodAffinity: &core.PodAffinity{
 15418  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 15419  								{
 15420  									TopologyKey: "foo",
 15421  									LabelSelector: &metav1.LabelSelector{
 15422  										MatchLabels: map[string]string{"foo": "bar"},
 15423  									},
 15424  								},
 15425  							},
 15426  						},
 15427  					},
 15428  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15429  				},
 15430  			},
 15431  			err:  "pod updates may not change fields other than",
 15432  			test: "the podAffinity cannot be updated on gated pods",
 15433  		},
 15434  		{
 15435  			old: core.Pod{
 15436  				Spec: core.PodSpec{
 15437  					Affinity:        nil,
 15438  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15439  				},
 15440  			},
 15441  			new: core.Pod{
 15442  				Spec: core.PodSpec{
 15443  					Affinity: &core.Affinity{
 15444  						NodeAffinity: &core.NodeAffinity{
 15445  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 15446  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 15447  									MatchExpressions: []core.NodeSelectorRequirement{{
 15448  										Key:      "expr",
 15449  										Operator: core.NodeSelectorOpIn,
 15450  										Values:   []string{"foo"},
 15451  									}},
 15452  								}},
 15453  							},
 15454  						},
 15455  						PodAntiAffinity: &core.PodAntiAffinity{
 15456  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 15457  								{
 15458  									TopologyKey: "foo",
 15459  									LabelSelector: &metav1.LabelSelector{
 15460  										MatchLabels: map[string]string{"foo": "bar"},
 15461  									},
 15462  								},
 15463  							},
 15464  						},
 15465  					},
 15466  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 15467  				},
 15468  			},
 15469  			err:  "pod updates may not change fields other than",
 15470  			test: "the podAntiAffinity cannot be updated on gated pods",
 15471  		},
 15472  	}
 15473  	for _, test := range tests {
 15474  		test.new.ObjectMeta.ResourceVersion = "1"
 15475  		test.old.ObjectMeta.ResourceVersion = "1"
 15476  
 15477  		// set required fields if old and new match and have no opinion on the value
 15478  		if test.new.Name == "" && test.old.Name == "" {
 15479  			test.new.Name = "name"
 15480  			test.old.Name = "name"
 15481  		}
 15482  		if test.new.Namespace == "" && test.old.Namespace == "" {
 15483  			test.new.Namespace = "namespace"
 15484  			test.old.Namespace = "namespace"
 15485  		}
 15486  		if test.new.Spec.Containers == nil && test.old.Spec.Containers == nil {
 15487  			test.new.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 15488  			test.old.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 15489  		}
 15490  		if len(test.new.Spec.DNSPolicy) == 0 && len(test.old.Spec.DNSPolicy) == 0 {
 15491  			test.new.Spec.DNSPolicy = core.DNSClusterFirst
 15492  			test.old.Spec.DNSPolicy = core.DNSClusterFirst
 15493  		}
 15494  		if len(test.new.Spec.RestartPolicy) == 0 && len(test.old.Spec.RestartPolicy) == 0 {
 15495  			test.new.Spec.RestartPolicy = "Always"
 15496  			test.old.Spec.RestartPolicy = "Always"
 15497  		}
 15498  
 15499  		errs := ValidatePodUpdate(&test.new, &test.old, PodValidationOptions{})
 15500  		if test.err == "" {
 15501  			if len(errs) != 0 {
 15502  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 15503  			}
 15504  		} else {
 15505  			if len(errs) == 0 {
 15506  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 15507  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 15508  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 15509  			}
 15510  		}
 15511  	}
 15512  }
 15513  
 15514  func TestValidatePodStatusUpdate(t *testing.T) {
 15515  	tests := []struct {
 15516  		new  core.Pod
 15517  		old  core.Pod
 15518  		err  string
 15519  		test string
 15520  	}{{
 15521  		core.Pod{
 15522  			ObjectMeta: metav1.ObjectMeta{
 15523  				Name: "foo",
 15524  			},
 15525  			Spec: core.PodSpec{
 15526  				NodeName: "node1",
 15527  			},
 15528  			Status: core.PodStatus{
 15529  				NominatedNodeName: "node1",
 15530  			},
 15531  		},
 15532  		core.Pod{
 15533  			ObjectMeta: metav1.ObjectMeta{
 15534  				Name: "foo",
 15535  			},
 15536  			Spec: core.PodSpec{
 15537  				NodeName: "node1",
 15538  			},
 15539  			Status: core.PodStatus{},
 15540  		},
 15541  		"",
 15542  		"removed nominatedNodeName",
 15543  	}, {
 15544  		core.Pod{
 15545  			ObjectMeta: metav1.ObjectMeta{
 15546  				Name: "foo",
 15547  			},
 15548  			Spec: core.PodSpec{
 15549  				NodeName: "node1",
 15550  			},
 15551  		},
 15552  		core.Pod{
 15553  			ObjectMeta: metav1.ObjectMeta{
 15554  				Name: "foo",
 15555  			},
 15556  			Spec: core.PodSpec{
 15557  				NodeName: "node1",
 15558  			},
 15559  			Status: core.PodStatus{
 15560  				NominatedNodeName: "node1",
 15561  			},
 15562  		},
 15563  		"",
 15564  		"add valid nominatedNodeName",
 15565  	}, {
 15566  		core.Pod{
 15567  			ObjectMeta: metav1.ObjectMeta{
 15568  				Name: "foo",
 15569  			},
 15570  			Spec: core.PodSpec{
 15571  				NodeName: "node1",
 15572  			},
 15573  			Status: core.PodStatus{
 15574  				NominatedNodeName: "Node1",
 15575  			},
 15576  		},
 15577  		core.Pod{
 15578  			ObjectMeta: metav1.ObjectMeta{
 15579  				Name: "foo",
 15580  			},
 15581  			Spec: core.PodSpec{
 15582  				NodeName: "node1",
 15583  			},
 15584  		},
 15585  		"nominatedNodeName",
 15586  		"Add invalid nominatedNodeName",
 15587  	}, {
 15588  		core.Pod{
 15589  			ObjectMeta: metav1.ObjectMeta{
 15590  				Name: "foo",
 15591  			},
 15592  			Spec: core.PodSpec{
 15593  				NodeName: "node1",
 15594  			},
 15595  			Status: core.PodStatus{
 15596  				NominatedNodeName: "node1",
 15597  			},
 15598  		},
 15599  		core.Pod{
 15600  			ObjectMeta: metav1.ObjectMeta{
 15601  				Name: "foo",
 15602  			},
 15603  			Spec: core.PodSpec{
 15604  				NodeName: "node1",
 15605  			},
 15606  			Status: core.PodStatus{
 15607  				NominatedNodeName: "node2",
 15608  			},
 15609  		},
 15610  		"",
 15611  		"Update nominatedNodeName",
 15612  	}, {
 15613  		core.Pod{
 15614  			ObjectMeta: metav1.ObjectMeta{
 15615  				Name: "foo",
 15616  			},
 15617  			Status: core.PodStatus{
 15618  				InitContainerStatuses: []core.ContainerStatus{{
 15619  					ContainerID: "docker://numbers",
 15620  					Image:       "alpine",
 15621  					Name:        "init",
 15622  					Ready:       false,
 15623  					Started:     proto.Bool(false),
 15624  					State: core.ContainerState{
 15625  						Waiting: &core.ContainerStateWaiting{
 15626  							Reason: "PodInitializing",
 15627  						},
 15628  					},
 15629  				}},
 15630  				ContainerStatuses: []core.ContainerStatus{{
 15631  					ContainerID: "docker://numbers",
 15632  					Image:       "nginx:alpine",
 15633  					Name:        "main",
 15634  					Ready:       false,
 15635  					Started:     proto.Bool(false),
 15636  					State: core.ContainerState{
 15637  						Waiting: &core.ContainerStateWaiting{
 15638  							Reason: "PodInitializing",
 15639  						},
 15640  					},
 15641  				}},
 15642  			},
 15643  		},
 15644  		core.Pod{
 15645  			ObjectMeta: metav1.ObjectMeta{
 15646  				Name: "foo",
 15647  			},
 15648  		},
 15649  		"",
 15650  		"Container statuses pending",
 15651  	}, {
 15652  		core.Pod{
 15653  			ObjectMeta: metav1.ObjectMeta{
 15654  				Name: "foo",
 15655  			},
 15656  			Status: core.PodStatus{
 15657  				InitContainerStatuses: []core.ContainerStatus{{
 15658  					ContainerID: "docker://numbers",
 15659  					Image:       "alpine",
 15660  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15661  					Name:        "init",
 15662  					Ready:       true,
 15663  					State: core.ContainerState{
 15664  						Terminated: &core.ContainerStateTerminated{
 15665  							ContainerID: "docker://numbers",
 15666  							Reason:      "Completed",
 15667  						},
 15668  					},
 15669  				}},
 15670  				ContainerStatuses: []core.ContainerStatus{{
 15671  					ContainerID: "docker://numbers",
 15672  					Image:       "nginx:alpine",
 15673  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15674  					Name:        "nginx",
 15675  					Ready:       true,
 15676  					Started:     proto.Bool(true),
 15677  					State: core.ContainerState{
 15678  						Running: &core.ContainerStateRunning{
 15679  							StartedAt: metav1.NewTime(time.Now()),
 15680  						},
 15681  					},
 15682  				}},
 15683  			},
 15684  		},
 15685  		core.Pod{
 15686  			ObjectMeta: metav1.ObjectMeta{
 15687  				Name: "foo",
 15688  			},
 15689  			Status: core.PodStatus{
 15690  				InitContainerStatuses: []core.ContainerStatus{{
 15691  					ContainerID: "docker://numbers",
 15692  					Image:       "alpine",
 15693  					Name:        "init",
 15694  					Ready:       false,
 15695  					State: core.ContainerState{
 15696  						Waiting: &core.ContainerStateWaiting{
 15697  							Reason: "PodInitializing",
 15698  						},
 15699  					},
 15700  				}},
 15701  				ContainerStatuses: []core.ContainerStatus{{
 15702  					ContainerID: "docker://numbers",
 15703  					Image:       "nginx:alpine",
 15704  					Name:        "main",
 15705  					Ready:       false,
 15706  					Started:     proto.Bool(false),
 15707  					State: core.ContainerState{
 15708  						Waiting: &core.ContainerStateWaiting{
 15709  							Reason: "PodInitializing",
 15710  						},
 15711  					},
 15712  				}},
 15713  			},
 15714  		},
 15715  		"",
 15716  		"Container statuses running",
 15717  	}, {
 15718  		core.Pod{
 15719  			ObjectMeta: metav1.ObjectMeta{
 15720  				Name: "foo",
 15721  			},
 15722  			Status: core.PodStatus{
 15723  				ContainerStatuses: []core.ContainerStatus{{
 15724  					ContainerID: "docker://numbers",
 15725  					Image:       "nginx:alpine",
 15726  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15727  					Name:        "nginx",
 15728  					Ready:       true,
 15729  					Started:     proto.Bool(true),
 15730  					State: core.ContainerState{
 15731  						Running: &core.ContainerStateRunning{
 15732  							StartedAt: metav1.NewTime(time.Now()),
 15733  						},
 15734  					},
 15735  				}},
 15736  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15737  					ContainerID: "docker://numbers",
 15738  					Image:       "busybox",
 15739  					Name:        "debug",
 15740  					Ready:       false,
 15741  					State: core.ContainerState{
 15742  						Waiting: &core.ContainerStateWaiting{
 15743  							Reason: "PodInitializing",
 15744  						},
 15745  					},
 15746  				}},
 15747  			},
 15748  		},
 15749  		core.Pod{
 15750  			ObjectMeta: metav1.ObjectMeta{
 15751  				Name: "foo",
 15752  			},
 15753  			Status: core.PodStatus{
 15754  				ContainerStatuses: []core.ContainerStatus{{
 15755  					ContainerID: "docker://numbers",
 15756  					Image:       "nginx:alpine",
 15757  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15758  					Name:        "nginx",
 15759  					Ready:       true,
 15760  					Started:     proto.Bool(true),
 15761  					State: core.ContainerState{
 15762  						Running: &core.ContainerStateRunning{
 15763  							StartedAt: metav1.NewTime(time.Now()),
 15764  						},
 15765  					},
 15766  				}},
 15767  			},
 15768  		},
 15769  		"",
 15770  		"Container statuses add ephemeral container",
 15771  	}, {
 15772  		core.Pod{
 15773  			ObjectMeta: metav1.ObjectMeta{
 15774  				Name: "foo",
 15775  			},
 15776  			Status: core.PodStatus{
 15777  				ContainerStatuses: []core.ContainerStatus{{
 15778  					ContainerID: "docker://numbers",
 15779  					Image:       "nginx:alpine",
 15780  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15781  					Name:        "nginx",
 15782  					Ready:       true,
 15783  					Started:     proto.Bool(true),
 15784  					State: core.ContainerState{
 15785  						Running: &core.ContainerStateRunning{
 15786  							StartedAt: metav1.NewTime(time.Now()),
 15787  						},
 15788  					},
 15789  				}},
 15790  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15791  					ContainerID: "docker://numbers",
 15792  					Image:       "busybox",
 15793  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15794  					Name:        "debug",
 15795  					Ready:       false,
 15796  					State: core.ContainerState{
 15797  						Running: &core.ContainerStateRunning{
 15798  							StartedAt: metav1.NewTime(time.Now()),
 15799  						},
 15800  					},
 15801  				}},
 15802  			},
 15803  		},
 15804  		core.Pod{
 15805  			ObjectMeta: metav1.ObjectMeta{
 15806  				Name: "foo",
 15807  			},
 15808  			Status: core.PodStatus{
 15809  				ContainerStatuses: []core.ContainerStatus{{
 15810  					ContainerID: "docker://numbers",
 15811  					Image:       "nginx:alpine",
 15812  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15813  					Name:        "nginx",
 15814  					Ready:       true,
 15815  					Started:     proto.Bool(true),
 15816  					State: core.ContainerState{
 15817  						Running: &core.ContainerStateRunning{
 15818  							StartedAt: metav1.NewTime(time.Now()),
 15819  						},
 15820  					},
 15821  				}},
 15822  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15823  					ContainerID: "docker://numbers",
 15824  					Image:       "busybox",
 15825  					Name:        "debug",
 15826  					Ready:       false,
 15827  					State: core.ContainerState{
 15828  						Waiting: &core.ContainerStateWaiting{
 15829  							Reason: "PodInitializing",
 15830  						},
 15831  					},
 15832  				}},
 15833  			},
 15834  		},
 15835  		"",
 15836  		"Container statuses ephemeral container running",
 15837  	}, {
 15838  		core.Pod{
 15839  			ObjectMeta: metav1.ObjectMeta{
 15840  				Name: "foo",
 15841  			},
 15842  			Status: core.PodStatus{
 15843  				ContainerStatuses: []core.ContainerStatus{{
 15844  					ContainerID: "docker://numbers",
 15845  					Image:       "nginx:alpine",
 15846  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15847  					Name:        "nginx",
 15848  					Ready:       true,
 15849  					Started:     proto.Bool(true),
 15850  					State: core.ContainerState{
 15851  						Running: &core.ContainerStateRunning{
 15852  							StartedAt: metav1.NewTime(time.Now()),
 15853  						},
 15854  					},
 15855  				}},
 15856  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15857  					ContainerID: "docker://numbers",
 15858  					Image:       "busybox",
 15859  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15860  					Name:        "debug",
 15861  					Ready:       false,
 15862  					State: core.ContainerState{
 15863  						Terminated: &core.ContainerStateTerminated{
 15864  							ContainerID: "docker://numbers",
 15865  							Reason:      "Completed",
 15866  							StartedAt:   metav1.NewTime(time.Now()),
 15867  							FinishedAt:  metav1.NewTime(time.Now()),
 15868  						},
 15869  					},
 15870  				}},
 15871  			},
 15872  		},
 15873  		core.Pod{
 15874  			ObjectMeta: metav1.ObjectMeta{
 15875  				Name: "foo",
 15876  			},
 15877  			Status: core.PodStatus{
 15878  				ContainerStatuses: []core.ContainerStatus{{
 15879  					ContainerID: "docker://numbers",
 15880  					Image:       "nginx:alpine",
 15881  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15882  					Name:        "nginx",
 15883  					Ready:       true,
 15884  					Started:     proto.Bool(true),
 15885  					State: core.ContainerState{
 15886  						Running: &core.ContainerStateRunning{
 15887  							StartedAt: metav1.NewTime(time.Now()),
 15888  						},
 15889  					},
 15890  				}},
 15891  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15892  					ContainerID: "docker://numbers",
 15893  					Image:       "busybox",
 15894  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15895  					Name:        "debug",
 15896  					Ready:       false,
 15897  					State: core.ContainerState{
 15898  						Running: &core.ContainerStateRunning{
 15899  							StartedAt: metav1.NewTime(time.Now()),
 15900  						},
 15901  					},
 15902  				}},
 15903  			},
 15904  		},
 15905  		"",
 15906  		"Container statuses ephemeral container exited",
 15907  	}, {
 15908  		core.Pod{
 15909  			ObjectMeta: metav1.ObjectMeta{
 15910  				Name: "foo",
 15911  			},
 15912  			Status: core.PodStatus{
 15913  				InitContainerStatuses: []core.ContainerStatus{{
 15914  					ContainerID: "docker://numbers",
 15915  					Image:       "alpine",
 15916  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15917  					Name:        "init",
 15918  					Ready:       true,
 15919  					State: core.ContainerState{
 15920  						Terminated: &core.ContainerStateTerminated{
 15921  							ContainerID: "docker://numbers",
 15922  							Reason:      "Completed",
 15923  						},
 15924  					},
 15925  				}},
 15926  				ContainerStatuses: []core.ContainerStatus{{
 15927  					ContainerID: "docker://numbers",
 15928  					Image:       "nginx:alpine",
 15929  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15930  					Name:        "nginx",
 15931  					Ready:       true,
 15932  					Started:     proto.Bool(true),
 15933  					State: core.ContainerState{
 15934  						Terminated: &core.ContainerStateTerminated{
 15935  							ContainerID: "docker://numbers",
 15936  							Reason:      "Completed",
 15937  							StartedAt:   metav1.NewTime(time.Now()),
 15938  							FinishedAt:  metav1.NewTime(time.Now()),
 15939  						},
 15940  					},
 15941  				}},
 15942  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15943  					ContainerID: "docker://numbers",
 15944  					Image:       "busybox",
 15945  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15946  					Name:        "debug",
 15947  					Ready:       false,
 15948  					State: core.ContainerState{
 15949  						Terminated: &core.ContainerStateTerminated{
 15950  							ContainerID: "docker://numbers",
 15951  							Reason:      "Completed",
 15952  							StartedAt:   metav1.NewTime(time.Now()),
 15953  							FinishedAt:  metav1.NewTime(time.Now()),
 15954  						},
 15955  					},
 15956  				}},
 15957  			},
 15958  		},
 15959  		core.Pod{
 15960  			ObjectMeta: metav1.ObjectMeta{
 15961  				Name: "foo",
 15962  			},
 15963  			Status: core.PodStatus{
 15964  				InitContainerStatuses: []core.ContainerStatus{{
 15965  					ContainerID: "docker://numbers",
 15966  					Image:       "alpine",
 15967  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15968  					Name:        "init",
 15969  					Ready:       true,
 15970  					State: core.ContainerState{
 15971  						Terminated: &core.ContainerStateTerminated{
 15972  							ContainerID: "docker://numbers",
 15973  							Reason:      "Completed",
 15974  						},
 15975  					},
 15976  				}},
 15977  				ContainerStatuses: []core.ContainerStatus{{
 15978  					ContainerID: "docker://numbers",
 15979  					Image:       "nginx:alpine",
 15980  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15981  					Name:        "nginx",
 15982  					Ready:       true,
 15983  					Started:     proto.Bool(true),
 15984  					State: core.ContainerState{
 15985  						Running: &core.ContainerStateRunning{
 15986  							StartedAt: metav1.NewTime(time.Now()),
 15987  						},
 15988  					},
 15989  				}},
 15990  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15991  					ContainerID: "docker://numbers",
 15992  					Image:       "busybox",
 15993  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15994  					Name:        "debug",
 15995  					Ready:       false,
 15996  					State: core.ContainerState{
 15997  						Running: &core.ContainerStateRunning{
 15998  							StartedAt: metav1.NewTime(time.Now()),
 15999  						},
 16000  					},
 16001  				}},
 16002  			},
 16003  		},
 16004  		"",
 16005  		"Container statuses all containers terminated",
 16006  	}, {
 16007  		core.Pod{
 16008  			ObjectMeta: metav1.ObjectMeta{
 16009  				Name: "foo",
 16010  			},
 16011  			Status: core.PodStatus{
 16012  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 16013  					{Name: "no-such-claim", ResourceClaimName: utilpointer.String("my-claim")},
 16014  				},
 16015  			},
 16016  		},
 16017  		core.Pod{
 16018  			ObjectMeta: metav1.ObjectMeta{
 16019  				Name: "foo",
 16020  			},
 16021  		},
 16022  		"status.resourceClaimStatuses[0].name: Invalid value: \"no-such-claim\": must match the name of an entry in `spec.resourceClaims`",
 16023  		"Non-existent PodResourceClaim",
 16024  	}, {
 16025  		core.Pod{
 16026  			ObjectMeta: metav1.ObjectMeta{
 16027  				Name: "foo",
 16028  			},
 16029  			Spec: core.PodSpec{
 16030  				ResourceClaims: []core.PodResourceClaim{
 16031  					{Name: "my-claim"},
 16032  				},
 16033  			},
 16034  			Status: core.PodStatus{
 16035  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 16036  					{Name: "my-claim", ResourceClaimName: utilpointer.String("%$!#")},
 16037  				},
 16038  			},
 16039  		},
 16040  		core.Pod{
 16041  			ObjectMeta: metav1.ObjectMeta{
 16042  				Name: "foo",
 16043  			},
 16044  			Spec: core.PodSpec{
 16045  				ResourceClaims: []core.PodResourceClaim{
 16046  					{Name: "my-claim"},
 16047  				},
 16048  			},
 16049  		},
 16050  		`status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`,
 16051  		"Invalid ResourceClaim name",
 16052  	}, {
 16053  		core.Pod{
 16054  			ObjectMeta: metav1.ObjectMeta{
 16055  				Name: "foo",
 16056  			},
 16057  			Spec: core.PodSpec{
 16058  				ResourceClaims: []core.PodResourceClaim{
 16059  					{Name: "my-claim"},
 16060  					{Name: "my-other-claim"},
 16061  				},
 16062  			},
 16063  			Status: core.PodStatus{
 16064  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 16065  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 16066  					{Name: "my-other-claim", ResourceClaimName: nil},
 16067  					{Name: "my-other-claim", ResourceClaimName: nil},
 16068  				},
 16069  			},
 16070  		},
 16071  		core.Pod{
 16072  			ObjectMeta: metav1.ObjectMeta{
 16073  				Name: "foo",
 16074  			},
 16075  			Spec: core.PodSpec{
 16076  				ResourceClaims: []core.PodResourceClaim{
 16077  					{Name: "my-claim"},
 16078  				},
 16079  			},
 16080  		},
 16081  		`status.resourceClaimStatuses[2].name: Duplicate value: "my-other-claim"`,
 16082  		"Duplicate ResourceClaimStatuses.Name",
 16083  	}, {
 16084  		core.Pod{
 16085  			ObjectMeta: metav1.ObjectMeta{
 16086  				Name: "foo",
 16087  			},
 16088  			Spec: core.PodSpec{
 16089  				ResourceClaims: []core.PodResourceClaim{
 16090  					{Name: "my-claim"},
 16091  					{Name: "my-other-claim"},
 16092  				},
 16093  			},
 16094  			Status: core.PodStatus{
 16095  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 16096  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 16097  					{Name: "my-other-claim", ResourceClaimName: nil},
 16098  				},
 16099  			},
 16100  		},
 16101  		core.Pod{
 16102  			ObjectMeta: metav1.ObjectMeta{
 16103  				Name: "foo",
 16104  			},
 16105  			Spec: core.PodSpec{
 16106  				ResourceClaims: []core.PodResourceClaim{
 16107  					{Name: "my-claim"},
 16108  				},
 16109  			},
 16110  		},
 16111  		"",
 16112  		"ResourceClaimStatuses okay",
 16113  	}, {
 16114  		core.Pod{
 16115  			ObjectMeta: metav1.ObjectMeta{
 16116  				Name: "foo",
 16117  			},
 16118  			Spec: core.PodSpec{
 16119  				InitContainers: []core.Container{
 16120  					{
 16121  						Name: "init",
 16122  					},
 16123  				},
 16124  				Containers: []core.Container{
 16125  					{
 16126  						Name: "nginx",
 16127  					},
 16128  				},
 16129  			},
 16130  			Status: core.PodStatus{
 16131  				InitContainerStatuses: []core.ContainerStatus{{
 16132  					ContainerID: "docker://numbers",
 16133  					Image:       "alpine",
 16134  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16135  					Name:        "init",
 16136  					Ready:       true,
 16137  					State: core.ContainerState{
 16138  						Running: &core.ContainerStateRunning{
 16139  							StartedAt: metav1.NewTime(time.Now()),
 16140  						},
 16141  					},
 16142  				}},
 16143  				ContainerStatuses: []core.ContainerStatus{{
 16144  					ContainerID: "docker://numbers",
 16145  					Image:       "nginx:alpine",
 16146  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16147  					Name:        "nginx",
 16148  					Ready:       true,
 16149  					Started:     proto.Bool(true),
 16150  					State: core.ContainerState{
 16151  						Running: &core.ContainerStateRunning{
 16152  							StartedAt: metav1.NewTime(time.Now()),
 16153  						},
 16154  					},
 16155  				}},
 16156  			},
 16157  		},
 16158  		core.Pod{
 16159  			ObjectMeta: metav1.ObjectMeta{
 16160  				Name: "foo",
 16161  			},
 16162  			Spec: core.PodSpec{
 16163  				InitContainers: []core.Container{
 16164  					{
 16165  						Name: "init",
 16166  					},
 16167  				},
 16168  				Containers: []core.Container{
 16169  					{
 16170  						Name: "nginx",
 16171  					},
 16172  				},
 16173  				RestartPolicy: core.RestartPolicyNever,
 16174  			},
 16175  			Status: core.PodStatus{
 16176  				InitContainerStatuses: []core.ContainerStatus{{
 16177  					ContainerID: "docker://numbers",
 16178  					Image:       "alpine",
 16179  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16180  					Name:        "init",
 16181  					Ready:       false,
 16182  					State: core.ContainerState{
 16183  						Terminated: &core.ContainerStateTerminated{
 16184  							ContainerID: "docker://numbers",
 16185  							Reason:      "Completed",
 16186  						},
 16187  					},
 16188  				}},
 16189  				ContainerStatuses: []core.ContainerStatus{{
 16190  					ContainerID: "docker://numbers",
 16191  					Image:       "nginx:alpine",
 16192  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16193  					Name:        "nginx",
 16194  					Ready:       true,
 16195  					Started:     proto.Bool(true),
 16196  					State: core.ContainerState{
 16197  						Running: &core.ContainerStateRunning{
 16198  							StartedAt: metav1.NewTime(time.Now()),
 16199  						},
 16200  					},
 16201  				}},
 16202  			},
 16203  		},
 16204  		`status.initContainerStatuses[0].state: Forbidden: may not be transitioned to non-terminated state`,
 16205  		"init container cannot restart if RestartPolicyNever",
 16206  	}, {
 16207  		core.Pod{
 16208  			ObjectMeta: metav1.ObjectMeta{
 16209  				Name: "foo",
 16210  			},
 16211  			Spec: core.PodSpec{
 16212  				InitContainers: []core.Container{
 16213  					{
 16214  						Name:          "restartable-init",
 16215  						RestartPolicy: &containerRestartPolicyAlways,
 16216  					},
 16217  				},
 16218  				Containers: []core.Container{
 16219  					{
 16220  						Name: "nginx",
 16221  					},
 16222  				},
 16223  				RestartPolicy: core.RestartPolicyNever,
 16224  			},
 16225  			Status: core.PodStatus{
 16226  				InitContainerStatuses: []core.ContainerStatus{{
 16227  					ContainerID: "docker://numbers",
 16228  					Image:       "alpine",
 16229  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16230  					Name:        "restartable-init",
 16231  					Ready:       true,
 16232  					State: core.ContainerState{
 16233  						Running: &core.ContainerStateRunning{
 16234  							StartedAt: metav1.NewTime(time.Now()),
 16235  						},
 16236  					},
 16237  				}},
 16238  				ContainerStatuses: []core.ContainerStatus{{
 16239  					ContainerID: "docker://numbers",
 16240  					Image:       "nginx:alpine",
 16241  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16242  					Name:        "nginx",
 16243  					Ready:       true,
 16244  					Started:     proto.Bool(true),
 16245  					State: core.ContainerState{
 16246  						Running: &core.ContainerStateRunning{
 16247  							StartedAt: metav1.NewTime(time.Now()),
 16248  						},
 16249  					},
 16250  				}},
 16251  			},
 16252  		},
 16253  		core.Pod{
 16254  			ObjectMeta: metav1.ObjectMeta{
 16255  				Name: "foo",
 16256  			},
 16257  			Spec: core.PodSpec{
 16258  				InitContainers: []core.Container{
 16259  					{
 16260  						Name:          "restartable-init",
 16261  						RestartPolicy: &containerRestartPolicyAlways,
 16262  					},
 16263  				},
 16264  				Containers: []core.Container{
 16265  					{
 16266  						Name: "nginx",
 16267  					},
 16268  				},
 16269  				RestartPolicy: core.RestartPolicyNever,
 16270  			},
 16271  			Status: core.PodStatus{
 16272  				InitContainerStatuses: []core.ContainerStatus{{
 16273  					ContainerID: "docker://numbers",
 16274  					Image:       "alpine",
 16275  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16276  					Name:        "restartable-init",
 16277  					Ready:       false,
 16278  					State: core.ContainerState{
 16279  						Terminated: &core.ContainerStateTerminated{
 16280  							ContainerID: "docker://numbers",
 16281  							Reason:      "Completed",
 16282  						},
 16283  					},
 16284  				}},
 16285  				ContainerStatuses: []core.ContainerStatus{{
 16286  					ContainerID: "docker://numbers",
 16287  					Image:       "nginx:alpine",
 16288  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16289  					Name:        "nginx",
 16290  					Ready:       true,
 16291  					Started:     proto.Bool(true),
 16292  					State: core.ContainerState{
 16293  						Running: &core.ContainerStateRunning{
 16294  							StartedAt: metav1.NewTime(time.Now()),
 16295  						},
 16296  					},
 16297  				}},
 16298  			},
 16299  		},
 16300  		"",
 16301  		"restartable init container can restart if RestartPolicyNever",
 16302  	}, {
 16303  		core.Pod{
 16304  			ObjectMeta: metav1.ObjectMeta{
 16305  				Name: "foo",
 16306  			},
 16307  			Spec: core.PodSpec{
 16308  				InitContainers: []core.Container{
 16309  					{
 16310  						Name:          "restartable-init",
 16311  						RestartPolicy: &containerRestartPolicyAlways,
 16312  					},
 16313  				},
 16314  				Containers: []core.Container{
 16315  					{
 16316  						Name: "nginx",
 16317  					},
 16318  				},
 16319  				RestartPolicy: core.RestartPolicyOnFailure,
 16320  			},
 16321  			Status: core.PodStatus{
 16322  				InitContainerStatuses: []core.ContainerStatus{{
 16323  					ContainerID: "docker://numbers",
 16324  					Image:       "alpine",
 16325  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16326  					Name:        "restartable-init",
 16327  					Ready:       true,
 16328  					State: core.ContainerState{
 16329  						Running: &core.ContainerStateRunning{
 16330  							StartedAt: metav1.NewTime(time.Now()),
 16331  						},
 16332  					},
 16333  				}},
 16334  				ContainerStatuses: []core.ContainerStatus{{
 16335  					ContainerID: "docker://numbers",
 16336  					Image:       "nginx:alpine",
 16337  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16338  					Name:        "nginx",
 16339  					Ready:       true,
 16340  					Started:     proto.Bool(true),
 16341  					State: core.ContainerState{
 16342  						Running: &core.ContainerStateRunning{
 16343  							StartedAt: metav1.NewTime(time.Now()),
 16344  						},
 16345  					},
 16346  				}},
 16347  			},
 16348  		},
 16349  		core.Pod{
 16350  			ObjectMeta: metav1.ObjectMeta{
 16351  				Name: "foo",
 16352  			},
 16353  			Spec: core.PodSpec{
 16354  				InitContainers: []core.Container{
 16355  					{
 16356  						Name:          "restartable-init",
 16357  						RestartPolicy: &containerRestartPolicyAlways,
 16358  					},
 16359  				},
 16360  				Containers: []core.Container{
 16361  					{
 16362  						Name: "nginx",
 16363  					},
 16364  				},
 16365  				RestartPolicy: core.RestartPolicyOnFailure,
 16366  			},
 16367  			Status: core.PodStatus{
 16368  				InitContainerStatuses: []core.ContainerStatus{{
 16369  					ContainerID: "docker://numbers",
 16370  					Image:       "alpine",
 16371  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16372  					Name:        "restartable-init",
 16373  					Ready:       false,
 16374  					State: core.ContainerState{
 16375  						Terminated: &core.ContainerStateTerminated{
 16376  							ContainerID: "docker://numbers",
 16377  							Reason:      "Completed",
 16378  						},
 16379  					},
 16380  				}},
 16381  				ContainerStatuses: []core.ContainerStatus{{
 16382  					ContainerID: "docker://numbers",
 16383  					Image:       "nginx:alpine",
 16384  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16385  					Name:        "nginx",
 16386  					Ready:       true,
 16387  					Started:     proto.Bool(true),
 16388  					State: core.ContainerState{
 16389  						Running: &core.ContainerStateRunning{
 16390  							StartedAt: metav1.NewTime(time.Now()),
 16391  						},
 16392  					},
 16393  				}},
 16394  			},
 16395  		},
 16396  		"",
 16397  		"restartable init container can restart if RestartPolicyOnFailure",
 16398  	}, {
 16399  		core.Pod{
 16400  			ObjectMeta: metav1.ObjectMeta{
 16401  				Name: "foo",
 16402  			},
 16403  			Spec: core.PodSpec{
 16404  				InitContainers: []core.Container{
 16405  					{
 16406  						Name:          "restartable-init",
 16407  						RestartPolicy: &containerRestartPolicyAlways,
 16408  					},
 16409  				},
 16410  				Containers: []core.Container{
 16411  					{
 16412  						Name: "nginx",
 16413  					},
 16414  				},
 16415  				RestartPolicy: core.RestartPolicyAlways,
 16416  			},
 16417  			Status: core.PodStatus{
 16418  				InitContainerStatuses: []core.ContainerStatus{{
 16419  					ContainerID: "docker://numbers",
 16420  					Image:       "alpine",
 16421  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16422  					Name:        "restartable-init",
 16423  					Ready:       true,
 16424  					State: core.ContainerState{
 16425  						Running: &core.ContainerStateRunning{
 16426  							StartedAt: metav1.NewTime(time.Now()),
 16427  						},
 16428  					},
 16429  				}},
 16430  				ContainerStatuses: []core.ContainerStatus{{
 16431  					ContainerID: "docker://numbers",
 16432  					Image:       "nginx:alpine",
 16433  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16434  					Name:        "nginx",
 16435  					Ready:       true,
 16436  					Started:     proto.Bool(true),
 16437  					State: core.ContainerState{
 16438  						Running: &core.ContainerStateRunning{
 16439  							StartedAt: metav1.NewTime(time.Now()),
 16440  						},
 16441  					},
 16442  				}},
 16443  			},
 16444  		},
 16445  		core.Pod{
 16446  			ObjectMeta: metav1.ObjectMeta{
 16447  				Name: "foo",
 16448  			},
 16449  			Spec: core.PodSpec{
 16450  				InitContainers: []core.Container{
 16451  					{
 16452  						Name:          "restartable-init",
 16453  						RestartPolicy: &containerRestartPolicyAlways,
 16454  					},
 16455  				},
 16456  				Containers: []core.Container{
 16457  					{
 16458  						Name: "nginx",
 16459  					},
 16460  				},
 16461  				RestartPolicy: core.RestartPolicyAlways,
 16462  			},
 16463  			Status: core.PodStatus{
 16464  				InitContainerStatuses: []core.ContainerStatus{{
 16465  					ContainerID: "docker://numbers",
 16466  					Image:       "alpine",
 16467  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16468  					Name:        "restartable-init",
 16469  					Ready:       false,
 16470  					State: core.ContainerState{
 16471  						Terminated: &core.ContainerStateTerminated{
 16472  							ContainerID: "docker://numbers",
 16473  							Reason:      "Completed",
 16474  						},
 16475  					},
 16476  				}},
 16477  				ContainerStatuses: []core.ContainerStatus{{
 16478  					ContainerID: "docker://numbers",
 16479  					Image:       "nginx:alpine",
 16480  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 16481  					Name:        "nginx",
 16482  					Ready:       true,
 16483  					Started:     proto.Bool(true),
 16484  					State: core.ContainerState{
 16485  						Running: &core.ContainerStateRunning{
 16486  							StartedAt: metav1.NewTime(time.Now()),
 16487  						},
 16488  					},
 16489  				}},
 16490  			},
 16491  		},
 16492  		"",
 16493  		"restartable init container can restart if RestartPolicyAlways",
 16494  	},
 16495  	}
 16496  
 16497  	for _, test := range tests {
 16498  		test.new.ObjectMeta.ResourceVersion = "1"
 16499  		test.old.ObjectMeta.ResourceVersion = "1"
 16500  		errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{})
 16501  		if test.err == "" {
 16502  			if len(errs) != 0 {
 16503  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 16504  			}
 16505  		} else {
 16506  			if len(errs) == 0 {
 16507  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 16508  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 16509  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 16510  			}
 16511  		}
 16512  	}
 16513  }
 16514  
 16515  func makeValidService() core.Service {
 16516  	clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster
 16517  	return core.Service{
 16518  		ObjectMeta: metav1.ObjectMeta{
 16519  			Name:            "valid",
 16520  			Namespace:       "valid",
 16521  			Labels:          map[string]string{},
 16522  			Annotations:     map[string]string{},
 16523  			ResourceVersion: "1",
 16524  		},
 16525  		Spec: core.ServiceSpec{
 16526  			Selector:              map[string]string{"key": "val"},
 16527  			SessionAffinity:       "None",
 16528  			Type:                  core.ServiceTypeClusterIP,
 16529  			Ports:                 []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt32(8675)}},
 16530  			InternalTrafficPolicy: &clusterInternalTrafficPolicy,
 16531  		},
 16532  	}
 16533  }
 16534  
 16535  func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
 16536  	makePod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 16537  		return &core.Pod{
 16538  			ObjectMeta: metav1.ObjectMeta{
 16539  				Annotations:     map[string]string{},
 16540  				Labels:          map[string]string{},
 16541  				Name:            "pod",
 16542  				Namespace:       "ns",
 16543  				ResourceVersion: "1",
 16544  			},
 16545  			Spec: core.PodSpec{
 16546  				Containers: []core.Container{{
 16547  					Name:                     "cnt",
 16548  					Image:                    "image",
 16549  					ImagePullPolicy:          "IfNotPresent",
 16550  					TerminationMessagePolicy: "File",
 16551  				}},
 16552  				DNSPolicy:           core.DNSClusterFirst,
 16553  				EphemeralContainers: ephemeralContainers,
 16554  				RestartPolicy:       core.RestartPolicyOnFailure,
 16555  			},
 16556  		}
 16557  	}
 16558  
 16559  	// Some tests use Windows host pods as an example of fields that might
 16560  	// conflict between an ephemeral container and the rest of the pod.
 16561  	capabilities.SetForTests(capabilities.Capabilities{
 16562  		AllowPrivileged: true,
 16563  	})
 16564  	makeWindowsHostPod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 16565  		return &core.Pod{
 16566  			ObjectMeta: metav1.ObjectMeta{
 16567  				Annotations:     map[string]string{},
 16568  				Labels:          map[string]string{},
 16569  				Name:            "pod",
 16570  				Namespace:       "ns",
 16571  				ResourceVersion: "1",
 16572  			},
 16573  			Spec: core.PodSpec{
 16574  				Containers: []core.Container{{
 16575  					Name:            "cnt",
 16576  					Image:           "image",
 16577  					ImagePullPolicy: "IfNotPresent",
 16578  					SecurityContext: &core.SecurityContext{
 16579  						WindowsOptions: &core.WindowsSecurityContextOptions{
 16580  							HostProcess: proto.Bool(true),
 16581  						},
 16582  					},
 16583  					TerminationMessagePolicy: "File",
 16584  				}},
 16585  				DNSPolicy:           core.DNSClusterFirst,
 16586  				EphemeralContainers: ephemeralContainers,
 16587  				RestartPolicy:       core.RestartPolicyOnFailure,
 16588  				SecurityContext: &core.PodSecurityContext{
 16589  					HostNetwork: true,
 16590  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16591  						HostProcess: proto.Bool(true),
 16592  					},
 16593  				},
 16594  			},
 16595  		}
 16596  	}
 16597  
 16598  	tests := []struct {
 16599  		name     string
 16600  		new, old *core.Pod
 16601  		err      string
 16602  	}{{
 16603  		"no ephemeral containers",
 16604  		makePod([]core.EphemeralContainer{}),
 16605  		makePod([]core.EphemeralContainer{}),
 16606  		"",
 16607  	}, {
 16608  		"No change in Ephemeral Containers",
 16609  		makePod([]core.EphemeralContainer{{
 16610  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16611  				Name:                     "debugger",
 16612  				Image:                    "busybox",
 16613  				ImagePullPolicy:          "IfNotPresent",
 16614  				TerminationMessagePolicy: "File",
 16615  			},
 16616  		}, {
 16617  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16618  				Name:                     "debugger2",
 16619  				Image:                    "busybox",
 16620  				ImagePullPolicy:          "IfNotPresent",
 16621  				TerminationMessagePolicy: "File",
 16622  			},
 16623  		}}),
 16624  		makePod([]core.EphemeralContainer{{
 16625  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16626  				Name:                     "debugger",
 16627  				Image:                    "busybox",
 16628  				ImagePullPolicy:          "IfNotPresent",
 16629  				TerminationMessagePolicy: "File",
 16630  			},
 16631  		}, {
 16632  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16633  				Name:                     "debugger2",
 16634  				Image:                    "busybox",
 16635  				ImagePullPolicy:          "IfNotPresent",
 16636  				TerminationMessagePolicy: "File",
 16637  			},
 16638  		}}),
 16639  		"",
 16640  	}, {
 16641  		"Ephemeral Container list order changes",
 16642  		makePod([]core.EphemeralContainer{{
 16643  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16644  				Name:                     "debugger",
 16645  				Image:                    "busybox",
 16646  				ImagePullPolicy:          "IfNotPresent",
 16647  				TerminationMessagePolicy: "File",
 16648  			},
 16649  		}, {
 16650  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16651  				Name:                     "debugger2",
 16652  				Image:                    "busybox",
 16653  				ImagePullPolicy:          "IfNotPresent",
 16654  				TerminationMessagePolicy: "File",
 16655  			},
 16656  		}}),
 16657  		makePod([]core.EphemeralContainer{{
 16658  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16659  				Name:                     "debugger2",
 16660  				Image:                    "busybox",
 16661  				ImagePullPolicy:          "IfNotPresent",
 16662  				TerminationMessagePolicy: "File",
 16663  			},
 16664  		}, {
 16665  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16666  				Name:                     "debugger",
 16667  				Image:                    "busybox",
 16668  				ImagePullPolicy:          "IfNotPresent",
 16669  				TerminationMessagePolicy: "File",
 16670  			},
 16671  		}}),
 16672  		"",
 16673  	}, {
 16674  		"Add an Ephemeral Container",
 16675  		makePod([]core.EphemeralContainer{{
 16676  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16677  				Name:                     "debugger",
 16678  				Image:                    "busybox",
 16679  				ImagePullPolicy:          "IfNotPresent",
 16680  				TerminationMessagePolicy: "File",
 16681  			},
 16682  		}}),
 16683  		makePod([]core.EphemeralContainer{}),
 16684  		"",
 16685  	}, {
 16686  		"Add two Ephemeral Containers",
 16687  		makePod([]core.EphemeralContainer{{
 16688  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16689  				Name:                     "debugger1",
 16690  				Image:                    "busybox",
 16691  				ImagePullPolicy:          "IfNotPresent",
 16692  				TerminationMessagePolicy: "File",
 16693  			},
 16694  		}, {
 16695  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16696  				Name:                     "debugger2",
 16697  				Image:                    "busybox",
 16698  				ImagePullPolicy:          "IfNotPresent",
 16699  				TerminationMessagePolicy: "File",
 16700  			},
 16701  		}}),
 16702  		makePod([]core.EphemeralContainer{}),
 16703  		"",
 16704  	}, {
 16705  		"Add to an existing Ephemeral Containers",
 16706  		makePod([]core.EphemeralContainer{{
 16707  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16708  				Name:                     "debugger",
 16709  				Image:                    "busybox",
 16710  				ImagePullPolicy:          "IfNotPresent",
 16711  				TerminationMessagePolicy: "File",
 16712  			},
 16713  		}, {
 16714  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16715  				Name:                     "debugger2",
 16716  				Image:                    "busybox",
 16717  				ImagePullPolicy:          "IfNotPresent",
 16718  				TerminationMessagePolicy: "File",
 16719  			},
 16720  		}}),
 16721  		makePod([]core.EphemeralContainer{{
 16722  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16723  				Name:                     "debugger",
 16724  				Image:                    "busybox",
 16725  				ImagePullPolicy:          "IfNotPresent",
 16726  				TerminationMessagePolicy: "File",
 16727  			},
 16728  		}}),
 16729  		"",
 16730  	}, {
 16731  		"Add to an existing Ephemeral Containers, list order changes",
 16732  		makePod([]core.EphemeralContainer{{
 16733  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16734  				Name:                     "debugger3",
 16735  				Image:                    "busybox",
 16736  				ImagePullPolicy:          "IfNotPresent",
 16737  				TerminationMessagePolicy: "File",
 16738  			},
 16739  		}, {
 16740  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16741  				Name:                     "debugger2",
 16742  				Image:                    "busybox",
 16743  				ImagePullPolicy:          "IfNotPresent",
 16744  				TerminationMessagePolicy: "File",
 16745  			},
 16746  		}, {
 16747  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16748  				Name:                     "debugger",
 16749  				Image:                    "busybox",
 16750  				ImagePullPolicy:          "IfNotPresent",
 16751  				TerminationMessagePolicy: "File",
 16752  			},
 16753  		}}),
 16754  		makePod([]core.EphemeralContainer{{
 16755  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16756  				Name:                     "debugger",
 16757  				Image:                    "busybox",
 16758  				ImagePullPolicy:          "IfNotPresent",
 16759  				TerminationMessagePolicy: "File",
 16760  			},
 16761  		}, {
 16762  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16763  				Name:                     "debugger2",
 16764  				Image:                    "busybox",
 16765  				ImagePullPolicy:          "IfNotPresent",
 16766  				TerminationMessagePolicy: "File",
 16767  			},
 16768  		}}),
 16769  		"",
 16770  	}, {
 16771  		"Remove an Ephemeral Container",
 16772  		makePod([]core.EphemeralContainer{}),
 16773  		makePod([]core.EphemeralContainer{{
 16774  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16775  				Name:                     "debugger",
 16776  				Image:                    "busybox",
 16777  				ImagePullPolicy:          "IfNotPresent",
 16778  				TerminationMessagePolicy: "File",
 16779  			},
 16780  		}}),
 16781  		"may not be removed",
 16782  	}, {
 16783  		"Replace an Ephemeral Container",
 16784  		makePod([]core.EphemeralContainer{{
 16785  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16786  				Name:                     "firstone",
 16787  				Image:                    "busybox",
 16788  				ImagePullPolicy:          "IfNotPresent",
 16789  				TerminationMessagePolicy: "File",
 16790  			},
 16791  		}}),
 16792  		makePod([]core.EphemeralContainer{{
 16793  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16794  				Name:                     "thentheother",
 16795  				Image:                    "busybox",
 16796  				ImagePullPolicy:          "IfNotPresent",
 16797  				TerminationMessagePolicy: "File",
 16798  			},
 16799  		}}),
 16800  		"may not be removed",
 16801  	}, {
 16802  		"Change an Ephemeral Containers",
 16803  		makePod([]core.EphemeralContainer{{
 16804  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16805  				Name:                     "debugger1",
 16806  				Image:                    "busybox",
 16807  				ImagePullPolicy:          "IfNotPresent",
 16808  				TerminationMessagePolicy: "File",
 16809  			},
 16810  		}, {
 16811  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16812  				Name:                     "debugger2",
 16813  				Image:                    "busybox",
 16814  				ImagePullPolicy:          "IfNotPresent",
 16815  				TerminationMessagePolicy: "File",
 16816  			},
 16817  		}}),
 16818  		makePod([]core.EphemeralContainer{{
 16819  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16820  				Name:                     "debugger1",
 16821  				Image:                    "debian",
 16822  				ImagePullPolicy:          "IfNotPresent",
 16823  				TerminationMessagePolicy: "File",
 16824  			},
 16825  		}, {
 16826  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16827  				Name:                     "debugger2",
 16828  				Image:                    "busybox",
 16829  				ImagePullPolicy:          "IfNotPresent",
 16830  				TerminationMessagePolicy: "File",
 16831  			},
 16832  		}}),
 16833  		"may not be changed",
 16834  	}, {
 16835  		"Ephemeral container with potential conflict with regular containers, but conflict not present",
 16836  		makeWindowsHostPod([]core.EphemeralContainer{{
 16837  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16838  				Name:            "debugger1",
 16839  				Image:           "image",
 16840  				ImagePullPolicy: "IfNotPresent",
 16841  				SecurityContext: &core.SecurityContext{
 16842  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16843  						HostProcess: proto.Bool(true),
 16844  					},
 16845  				},
 16846  				TerminationMessagePolicy: "File",
 16847  			},
 16848  		}}),
 16849  		makeWindowsHostPod(nil),
 16850  		"",
 16851  	}, {
 16852  		"Ephemeral container with potential conflict with regular containers, and conflict is present",
 16853  		makeWindowsHostPod([]core.EphemeralContainer{{
 16854  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 16855  				Name:            "debugger1",
 16856  				Image:           "image",
 16857  				ImagePullPolicy: "IfNotPresent",
 16858  				SecurityContext: &core.SecurityContext{
 16859  					WindowsOptions: &core.WindowsSecurityContextOptions{
 16860  						HostProcess: proto.Bool(false),
 16861  					},
 16862  				},
 16863  				TerminationMessagePolicy: "File",
 16864  			},
 16865  		}}),
 16866  		makeWindowsHostPod(nil),
 16867  		"spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical",
 16868  	}, {
 16869  		"Add ephemeral container to static pod",
 16870  		func() *core.Pod {
 16871  			p := makePod(nil)
 16872  			p.Spec.NodeName = "some-name"
 16873  			p.ObjectMeta.Annotations = map[string]string{
 16874  				core.MirrorPodAnnotationKey: "foo",
 16875  			}
 16876  			p.Spec.EphemeralContainers = []core.EphemeralContainer{{
 16877  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 16878  					Name:                     "debugger1",
 16879  					Image:                    "debian",
 16880  					ImagePullPolicy:          "IfNotPresent",
 16881  					TerminationMessagePolicy: "File",
 16882  				},
 16883  			}}
 16884  			return p
 16885  		}(),
 16886  		func() *core.Pod {
 16887  			p := makePod(nil)
 16888  			p.Spec.NodeName = "some-name"
 16889  			p.ObjectMeta.Annotations = map[string]string{
 16890  				core.MirrorPodAnnotationKey: "foo",
 16891  			}
 16892  			return p
 16893  		}(),
 16894  		"Forbidden: static pods do not support ephemeral containers",
 16895  	},
 16896  	}
 16897  
 16898  	for _, tc := range tests {
 16899  		errs := ValidatePodEphemeralContainersUpdate(tc.new, tc.old, PodValidationOptions{})
 16900  		if tc.err == "" {
 16901  			if len(errs) != 0 {
 16902  				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))
 16903  			}
 16904  		} else {
 16905  			if len(errs) == 0 {
 16906  				t.Errorf("unexpected valid for test: %s\nLocal diff of test objects (-old +new):\n%s", tc.name, cmp.Diff(tc.old, tc.new))
 16907  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, tc.err) {
 16908  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", tc.name, tc.err, actualErr)
 16909  			}
 16910  		}
 16911  	}
 16912  }
 16913  
 16914  func TestValidateServiceCreate(t *testing.T) {
 16915  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 16916  	singleStack := core.IPFamilyPolicySingleStack
 16917  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 16918  
 16919  	testCases := []struct {
 16920  		name         string
 16921  		tweakSvc     func(svc *core.Service) // given a basic valid service, each test case can customize it
 16922  		numErrs      int
 16923  		featureGates []featuregate.Feature
 16924  	}{{
 16925  		name: "missing namespace",
 16926  		tweakSvc: func(s *core.Service) {
 16927  			s.Namespace = ""
 16928  		},
 16929  		numErrs: 1,
 16930  	}, {
 16931  		name: "invalid namespace",
 16932  		tweakSvc: func(s *core.Service) {
 16933  			s.Namespace = "-123"
 16934  		},
 16935  		numErrs: 1,
 16936  	}, {
 16937  		name: "missing name",
 16938  		tweakSvc: func(s *core.Service) {
 16939  			s.Name = ""
 16940  		},
 16941  		numErrs: 1,
 16942  	}, {
 16943  		name: "invalid name",
 16944  		tweakSvc: func(s *core.Service) {
 16945  			s.Name = "-123"
 16946  		},
 16947  		numErrs: 1,
 16948  	}, {
 16949  		name: "too long name",
 16950  		tweakSvc: func(s *core.Service) {
 16951  			s.Name = strings.Repeat("a", 64)
 16952  		},
 16953  		numErrs: 1,
 16954  	}, {
 16955  		name: "invalid generateName",
 16956  		tweakSvc: func(s *core.Service) {
 16957  			s.GenerateName = "-123"
 16958  		},
 16959  		numErrs: 1,
 16960  	}, {
 16961  		name: "too long generateName",
 16962  		tweakSvc: func(s *core.Service) {
 16963  			s.GenerateName = strings.Repeat("a", 64)
 16964  		},
 16965  		numErrs: 1,
 16966  	}, {
 16967  		name: "invalid label",
 16968  		tweakSvc: func(s *core.Service) {
 16969  			s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar"
 16970  		},
 16971  		numErrs: 1,
 16972  	}, {
 16973  		name: "invalid annotation",
 16974  		tweakSvc: func(s *core.Service) {
 16975  			s.Annotations["NoSpecialCharsLike=Equals"] = "bar"
 16976  		},
 16977  		numErrs: 1,
 16978  	}, {
 16979  		name: "nil selector",
 16980  		tweakSvc: func(s *core.Service) {
 16981  			s.Spec.Selector = nil
 16982  		},
 16983  		numErrs: 0,
 16984  	}, {
 16985  		name: "invalid selector",
 16986  		tweakSvc: func(s *core.Service) {
 16987  			s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar"
 16988  		},
 16989  		numErrs: 1,
 16990  	}, {
 16991  		name: "missing session affinity",
 16992  		tweakSvc: func(s *core.Service) {
 16993  			s.Spec.SessionAffinity = ""
 16994  		},
 16995  		numErrs: 1,
 16996  	}, {
 16997  		name: "missing type",
 16998  		tweakSvc: func(s *core.Service) {
 16999  			s.Spec.Type = ""
 17000  		},
 17001  		numErrs: 1,
 17002  	}, {
 17003  		name: "missing ports",
 17004  		tweakSvc: func(s *core.Service) {
 17005  			s.Spec.Ports = nil
 17006  		},
 17007  		numErrs: 1,
 17008  	}, {
 17009  		name: "missing ports but headless",
 17010  		tweakSvc: func(s *core.Service) {
 17011  			s.Spec.Ports = nil
 17012  			s.Spec.ClusterIP = core.ClusterIPNone
 17013  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17014  		},
 17015  		numErrs: 0,
 17016  	}, {
 17017  		name: "empty port[0] name",
 17018  		tweakSvc: func(s *core.Service) {
 17019  			s.Spec.Ports[0].Name = ""
 17020  		},
 17021  		numErrs: 0,
 17022  	}, {
 17023  		name: "empty port[1] name",
 17024  		tweakSvc: func(s *core.Service) {
 17025  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 17026  		},
 17027  		numErrs: 1,
 17028  	}, {
 17029  		name: "empty multi-port port[0] name",
 17030  		tweakSvc: func(s *core.Service) {
 17031  			s.Spec.Ports[0].Name = ""
 17032  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 17033  		},
 17034  		numErrs: 1,
 17035  	}, {
 17036  		name: "invalid port name",
 17037  		tweakSvc: func(s *core.Service) {
 17038  			s.Spec.Ports[0].Name = "INVALID"
 17039  		},
 17040  		numErrs: 1,
 17041  	}, {
 17042  		name: "missing protocol",
 17043  		tweakSvc: func(s *core.Service) {
 17044  			s.Spec.Ports[0].Protocol = ""
 17045  		},
 17046  		numErrs: 1,
 17047  	}, {
 17048  		name: "invalid protocol",
 17049  		tweakSvc: func(s *core.Service) {
 17050  			s.Spec.Ports[0].Protocol = "INVALID"
 17051  		},
 17052  		numErrs: 1,
 17053  	}, {
 17054  		name: "invalid cluster ip",
 17055  		tweakSvc: func(s *core.Service) {
 17056  			s.Spec.ClusterIP = "invalid"
 17057  			s.Spec.ClusterIPs = []string{"invalid"}
 17058  		},
 17059  		numErrs: 1,
 17060  	}, {
 17061  		name: "missing port",
 17062  		tweakSvc: func(s *core.Service) {
 17063  			s.Spec.Ports[0].Port = 0
 17064  		},
 17065  		numErrs: 1,
 17066  	}, {
 17067  		name: "invalid port",
 17068  		tweakSvc: func(s *core.Service) {
 17069  			s.Spec.Ports[0].Port = 65536
 17070  		},
 17071  		numErrs: 1,
 17072  	}, {
 17073  		name: "invalid TargetPort int",
 17074  		tweakSvc: func(s *core.Service) {
 17075  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536)
 17076  		},
 17077  		numErrs: 1,
 17078  	}, {
 17079  		name: "valid port headless",
 17080  		tweakSvc: func(s *core.Service) {
 17081  			s.Spec.Ports[0].Port = 11722
 17082  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722)
 17083  			s.Spec.ClusterIP = core.ClusterIPNone
 17084  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17085  		},
 17086  		numErrs: 0,
 17087  	}, {
 17088  		name: "invalid port headless 1",
 17089  		tweakSvc: func(s *core.Service) {
 17090  			s.Spec.Ports[0].Port = 11722
 17091  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721)
 17092  			s.Spec.ClusterIP = core.ClusterIPNone
 17093  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17094  		},
 17095  		// in the v1 API, targetPorts on headless services were tolerated.
 17096  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 17097  		// numErrs: 1,
 17098  		numErrs: 0,
 17099  	}, {
 17100  		name: "invalid port headless 2",
 17101  		tweakSvc: func(s *core.Service) {
 17102  			s.Spec.Ports[0].Port = 11722
 17103  			s.Spec.Ports[0].TargetPort = intstr.FromString("target")
 17104  			s.Spec.ClusterIP = core.ClusterIPNone
 17105  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17106  		},
 17107  		// in the v1 API, targetPorts on headless services were tolerated.
 17108  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 17109  		// numErrs: 1,
 17110  		numErrs: 0,
 17111  	}, {
 17112  		name: "invalid publicIPs localhost",
 17113  		tweakSvc: func(s *core.Service) {
 17114  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17115  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 17116  		},
 17117  		numErrs: 1,
 17118  	}, {
 17119  		name: "invalid publicIPs unspecified",
 17120  		tweakSvc: func(s *core.Service) {
 17121  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17122  			s.Spec.ExternalIPs = []string{"0.0.0.0"}
 17123  		},
 17124  		numErrs: 1,
 17125  	}, {
 17126  		name: "invalid publicIPs loopback",
 17127  		tweakSvc: func(s *core.Service) {
 17128  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17129  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 17130  		},
 17131  		numErrs: 1,
 17132  	}, {
 17133  		name: "invalid publicIPs host",
 17134  		tweakSvc: func(s *core.Service) {
 17135  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17136  			s.Spec.ExternalIPs = []string{"myhost.mydomain"}
 17137  		},
 17138  		numErrs: 1,
 17139  	}, {
 17140  		name: "valid publicIPs",
 17141  		tweakSvc: func(s *core.Service) {
 17142  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17143  			s.Spec.ExternalIPs = []string{"1.2.3.4"}
 17144  		},
 17145  		numErrs: 0,
 17146  	}, {
 17147  		name: "dup port name",
 17148  		tweakSvc: func(s *core.Service) {
 17149  			s.Spec.Ports[0].Name = "p"
 17150  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17151  		},
 17152  		numErrs: 1,
 17153  	}, {
 17154  		name: "valid load balancer protocol UDP 1",
 17155  		tweakSvc: func(s *core.Service) {
 17156  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17157  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17158  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17159  			s.Spec.Ports[0].Protocol = "UDP"
 17160  		},
 17161  		numErrs: 0,
 17162  	}, {
 17163  		name: "valid load balancer protocol UDP 2",
 17164  		tweakSvc: func(s *core.Service) {
 17165  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17166  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17167  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17168  			s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}
 17169  		},
 17170  		numErrs: 0,
 17171  	}, {
 17172  		name: "load balancer with mix protocol",
 17173  		tweakSvc: func(s *core.Service) {
 17174  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17175  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17176  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17177  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)})
 17178  		},
 17179  		numErrs: 0,
 17180  	}, {
 17181  		name: "valid 1",
 17182  		tweakSvc: func(s *core.Service) {
 17183  			// do nothing
 17184  		},
 17185  		numErrs: 0,
 17186  	}, {
 17187  		name: "valid 2",
 17188  		tweakSvc: func(s *core.Service) {
 17189  			s.Spec.Ports[0].Protocol = "UDP"
 17190  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345)
 17191  		},
 17192  		numErrs: 0,
 17193  	}, {
 17194  		name: "valid 3",
 17195  		tweakSvc: func(s *core.Service) {
 17196  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 17197  		},
 17198  		numErrs: 0,
 17199  	}, {
 17200  		name: "valid cluster ip - none ",
 17201  		tweakSvc: func(s *core.Service) {
 17202  			s.Spec.ClusterIP = core.ClusterIPNone
 17203  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 17204  		},
 17205  		numErrs: 0,
 17206  	}, {
 17207  		name: "valid cluster ip - empty",
 17208  		tweakSvc: func(s *core.Service) {
 17209  			s.Spec.ClusterIPs = nil
 17210  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 17211  		},
 17212  		numErrs: 0,
 17213  	}, {
 17214  		name: "valid type - clusterIP",
 17215  		tweakSvc: func(s *core.Service) {
 17216  			s.Spec.Type = core.ServiceTypeClusterIP
 17217  		},
 17218  		numErrs: 0,
 17219  	}, {
 17220  		name: "valid type - loadbalancer",
 17221  		tweakSvc: func(s *core.Service) {
 17222  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17223  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17224  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17225  		},
 17226  		numErrs: 0,
 17227  	}, {
 17228  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false",
 17229  		tweakSvc: func(s *core.Service) {
 17230  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17231  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17232  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 17233  		},
 17234  		numErrs: 0,
 17235  	}, {
 17236  		name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type",
 17237  		tweakSvc: func(s *core.Service) {
 17238  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17239  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17240  		},
 17241  		numErrs: 1,
 17242  	}, {
 17243  		name: "valid type loadbalancer 2 ports",
 17244  		tweakSvc: func(s *core.Service) {
 17245  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17246  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17247  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17248  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17249  		},
 17250  		numErrs: 0,
 17251  	}, {
 17252  		name: "valid external load balancer 2 ports",
 17253  		tweakSvc: func(s *core.Service) {
 17254  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17255  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17256  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17257  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17258  		},
 17259  		numErrs: 0,
 17260  	}, {
 17261  		name: "duplicate nodeports",
 17262  		tweakSvc: func(s *core.Service) {
 17263  			s.Spec.Type = core.ServiceTypeNodePort
 17264  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17265  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17266  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17267  		},
 17268  		numErrs: 1,
 17269  	}, {
 17270  		name: "duplicate nodeports (different protocols)",
 17271  		tweakSvc: func(s *core.Service) {
 17272  			s.Spec.Type = core.ServiceTypeNodePort
 17273  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17274  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17275  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17276  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)})
 17277  		},
 17278  		numErrs: 0,
 17279  	}, {
 17280  		name: "invalid duplicate ports (with same protocol)",
 17281  		tweakSvc: func(s *core.Service) {
 17282  			s.Spec.Type = core.ServiceTypeClusterIP
 17283  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 17284  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)})
 17285  		},
 17286  		numErrs: 1,
 17287  	}, {
 17288  		name: "valid duplicate ports (with different protocols)",
 17289  		tweakSvc: func(s *core.Service) {
 17290  			s.Spec.Type = core.ServiceTypeClusterIP
 17291  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 17292  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)})
 17293  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)})
 17294  		},
 17295  		numErrs: 0,
 17296  	}, {
 17297  		name: "valid type - cluster",
 17298  		tweakSvc: func(s *core.Service) {
 17299  			s.Spec.Type = core.ServiceTypeClusterIP
 17300  		},
 17301  		numErrs: 0,
 17302  	}, {
 17303  		name: "valid type - nodeport",
 17304  		tweakSvc: func(s *core.Service) {
 17305  			s.Spec.Type = core.ServiceTypeNodePort
 17306  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17307  		},
 17308  		numErrs: 0,
 17309  	}, {
 17310  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true",
 17311  		tweakSvc: func(s *core.Service) {
 17312  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17313  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17314  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17315  		},
 17316  		numErrs: 0,
 17317  	}, {
 17318  		name: "valid type loadbalancer 2 ports",
 17319  		tweakSvc: func(s *core.Service) {
 17320  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17321  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17322  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17323  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17324  		},
 17325  		numErrs: 0,
 17326  	}, {
 17327  		name: "valid type loadbalancer with NodePort",
 17328  		tweakSvc: func(s *core.Service) {
 17329  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17330  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17331  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17332  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17333  		},
 17334  		numErrs: 0,
 17335  	}, {
 17336  		name: "valid type=NodePort service with NodePort",
 17337  		tweakSvc: func(s *core.Service) {
 17338  			s.Spec.Type = core.ServiceTypeNodePort
 17339  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17340  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17341  		},
 17342  		numErrs: 0,
 17343  	}, {
 17344  		name: "valid type=NodePort service without NodePort",
 17345  		tweakSvc: func(s *core.Service) {
 17346  			s.Spec.Type = core.ServiceTypeNodePort
 17347  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17348  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17349  		},
 17350  		numErrs: 0,
 17351  	}, {
 17352  		name: "valid cluster service without NodePort",
 17353  		tweakSvc: func(s *core.Service) {
 17354  			s.Spec.Type = core.ServiceTypeClusterIP
 17355  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17356  		},
 17357  		numErrs: 0,
 17358  	}, {
 17359  		name: "invalid cluster service with NodePort",
 17360  		tweakSvc: func(s *core.Service) {
 17361  			s.Spec.Type = core.ServiceTypeClusterIP
 17362  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 17363  		},
 17364  		numErrs: 1,
 17365  	}, {
 17366  		name: "invalid public service with duplicate NodePort",
 17367  		tweakSvc: func(s *core.Service) {
 17368  			s.Spec.Type = core.ServiceTypeNodePort
 17369  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17370  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17371  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 17372  		},
 17373  		numErrs: 1,
 17374  	}, {
 17375  		name: "valid type=LoadBalancer",
 17376  		tweakSvc: func(s *core.Service) {
 17377  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17378  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17379  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17380  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17381  		},
 17382  		numErrs: 0,
 17383  	}, {
 17384  		// For now we open firewalls, and its insecure if we open 10250, remove this
 17385  		// when we have better protections in place.
 17386  		name: "invalid port type=LoadBalancer",
 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.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 17392  		},
 17393  		numErrs: 1,
 17394  	}, {
 17395  		name: "valid LoadBalancer source range annotation",
 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.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24,  5.6.0.0/16"
 17401  		},
 17402  		numErrs: 0,
 17403  	}, {
 17404  		name: "valid empty LoadBalancer source range annotation",
 17405  		tweakSvc: func(s *core.Service) {
 17406  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17407  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17408  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17409  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
 17410  		},
 17411  		numErrs: 0,
 17412  	}, {
 17413  		name: "valid whitespace-only LoadBalancer source range annotation",
 17414  		tweakSvc: func(s *core.Service) {
 17415  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17416  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17417  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17418  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "  "
 17419  		},
 17420  		numErrs: 0,
 17421  	}, {
 17422  		name: "invalid LoadBalancer source range annotation (hostname)",
 17423  		tweakSvc: func(s *core.Service) {
 17424  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17425  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17426  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17427  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
 17428  		},
 17429  		numErrs: 1,
 17430  	}, {
 17431  		name: "invalid LoadBalancer source range annotation (invalid CIDR)",
 17432  		tweakSvc: func(s *core.Service) {
 17433  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17434  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17435  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17436  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33"
 17437  		},
 17438  		numErrs: 1,
 17439  	}, {
 17440  		name: "invalid LoadBalancer source range annotation for non LoadBalancer type service",
 17441  		tweakSvc: func(s *core.Service) {
 17442  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.0/24"
 17443  		},
 17444  		numErrs: 1,
 17445  	}, {
 17446  		name: "invalid empty-but-set LoadBalancer source range annotation for non LoadBalancer type service",
 17447  		tweakSvc: func(s *core.Service) {
 17448  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
 17449  		},
 17450  		numErrs: 1,
 17451  	}, {
 17452  		name: "valid LoadBalancer source range",
 17453  		tweakSvc: func(s *core.Service) {
 17454  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17455  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17456  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17457  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17458  		},
 17459  		numErrs: 0,
 17460  	}, {
 17461  		name: "valid LoadBalancer source range with whitespace",
 17462  		tweakSvc: func(s *core.Service) {
 17463  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17464  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17465  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17466  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24  ", " 5.6.0.0/16"}
 17467  		},
 17468  		numErrs: 0,
 17469  	}, {
 17470  		name: "invalid empty LoadBalancer source range",
 17471  		tweakSvc: func(s *core.Service) {
 17472  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17473  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17474  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17475  			s.Spec.LoadBalancerSourceRanges = []string{"   "}
 17476  		},
 17477  		numErrs: 1,
 17478  	}, {
 17479  		name: "invalid LoadBalancer source range (hostname)",
 17480  		tweakSvc: func(s *core.Service) {
 17481  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17482  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17483  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17484  			s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"}
 17485  		},
 17486  		numErrs: 1,
 17487  	}, {
 17488  		name: "invalid LoadBalancer source range (invalid CIDR)",
 17489  		tweakSvc: func(s *core.Service) {
 17490  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17491  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17492  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17493  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/33"}
 17494  		},
 17495  		numErrs: 1,
 17496  	}, {
 17497  		name: "invalid source range for non LoadBalancer type service",
 17498  		tweakSvc: func(s *core.Service) {
 17499  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17500  		},
 17501  		numErrs: 1,
 17502  	}, {
 17503  		name: "invalid source range annotation ignored with valid source range field",
 17504  		tweakSvc: func(s *core.Service) {
 17505  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17506  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17507  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17508  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
 17509  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.0/24", "5.6.0.0/16"}
 17510  		},
 17511  		numErrs: 0,
 17512  	}, {
 17513  		name: "valid ExternalName",
 17514  		tweakSvc: func(s *core.Service) {
 17515  			s.Spec.Type = core.ServiceTypeExternalName
 17516  			s.Spec.ExternalName = "foo.bar.example.com"
 17517  		},
 17518  		numErrs: 0,
 17519  	}, {
 17520  		name: "valid ExternalName (trailing dot)",
 17521  		tweakSvc: func(s *core.Service) {
 17522  			s.Spec.Type = core.ServiceTypeExternalName
 17523  			s.Spec.ExternalName = "foo.bar.example.com."
 17524  		},
 17525  		numErrs: 0,
 17526  	}, {
 17527  		name: "invalid ExternalName clusterIP (valid IP)",
 17528  		tweakSvc: func(s *core.Service) {
 17529  			s.Spec.Type = core.ServiceTypeExternalName
 17530  			s.Spec.ClusterIP = "1.2.3.4"
 17531  			s.Spec.ClusterIPs = []string{"1.2.3.4"}
 17532  			s.Spec.ExternalName = "foo.bar.example.com"
 17533  		},
 17534  		numErrs: 1,
 17535  	}, {
 17536  		name: "invalid ExternalName clusterIP (None)",
 17537  		tweakSvc: func(s *core.Service) {
 17538  			s.Spec.Type = core.ServiceTypeExternalName
 17539  			s.Spec.ClusterIP = "None"
 17540  			s.Spec.ClusterIPs = []string{"None"}
 17541  			s.Spec.ExternalName = "foo.bar.example.com"
 17542  		},
 17543  		numErrs: 1,
 17544  	}, {
 17545  		name: "invalid ExternalName (not a DNS name)",
 17546  		tweakSvc: func(s *core.Service) {
 17547  			s.Spec.Type = core.ServiceTypeExternalName
 17548  			s.Spec.ExternalName = "-123"
 17549  		},
 17550  		numErrs: 1,
 17551  	}, {
 17552  		name: "LoadBalancer type cannot have None ClusterIP",
 17553  		tweakSvc: func(s *core.Service) {
 17554  			s.Spec.ClusterIP = "None"
 17555  			s.Spec.ClusterIPs = []string{"None"}
 17556  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17557  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17558  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17559  		},
 17560  		numErrs: 1,
 17561  	}, {
 17562  		name: "invalid node port with clusterIP None",
 17563  		tweakSvc: func(s *core.Service) {
 17564  			s.Spec.Type = core.ServiceTypeNodePort
 17565  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17566  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 17567  			s.Spec.ClusterIP = "None"
 17568  			s.Spec.ClusterIPs = []string{"None"}
 17569  		},
 17570  		numErrs: 1,
 17571  	},
 17572  		// ESIPP section begins.
 17573  		{
 17574  			name: "invalid externalTraffic field",
 17575  			tweakSvc: func(s *core.Service) {
 17576  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17577  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17578  				s.Spec.ExternalTrafficPolicy = "invalid"
 17579  			},
 17580  			numErrs: 1,
 17581  		}, {
 17582  			name: "nil internalTraffic field when feature gate is on",
 17583  			tweakSvc: func(s *core.Service) {
 17584  				s.Spec.InternalTrafficPolicy = nil
 17585  			},
 17586  			numErrs: 1,
 17587  		}, {
 17588  			name: "internalTrafficPolicy field nil when type is ExternalName",
 17589  			tweakSvc: func(s *core.Service) {
 17590  				s.Spec.InternalTrafficPolicy = nil
 17591  				s.Spec.Type = core.ServiceTypeExternalName
 17592  				s.Spec.ExternalName = "foo.bar.com"
 17593  			},
 17594  			numErrs: 0,
 17595  		}, {
 17596  			// Typically this should fail validation, but in v1.22 we have existing clusters
 17597  			// that may have allowed internalTrafficPolicy when Type=ExternalName.
 17598  			// This test case ensures we don't break compatibility for internalTrafficPolicy
 17599  			// when Type=ExternalName
 17600  			name: "internalTrafficPolicy field is set when type is ExternalName",
 17601  			tweakSvc: func(s *core.Service) {
 17602  				cluster := core.ServiceInternalTrafficPolicyCluster
 17603  				s.Spec.InternalTrafficPolicy = &cluster
 17604  				s.Spec.Type = core.ServiceTypeExternalName
 17605  				s.Spec.ExternalName = "foo.bar.com"
 17606  			},
 17607  			numErrs: 0,
 17608  		}, {
 17609  			name: "invalid internalTraffic field",
 17610  			tweakSvc: func(s *core.Service) {
 17611  				invalid := core.ServiceInternalTrafficPolicy("invalid")
 17612  				s.Spec.InternalTrafficPolicy = &invalid
 17613  			},
 17614  			numErrs: 1,
 17615  		}, {
 17616  			name: "internalTrafficPolicy field set to Cluster",
 17617  			tweakSvc: func(s *core.Service) {
 17618  				cluster := core.ServiceInternalTrafficPolicyCluster
 17619  				s.Spec.InternalTrafficPolicy = &cluster
 17620  			},
 17621  			numErrs: 0,
 17622  		}, {
 17623  			name: "internalTrafficPolicy field set to Local",
 17624  			tweakSvc: func(s *core.Service) {
 17625  				local := core.ServiceInternalTrafficPolicyLocal
 17626  				s.Spec.InternalTrafficPolicy = &local
 17627  			},
 17628  			numErrs: 0,
 17629  		}, {
 17630  			name: "negative healthCheckNodePort field",
 17631  			tweakSvc: func(s *core.Service) {
 17632  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17633  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17634  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17635  				s.Spec.HealthCheckNodePort = -1
 17636  			},
 17637  			numErrs: 1,
 17638  		}, {
 17639  			name: "negative healthCheckNodePort field",
 17640  			tweakSvc: func(s *core.Service) {
 17641  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17642  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17643  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17644  				s.Spec.HealthCheckNodePort = 31100
 17645  			},
 17646  			numErrs: 0,
 17647  		},
 17648  		// ESIPP section ends.
 17649  		{
 17650  			name: "invalid timeoutSeconds field",
 17651  			tweakSvc: func(s *core.Service) {
 17652  				s.Spec.Type = core.ServiceTypeClusterIP
 17653  				s.Spec.SessionAffinity = core.ServiceAffinityClientIP
 17654  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 17655  					ClientIP: &core.ClientIPConfig{
 17656  						TimeoutSeconds: utilpointer.Int32(-1),
 17657  					},
 17658  				}
 17659  			},
 17660  			numErrs: 1,
 17661  		}, {
 17662  			name: "sessionAffinityConfig can't be set when session affinity is None",
 17663  			tweakSvc: func(s *core.Service) {
 17664  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17665  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17666  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17667  				s.Spec.SessionAffinity = core.ServiceAffinityNone
 17668  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 17669  					ClientIP: &core.ClientIPConfig{
 17670  						TimeoutSeconds: utilpointer.Int32(90),
 17671  					},
 17672  				}
 17673  			},
 17674  			numErrs: 1,
 17675  		},
 17676  		/* ip families validation */
 17677  		{
 17678  			name: "invalid, service with invalid ipFamilies",
 17679  			tweakSvc: func(s *core.Service) {
 17680  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 17681  				s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily}
 17682  			},
 17683  			numErrs: 1,
 17684  		}, {
 17685  			name: "invalid, service with invalid ipFamilies (2nd)",
 17686  			tweakSvc: func(s *core.Service) {
 17687  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 17688  				s.Spec.IPFamilyPolicy = &requireDualStack
 17689  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily}
 17690  			},
 17691  			numErrs: 1,
 17692  		}, {
 17693  			name: "IPFamilyPolicy(singleStack) is set for two families",
 17694  			tweakSvc: func(s *core.Service) {
 17695  				s.Spec.IPFamilyPolicy = &singleStack
 17696  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17697  			},
 17698  			numErrs: 0, // this validated in alloc code.
 17699  		}, {
 17700  			name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)",
 17701  			tweakSvc: func(s *core.Service) {
 17702  				s.Spec.IPFamilyPolicy = &preferDualStack
 17703  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17704  			},
 17705  			numErrs: 0,
 17706  		},
 17707  
 17708  		{
 17709  			name: "invalid, service with 2+ ipFamilies",
 17710  			tweakSvc: func(s *core.Service) {
 17711  				s.Spec.IPFamilyPolicy = &requireDualStack
 17712  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol}
 17713  			},
 17714  			numErrs: 1,
 17715  		}, {
 17716  			name: "invalid, service with same ip families",
 17717  			tweakSvc: func(s *core.Service) {
 17718  				s.Spec.IPFamilyPolicy = &requireDualStack
 17719  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol}
 17720  			},
 17721  			numErrs: 1,
 17722  		}, {
 17723  			name: "valid, nil service ipFamilies",
 17724  			tweakSvc: func(s *core.Service) {
 17725  				s.Spec.IPFamilies = nil
 17726  			},
 17727  			numErrs: 0,
 17728  		}, {
 17729  			name: "valid, service with valid ipFamilies (v4)",
 17730  			tweakSvc: func(s *core.Service) {
 17731  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17732  			},
 17733  			numErrs: 0,
 17734  		}, {
 17735  			name: "valid, service with valid ipFamilies (v6)",
 17736  			tweakSvc: func(s *core.Service) {
 17737  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17738  			},
 17739  			numErrs: 0,
 17740  		}, {
 17741  			name: "valid, service with valid ipFamilies(v4,v6)",
 17742  			tweakSvc: func(s *core.Service) {
 17743  				s.Spec.IPFamilyPolicy = &requireDualStack
 17744  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17745  			},
 17746  			numErrs: 0,
 17747  		}, {
 17748  			name: "valid, service with valid ipFamilies(v6,v4)",
 17749  			tweakSvc: func(s *core.Service) {
 17750  				s.Spec.IPFamilyPolicy = &requireDualStack
 17751  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17752  			},
 17753  			numErrs: 0,
 17754  		}, {
 17755  			name: "valid, service preferred dual stack with single family",
 17756  			tweakSvc: func(s *core.Service) {
 17757  				s.Spec.IPFamilyPolicy = &preferDualStack
 17758  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17759  			},
 17760  			numErrs: 0,
 17761  		},
 17762  		/* cluster IPs. some tests are redundant */
 17763  		{
 17764  			name: "invalid, garbage single ip",
 17765  			tweakSvc: func(s *core.Service) {
 17766  				s.Spec.ClusterIP = "garbage-ip"
 17767  				s.Spec.ClusterIPs = []string{"garbage-ip"}
 17768  			},
 17769  			numErrs: 1,
 17770  		}, {
 17771  			name: "invalid, garbage ips",
 17772  			tweakSvc: func(s *core.Service) {
 17773  				s.Spec.IPFamilyPolicy = &requireDualStack
 17774  				s.Spec.ClusterIP = "garbage-ip"
 17775  				s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"}
 17776  			},
 17777  			numErrs: 2,
 17778  		}, {
 17779  			name: "invalid, garbage first ip",
 17780  			tweakSvc: func(s *core.Service) {
 17781  				s.Spec.IPFamilyPolicy = &requireDualStack
 17782  				s.Spec.ClusterIP = "garbage-ip"
 17783  				s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"}
 17784  			},
 17785  			numErrs: 1,
 17786  		}, {
 17787  			name: "invalid, garbage second ip",
 17788  			tweakSvc: func(s *core.Service) {
 17789  				s.Spec.IPFamilyPolicy = &requireDualStack
 17790  				s.Spec.ClusterIP = "2001::1"
 17791  				s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"}
 17792  			},
 17793  			numErrs: 1,
 17794  		}, {
 17795  			name: "invalid, NONE + IP",
 17796  			tweakSvc: func(s *core.Service) {
 17797  				s.Spec.IPFamilyPolicy = &requireDualStack
 17798  				s.Spec.ClusterIP = "None"
 17799  				s.Spec.ClusterIPs = []string{"None", "2001::1"}
 17800  			},
 17801  			numErrs: 1,
 17802  		}, {
 17803  			name: "invalid, IP + NONE",
 17804  			tweakSvc: func(s *core.Service) {
 17805  				s.Spec.IPFamilyPolicy = &requireDualStack
 17806  				s.Spec.ClusterIP = "2001::1"
 17807  				s.Spec.ClusterIPs = []string{"2001::1", "None"}
 17808  			},
 17809  			numErrs: 1,
 17810  		}, {
 17811  			name: "invalid, EMPTY STRING + IP",
 17812  			tweakSvc: func(s *core.Service) {
 17813  				s.Spec.IPFamilyPolicy = &requireDualStack
 17814  				s.Spec.ClusterIP = ""
 17815  				s.Spec.ClusterIPs = []string{"", "2001::1"}
 17816  			},
 17817  			numErrs: 2,
 17818  		}, {
 17819  			name: "invalid, IP + EMPTY STRING",
 17820  			tweakSvc: func(s *core.Service) {
 17821  				s.Spec.IPFamilyPolicy = &requireDualStack
 17822  				s.Spec.ClusterIP = "2001::1"
 17823  				s.Spec.ClusterIPs = []string{"2001::1", ""}
 17824  			},
 17825  			numErrs: 1,
 17826  		}, {
 17827  			name: "invalid, same ip family (v6)",
 17828  			tweakSvc: func(s *core.Service) {
 17829  				s.Spec.IPFamilyPolicy = &requireDualStack
 17830  				s.Spec.ClusterIP = "2001::1"
 17831  				s.Spec.ClusterIPs = []string{"2001::1", "2001::4"}
 17832  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17833  			},
 17834  			numErrs: 2,
 17835  		}, {
 17836  			name: "invalid, same ip family (v4)",
 17837  			tweakSvc: func(s *core.Service) {
 17838  				s.Spec.IPFamilyPolicy = &requireDualStack
 17839  				s.Spec.ClusterIP = "10.0.0.1"
 17840  				s.Spec.ClusterIPs = []string{"10.0.0.1", "10.0.0.10"}
 17841  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17842  
 17843  			},
 17844  			numErrs: 2,
 17845  		}, {
 17846  			name: "invalid, more than two ips",
 17847  			tweakSvc: func(s *core.Service) {
 17848  				s.Spec.IPFamilyPolicy = &requireDualStack
 17849  				s.Spec.ClusterIP = "10.0.0.1"
 17850  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1", "10.0.0.10"}
 17851  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17852  			},
 17853  			numErrs: 1,
 17854  		}, {
 17855  			name: " multi ip, dualstack not set (request for downgrade)",
 17856  			tweakSvc: func(s *core.Service) {
 17857  				s.Spec.IPFamilyPolicy = &singleStack
 17858  				s.Spec.ClusterIP = "10.0.0.1"
 17859  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17860  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17861  			},
 17862  			numErrs: 0,
 17863  		}, {
 17864  			name: "valid, headless-no-selector + multi family + gate off",
 17865  			tweakSvc: func(s *core.Service) {
 17866  				s.Spec.IPFamilyPolicy = &requireDualStack
 17867  				s.Spec.ClusterIP = "None"
 17868  				s.Spec.ClusterIPs = []string{"None"}
 17869  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17870  				s.Spec.Selector = nil
 17871  			},
 17872  			numErrs: 0,
 17873  		}, {
 17874  			name: "valid, multi ip, single ipfamilies preferDualStack",
 17875  			tweakSvc: func(s *core.Service) {
 17876  				s.Spec.IPFamilyPolicy = &preferDualStack
 17877  				s.Spec.ClusterIP = "10.0.0.1"
 17878  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17879  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17880  			},
 17881  			numErrs: 0,
 17882  		},
 17883  
 17884  		{
 17885  			name: "valid, multi ip, single ipfamilies (must match when provided) + requireDualStack",
 17886  			tweakSvc: func(s *core.Service) {
 17887  				s.Spec.IPFamilyPolicy = &requireDualStack
 17888  				s.Spec.ClusterIP = "10.0.0.1"
 17889  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17890  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17891  			},
 17892  			numErrs: 0,
 17893  		}, {
 17894  			name: "invalid, families don't match (v4=>v6)",
 17895  			tweakSvc: func(s *core.Service) {
 17896  				s.Spec.ClusterIP = "10.0.0.1"
 17897  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17898  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17899  			},
 17900  			numErrs: 1,
 17901  		}, {
 17902  			name: "invalid, families don't match (v6=>v4)",
 17903  			tweakSvc: func(s *core.Service) {
 17904  				s.Spec.ClusterIP = "2001::1"
 17905  				s.Spec.ClusterIPs = []string{"2001::1"}
 17906  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 17907  			},
 17908  			numErrs: 1,
 17909  		}, {
 17910  			name: "valid. no field set",
 17911  			tweakSvc: func(s *core.Service) {
 17912  			},
 17913  			numErrs: 0,
 17914  		},
 17915  
 17916  		{
 17917  			name: "valid, single ip",
 17918  			tweakSvc: func(s *core.Service) {
 17919  				s.Spec.IPFamilyPolicy = &singleStack
 17920  				s.Spec.ClusterIP = "10.0.0.1"
 17921  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17922  			},
 17923  			numErrs: 0,
 17924  		}, {
 17925  			name: "valid, single family",
 17926  			tweakSvc: func(s *core.Service) {
 17927  				s.Spec.IPFamilyPolicy = &singleStack
 17928  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17929  
 17930  			},
 17931  			numErrs: 0,
 17932  		}, {
 17933  			name: "valid, single ip + single family",
 17934  			tweakSvc: func(s *core.Service) {
 17935  				s.Spec.IPFamilyPolicy = &singleStack
 17936  				s.Spec.ClusterIP = "2001::1"
 17937  				s.Spec.ClusterIPs = []string{"2001::1"}
 17938  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17939  
 17940  			},
 17941  			numErrs: 0,
 17942  		}, {
 17943  			name: "valid, single ip + single family (dual stack requested)",
 17944  			tweakSvc: func(s *core.Service) {
 17945  				s.Spec.IPFamilyPolicy = &preferDualStack
 17946  				s.Spec.ClusterIP = "2001::1"
 17947  				s.Spec.ClusterIPs = []string{"2001::1"}
 17948  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17949  
 17950  			},
 17951  			numErrs: 0,
 17952  		}, {
 17953  			name: "valid, single ip, multi ipfamilies",
 17954  			tweakSvc: func(s *core.Service) {
 17955  				s.Spec.IPFamilyPolicy = &requireDualStack
 17956  				s.Spec.ClusterIP = "10.0.0.1"
 17957  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17958  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17959  			},
 17960  			numErrs: 0,
 17961  		}, {
 17962  			name: "valid, multi ips, multi ipfamilies (4,6)",
 17963  			tweakSvc: func(s *core.Service) {
 17964  				s.Spec.IPFamilyPolicy = &requireDualStack
 17965  				s.Spec.ClusterIP = "10.0.0.1"
 17966  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17967  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17968  			},
 17969  			numErrs: 0,
 17970  		}, {
 17971  			name: "valid, ips, multi ipfamilies (6,4)",
 17972  			tweakSvc: func(s *core.Service) {
 17973  				s.Spec.IPFamilyPolicy = &requireDualStack
 17974  				s.Spec.ClusterIP = "2001::1"
 17975  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17976  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17977  			},
 17978  			numErrs: 0,
 17979  		}, {
 17980  			name: "valid, multi ips (6,4)",
 17981  			tweakSvc: func(s *core.Service) {
 17982  				s.Spec.IPFamilyPolicy = &requireDualStack
 17983  				s.Spec.ClusterIP = "2001::1"
 17984  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17985  			},
 17986  			numErrs: 0,
 17987  		}, {
 17988  			name: "valid, multi ipfamilies (6,4)",
 17989  			tweakSvc: func(s *core.Service) {
 17990  				s.Spec.IPFamilyPolicy = &requireDualStack
 17991  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17992  			},
 17993  			numErrs: 0,
 17994  		}, {
 17995  			name: "valid, multi ips (4,6)",
 17996  			tweakSvc: func(s *core.Service) {
 17997  				s.Spec.IPFamilyPolicy = &requireDualStack
 17998  				s.Spec.ClusterIP = "10.0.0.1"
 17999  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 18000  			},
 18001  			numErrs: 0,
 18002  		}, {
 18003  			name: "valid,  multi ipfamilies (4,6)",
 18004  			tweakSvc: func(s *core.Service) {
 18005  				s.Spec.IPFamilyPolicy = &requireDualStack
 18006  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 18007  			},
 18008  			numErrs: 0,
 18009  		}, {
 18010  			name: "valid, dual stack",
 18011  			tweakSvc: func(s *core.Service) {
 18012  				s.Spec.IPFamilyPolicy = &requireDualStack
 18013  			},
 18014  			numErrs: 0,
 18015  		},
 18016  
 18017  		{
 18018  			name: `valid appProtocol`,
 18019  			tweakSvc: func(s *core.Service) {
 18020  				s.Spec.Ports = []core.ServicePort{{
 18021  					Port:        12345,
 18022  					TargetPort:  intstr.FromInt32(12345),
 18023  					Protocol:    "TCP",
 18024  					AppProtocol: utilpointer.String("HTTP"),
 18025  				}}
 18026  			},
 18027  			numErrs: 0,
 18028  		}, {
 18029  			name: `valid custom appProtocol`,
 18030  			tweakSvc: func(s *core.Service) {
 18031  				s.Spec.Ports = []core.ServicePort{{
 18032  					Port:        12345,
 18033  					TargetPort:  intstr.FromInt32(12345),
 18034  					Protocol:    "TCP",
 18035  					AppProtocol: utilpointer.String("example.com/protocol"),
 18036  				}}
 18037  			},
 18038  			numErrs: 0,
 18039  		}, {
 18040  			name: `invalid appProtocol`,
 18041  			tweakSvc: func(s *core.Service) {
 18042  				s.Spec.Ports = []core.ServicePort{{
 18043  					Port:        12345,
 18044  					TargetPort:  intstr.FromInt32(12345),
 18045  					Protocol:    "TCP",
 18046  					AppProtocol: utilpointer.String("example.com/protocol_with{invalid}[characters]"),
 18047  				}}
 18048  			},
 18049  			numErrs: 1,
 18050  		},
 18051  
 18052  		{
 18053  			name: "invalid cluster ip != clusterIP in multi ip service",
 18054  			tweakSvc: func(s *core.Service) {
 18055  				s.Spec.IPFamilyPolicy = &requireDualStack
 18056  				s.Spec.ClusterIP = "10.0.0.10"
 18057  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 18058  			},
 18059  			numErrs: 1,
 18060  		}, {
 18061  			name: "invalid cluster ip != clusterIP in single ip service",
 18062  			tweakSvc: func(s *core.Service) {
 18063  				s.Spec.ClusterIP = "10.0.0.10"
 18064  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 18065  			},
 18066  			numErrs: 1,
 18067  		}, {
 18068  			name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 18069  			tweakSvc: func(s *core.Service) {
 18070  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18071  			},
 18072  			numErrs: 1,
 18073  		}, {
 18074  			name: "valid LoadBalancerClass when type is LoadBalancer",
 18075  			tweakSvc: func(s *core.Service) {
 18076  				s.Spec.Type = core.ServiceTypeLoadBalancer
 18077  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18078  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18079  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 18080  			},
 18081  			numErrs: 0,
 18082  		}, {
 18083  			name: "invalid LoadBalancerClass",
 18084  			tweakSvc: func(s *core.Service) {
 18085  				s.Spec.Type = core.ServiceTypeLoadBalancer
 18086  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18087  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18088  				s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass")
 18089  			},
 18090  			numErrs: 1,
 18091  		}, {
 18092  			name: "invalid: set LoadBalancerClass when type is not LoadBalancer",
 18093  			tweakSvc: func(s *core.Service) {
 18094  				s.Spec.Type = core.ServiceTypeClusterIP
 18095  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 18096  			},
 18097  			numErrs: 1,
 18098  		}, {
 18099  			name: "topology annotations are mismatched",
 18100  			tweakSvc: func(s *core.Service) {
 18101  				s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 18102  				s.Annotations[core.AnnotationTopologyMode] = "different"
 18103  			},
 18104  			numErrs: 1,
 18105  		}, {
 18106  			name: "valid: trafficDistribution field set to PreferClose",
 18107  			tweakSvc: func(s *core.Service) {
 18108  				s.Spec.TrafficDistribution = utilpointer.String("PreferClose")
 18109  			},
 18110  			numErrs: 0,
 18111  		}, {
 18112  			name: "invalid: trafficDistribution field set to Random",
 18113  			tweakSvc: func(s *core.Service) {
 18114  				s.Spec.TrafficDistribution = utilpointer.String("Random")
 18115  			},
 18116  			numErrs: 1,
 18117  		},
 18118  	}
 18119  
 18120  	for _, tc := range testCases {
 18121  		t.Run(tc.name, func(t *testing.T) {
 18122  			for i := range tc.featureGates {
 18123  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tc.featureGates[i], true)()
 18124  			}
 18125  			svc := makeValidService()
 18126  			tc.tweakSvc(&svc)
 18127  			errs := ValidateServiceCreate(&svc)
 18128  			if len(errs) != tc.numErrs {
 18129  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 18130  			}
 18131  		})
 18132  	}
 18133  }
 18134  
 18135  func TestValidateServiceExternalTrafficPolicy(t *testing.T) {
 18136  	testCases := []struct {
 18137  		name     string
 18138  		tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it.
 18139  		numErrs  int
 18140  	}{{
 18141  		name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
 18142  		tweakSvc: func(s *core.Service) {
 18143  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18144  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18145  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18146  			s.Spec.HealthCheckNodePort = 34567
 18147  		},
 18148  		numErrs: 0,
 18149  	}, {
 18150  		name: "valid nodePort service with externalTrafficPolicy set",
 18151  		tweakSvc: func(s *core.Service) {
 18152  			s.Spec.Type = core.ServiceTypeNodePort
 18153  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18154  		},
 18155  		numErrs: 0,
 18156  	}, {
 18157  		name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
 18158  		tweakSvc: func(s *core.Service) {
 18159  			s.Spec.Type = core.ServiceTypeClusterIP
 18160  		},
 18161  		numErrs: 0,
 18162  	}, {
 18163  		name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
 18164  		tweakSvc: func(s *core.Service) {
 18165  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18166  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18167  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18168  			s.Spec.HealthCheckNodePort = 34567
 18169  		},
 18170  		numErrs: 1,
 18171  	}, {
 18172  		name: "cannot set healthCheckNodePort field on nodePort service",
 18173  		tweakSvc: func(s *core.Service) {
 18174  			s.Spec.Type = core.ServiceTypeNodePort
 18175  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18176  			s.Spec.HealthCheckNodePort = 34567
 18177  		},
 18178  		numErrs: 1,
 18179  	}, {
 18180  		name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
 18181  		tweakSvc: func(s *core.Service) {
 18182  			s.Spec.Type = core.ServiceTypeClusterIP
 18183  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18184  			s.Spec.HealthCheckNodePort = 34567
 18185  		},
 18186  		numErrs: 2,
 18187  	}, {
 18188  		name: "cannot set externalTrafficPolicy field on ExternalName service",
 18189  		tweakSvc: func(s *core.Service) {
 18190  			s.Spec.Type = core.ServiceTypeExternalName
 18191  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 18192  		},
 18193  		numErrs: 1,
 18194  	}, {
 18195  		name: "externalTrafficPolicy is required on NodePort service",
 18196  		tweakSvc: func(s *core.Service) {
 18197  			s.Spec.Type = core.ServiceTypeNodePort
 18198  		},
 18199  		numErrs: 1,
 18200  	}, {
 18201  		name: "externalTrafficPolicy is required on LoadBalancer service",
 18202  		tweakSvc: func(s *core.Service) {
 18203  			s.Spec.Type = core.ServiceTypeLoadBalancer
 18204  		},
 18205  		numErrs: 1,
 18206  	}, {
 18207  		name: "externalTrafficPolicy is required on ClusterIP service with externalIPs",
 18208  		tweakSvc: func(s *core.Service) {
 18209  			s.Spec.Type = core.ServiceTypeClusterIP
 18210  			s.Spec.ExternalIPs = []string{"1.2.3,4"}
 18211  		},
 18212  		numErrs: 1,
 18213  	},
 18214  	}
 18215  
 18216  	for _, tc := range testCases {
 18217  		svc := makeValidService()
 18218  		tc.tweakSvc(&svc)
 18219  		errs := validateServiceExternalTrafficPolicy(&svc)
 18220  		if len(errs) != tc.numErrs {
 18221  			t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
 18222  		}
 18223  	}
 18224  }
 18225  
 18226  func TestValidateReplicationControllerStatus(t *testing.T) {
 18227  	tests := []struct {
 18228  		name string
 18229  
 18230  		replicas             int32
 18231  		fullyLabeledReplicas int32
 18232  		readyReplicas        int32
 18233  		availableReplicas    int32
 18234  		observedGeneration   int64
 18235  
 18236  		expectedErr bool
 18237  	}{{
 18238  		name:                 "valid status",
 18239  		replicas:             3,
 18240  		fullyLabeledReplicas: 3,
 18241  		readyReplicas:        2,
 18242  		availableReplicas:    1,
 18243  		observedGeneration:   2,
 18244  		expectedErr:          false,
 18245  	}, {
 18246  		name:                 "invalid replicas",
 18247  		replicas:             -1,
 18248  		fullyLabeledReplicas: 3,
 18249  		readyReplicas:        2,
 18250  		availableReplicas:    1,
 18251  		observedGeneration:   2,
 18252  		expectedErr:          true,
 18253  	}, {
 18254  		name:                 "invalid fullyLabeledReplicas",
 18255  		replicas:             3,
 18256  		fullyLabeledReplicas: -1,
 18257  		readyReplicas:        2,
 18258  		availableReplicas:    1,
 18259  		observedGeneration:   2,
 18260  		expectedErr:          true,
 18261  	}, {
 18262  		name:                 "invalid readyReplicas",
 18263  		replicas:             3,
 18264  		fullyLabeledReplicas: 3,
 18265  		readyReplicas:        -1,
 18266  		availableReplicas:    1,
 18267  		observedGeneration:   2,
 18268  		expectedErr:          true,
 18269  	}, {
 18270  		name:                 "invalid availableReplicas",
 18271  		replicas:             3,
 18272  		fullyLabeledReplicas: 3,
 18273  		readyReplicas:        3,
 18274  		availableReplicas:    -1,
 18275  		observedGeneration:   2,
 18276  		expectedErr:          true,
 18277  	}, {
 18278  		name:                 "invalid observedGeneration",
 18279  		replicas:             3,
 18280  		fullyLabeledReplicas: 3,
 18281  		readyReplicas:        3,
 18282  		availableReplicas:    3,
 18283  		observedGeneration:   -1,
 18284  		expectedErr:          true,
 18285  	}, {
 18286  		name:                 "fullyLabeledReplicas greater than replicas",
 18287  		replicas:             3,
 18288  		fullyLabeledReplicas: 4,
 18289  		readyReplicas:        3,
 18290  		availableReplicas:    3,
 18291  		observedGeneration:   1,
 18292  		expectedErr:          true,
 18293  	}, {
 18294  		name:                 "readyReplicas greater than replicas",
 18295  		replicas:             3,
 18296  		fullyLabeledReplicas: 3,
 18297  		readyReplicas:        4,
 18298  		availableReplicas:    3,
 18299  		observedGeneration:   1,
 18300  		expectedErr:          true,
 18301  	}, {
 18302  		name:                 "availableReplicas greater than replicas",
 18303  		replicas:             3,
 18304  		fullyLabeledReplicas: 3,
 18305  		readyReplicas:        3,
 18306  		availableReplicas:    4,
 18307  		observedGeneration:   1,
 18308  		expectedErr:          true,
 18309  	}, {
 18310  		name:                 "availableReplicas greater than readyReplicas",
 18311  		replicas:             3,
 18312  		fullyLabeledReplicas: 3,
 18313  		readyReplicas:        2,
 18314  		availableReplicas:    3,
 18315  		observedGeneration:   1,
 18316  		expectedErr:          true,
 18317  	},
 18318  	}
 18319  
 18320  	for _, test := range tests {
 18321  		status := core.ReplicationControllerStatus{
 18322  			Replicas:             test.replicas,
 18323  			FullyLabeledReplicas: test.fullyLabeledReplicas,
 18324  			ReadyReplicas:        test.readyReplicas,
 18325  			AvailableReplicas:    test.availableReplicas,
 18326  			ObservedGeneration:   test.observedGeneration,
 18327  		}
 18328  
 18329  		if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
 18330  			t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
 18331  		}
 18332  	}
 18333  }
 18334  
 18335  func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
 18336  	validSelector := map[string]string{"a": "b"}
 18337  	validPodTemplate := core.PodTemplate{
 18338  		Template: core.PodTemplateSpec{
 18339  			ObjectMeta: metav1.ObjectMeta{
 18340  				Labels: validSelector,
 18341  			},
 18342  			Spec: core.PodSpec{
 18343  				RestartPolicy: core.RestartPolicyAlways,
 18344  				DNSPolicy:     core.DNSClusterFirst,
 18345  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18346  			},
 18347  		},
 18348  	}
 18349  	type rcUpdateTest struct {
 18350  		old    core.ReplicationController
 18351  		update core.ReplicationController
 18352  	}
 18353  	successCases := []rcUpdateTest{{
 18354  		old: core.ReplicationController{
 18355  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18356  			Spec: core.ReplicationControllerSpec{
 18357  				Selector: validSelector,
 18358  				Template: &validPodTemplate.Template,
 18359  			},
 18360  			Status: core.ReplicationControllerStatus{
 18361  				Replicas: 2,
 18362  			},
 18363  		},
 18364  		update: core.ReplicationController{
 18365  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18366  			Spec: core.ReplicationControllerSpec{
 18367  				Replicas: 3,
 18368  				Selector: validSelector,
 18369  				Template: &validPodTemplate.Template,
 18370  			},
 18371  			Status: core.ReplicationControllerStatus{
 18372  				Replicas: 4,
 18373  			},
 18374  		},
 18375  	},
 18376  	}
 18377  	for _, successCase := range successCases {
 18378  		successCase.old.ObjectMeta.ResourceVersion = "1"
 18379  		successCase.update.ObjectMeta.ResourceVersion = "1"
 18380  		if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
 18381  			t.Errorf("expected success: %v", errs)
 18382  		}
 18383  	}
 18384  	errorCases := map[string]rcUpdateTest{
 18385  		"negative replicas": {
 18386  			old: core.ReplicationController{
 18387  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18388  				Spec: core.ReplicationControllerSpec{
 18389  					Selector: validSelector,
 18390  					Template: &validPodTemplate.Template,
 18391  				},
 18392  				Status: core.ReplicationControllerStatus{
 18393  					Replicas: 3,
 18394  				},
 18395  			},
 18396  			update: core.ReplicationController{
 18397  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18398  				Spec: core.ReplicationControllerSpec{
 18399  					Replicas: 2,
 18400  					Selector: validSelector,
 18401  					Template: &validPodTemplate.Template,
 18402  				},
 18403  				Status: core.ReplicationControllerStatus{
 18404  					Replicas: -3,
 18405  				},
 18406  			},
 18407  		},
 18408  	}
 18409  	for testName, errorCase := range errorCases {
 18410  		if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
 18411  			t.Errorf("expected failure: %s", testName)
 18412  		}
 18413  	}
 18414  
 18415  }
 18416  
 18417  func TestValidateReplicationControllerUpdate(t *testing.T) {
 18418  	validSelector := map[string]string{"a": "b"}
 18419  	validPodTemplate := core.PodTemplate{
 18420  		Template: core.PodTemplateSpec{
 18421  			ObjectMeta: metav1.ObjectMeta{
 18422  				Labels: validSelector,
 18423  			},
 18424  			Spec: core.PodSpec{
 18425  				RestartPolicy: core.RestartPolicyAlways,
 18426  				DNSPolicy:     core.DNSClusterFirst,
 18427  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18428  			},
 18429  		},
 18430  	}
 18431  	readWriteVolumePodTemplate := core.PodTemplate{
 18432  		Template: core.PodTemplateSpec{
 18433  			ObjectMeta: metav1.ObjectMeta{
 18434  				Labels: validSelector,
 18435  			},
 18436  			Spec: core.PodSpec{
 18437  				RestartPolicy: core.RestartPolicyAlways,
 18438  				DNSPolicy:     core.DNSClusterFirst,
 18439  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18440  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 18441  			},
 18442  		},
 18443  	}
 18444  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18445  	invalidPodTemplate := core.PodTemplate{
 18446  		Template: core.PodTemplateSpec{
 18447  			Spec: core.PodSpec{
 18448  				RestartPolicy: core.RestartPolicyAlways,
 18449  				DNSPolicy:     core.DNSClusterFirst,
 18450  			},
 18451  			ObjectMeta: metav1.ObjectMeta{
 18452  				Labels: invalidSelector,
 18453  			},
 18454  		},
 18455  	}
 18456  	type rcUpdateTest struct {
 18457  		old    core.ReplicationController
 18458  		update core.ReplicationController
 18459  	}
 18460  	successCases := []rcUpdateTest{{
 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: 3,
 18472  				Selector: validSelector,
 18473  				Template: &validPodTemplate.Template,
 18474  			},
 18475  		},
 18476  	}, {
 18477  		old: core.ReplicationController{
 18478  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18479  			Spec: core.ReplicationControllerSpec{
 18480  				Selector: validSelector,
 18481  				Template: &validPodTemplate.Template,
 18482  			},
 18483  		},
 18484  		update: core.ReplicationController{
 18485  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18486  			Spec: core.ReplicationControllerSpec{
 18487  				Replicas: 1,
 18488  				Selector: validSelector,
 18489  				Template: &readWriteVolumePodTemplate.Template,
 18490  			},
 18491  		},
 18492  	},
 18493  	}
 18494  	for _, successCase := range successCases {
 18495  		successCase.old.ObjectMeta.ResourceVersion = "1"
 18496  		successCase.update.ObjectMeta.ResourceVersion = "1"
 18497  		if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old, PodValidationOptions{}); len(errs) != 0 {
 18498  			t.Errorf("expected success: %v", errs)
 18499  		}
 18500  	}
 18501  	errorCases := map[string]rcUpdateTest{
 18502  		"more than one read/write": {
 18503  			old: core.ReplicationController{
 18504  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18505  				Spec: core.ReplicationControllerSpec{
 18506  					Selector: validSelector,
 18507  					Template: &validPodTemplate.Template,
 18508  				},
 18509  			},
 18510  			update: core.ReplicationController{
 18511  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18512  				Spec: core.ReplicationControllerSpec{
 18513  					Replicas: 2,
 18514  					Selector: validSelector,
 18515  					Template: &readWriteVolumePodTemplate.Template,
 18516  				},
 18517  			},
 18518  		},
 18519  		"invalid selector": {
 18520  			old: core.ReplicationController{
 18521  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18522  				Spec: core.ReplicationControllerSpec{
 18523  					Selector: validSelector,
 18524  					Template: &validPodTemplate.Template,
 18525  				},
 18526  			},
 18527  			update: core.ReplicationController{
 18528  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18529  				Spec: core.ReplicationControllerSpec{
 18530  					Replicas: 2,
 18531  					Selector: invalidSelector,
 18532  					Template: &validPodTemplate.Template,
 18533  				},
 18534  			},
 18535  		},
 18536  		"invalid pod": {
 18537  			old: core.ReplicationController{
 18538  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18539  				Spec: core.ReplicationControllerSpec{
 18540  					Selector: validSelector,
 18541  					Template: &validPodTemplate.Template,
 18542  				},
 18543  			},
 18544  			update: core.ReplicationController{
 18545  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18546  				Spec: core.ReplicationControllerSpec{
 18547  					Replicas: 2,
 18548  					Selector: validSelector,
 18549  					Template: &invalidPodTemplate.Template,
 18550  				},
 18551  			},
 18552  		},
 18553  		"negative replicas": {
 18554  			old: core.ReplicationController{
 18555  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18556  				Spec: core.ReplicationControllerSpec{
 18557  					Selector: validSelector,
 18558  					Template: &validPodTemplate.Template,
 18559  				},
 18560  			},
 18561  			update: core.ReplicationController{
 18562  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18563  				Spec: core.ReplicationControllerSpec{
 18564  					Replicas: -1,
 18565  					Selector: validSelector,
 18566  					Template: &validPodTemplate.Template,
 18567  				},
 18568  			},
 18569  		},
 18570  	}
 18571  	for testName, errorCase := range errorCases {
 18572  		if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old, PodValidationOptions{}); len(errs) == 0 {
 18573  			t.Errorf("expected failure: %s", testName)
 18574  		}
 18575  	}
 18576  }
 18577  
 18578  func TestValidateReplicationController(t *testing.T) {
 18579  	validSelector := map[string]string{"a": "b"}
 18580  	validPodTemplate := core.PodTemplate{
 18581  		Template: core.PodTemplateSpec{
 18582  			ObjectMeta: metav1.ObjectMeta{
 18583  				Labels: validSelector,
 18584  			},
 18585  			Spec: core.PodSpec{
 18586  				RestartPolicy: core.RestartPolicyAlways,
 18587  				DNSPolicy:     core.DNSClusterFirst,
 18588  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18589  			},
 18590  		},
 18591  	}
 18592  	readWriteVolumePodTemplate := core.PodTemplate{
 18593  		Template: core.PodTemplateSpec{
 18594  			ObjectMeta: metav1.ObjectMeta{
 18595  				Labels: validSelector,
 18596  			},
 18597  			Spec: core.PodSpec{
 18598  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 18599  				RestartPolicy: core.RestartPolicyAlways,
 18600  				DNSPolicy:     core.DNSClusterFirst,
 18601  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18602  			},
 18603  		},
 18604  	}
 18605  	hostnetPodTemplate := core.PodTemplate{
 18606  		Template: core.PodTemplateSpec{
 18607  			ObjectMeta: metav1.ObjectMeta{
 18608  				Labels: validSelector,
 18609  			},
 18610  			Spec: core.PodSpec{
 18611  				SecurityContext: &core.PodSecurityContext{
 18612  					HostNetwork: true,
 18613  				},
 18614  				RestartPolicy: core.RestartPolicyAlways,
 18615  				DNSPolicy:     core.DNSClusterFirst,
 18616  				Containers: []core.Container{{
 18617  					Name:                     "abc",
 18618  					Image:                    "image",
 18619  					ImagePullPolicy:          "IfNotPresent",
 18620  					TerminationMessagePolicy: "File",
 18621  					Ports: []core.ContainerPort{{
 18622  						ContainerPort: 12345,
 18623  						Protocol:      core.ProtocolTCP,
 18624  					}},
 18625  				}},
 18626  			},
 18627  		},
 18628  	}
 18629  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18630  	invalidPodTemplate := core.PodTemplate{
 18631  		Template: core.PodTemplateSpec{
 18632  			Spec: core.PodSpec{
 18633  				RestartPolicy: core.RestartPolicyAlways,
 18634  				DNSPolicy:     core.DNSClusterFirst,
 18635  			},
 18636  			ObjectMeta: metav1.ObjectMeta{
 18637  				Labels: invalidSelector,
 18638  			},
 18639  		},
 18640  	}
 18641  	successCases := []core.ReplicationController{{
 18642  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18643  		Spec: core.ReplicationControllerSpec{
 18644  			Selector: validSelector,
 18645  			Template: &validPodTemplate.Template,
 18646  		},
 18647  	}, {
 18648  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18649  		Spec: core.ReplicationControllerSpec{
 18650  			Selector: validSelector,
 18651  			Template: &validPodTemplate.Template,
 18652  		},
 18653  	}, {
 18654  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18655  		Spec: core.ReplicationControllerSpec{
 18656  			Replicas: 1,
 18657  			Selector: validSelector,
 18658  			Template: &readWriteVolumePodTemplate.Template,
 18659  		},
 18660  	}, {
 18661  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
 18662  		Spec: core.ReplicationControllerSpec{
 18663  			Replicas: 1,
 18664  			Selector: validSelector,
 18665  			Template: &hostnetPodTemplate.Template,
 18666  		},
 18667  	}}
 18668  	for _, successCase := range successCases {
 18669  		if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 {
 18670  			t.Errorf("expected success: %v", errs)
 18671  		}
 18672  	}
 18673  
 18674  	errorCases := map[string]core.ReplicationController{
 18675  		"zero-length ID": {
 18676  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 18677  			Spec: core.ReplicationControllerSpec{
 18678  				Selector: validSelector,
 18679  				Template: &validPodTemplate.Template,
 18680  			},
 18681  		},
 18682  		"missing-namespace": {
 18683  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 18684  			Spec: core.ReplicationControllerSpec{
 18685  				Selector: validSelector,
 18686  				Template: &validPodTemplate.Template,
 18687  			},
 18688  		},
 18689  		"empty selector": {
 18690  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18691  			Spec: core.ReplicationControllerSpec{
 18692  				Template: &validPodTemplate.Template,
 18693  			},
 18694  		},
 18695  		"selector_doesnt_match": {
 18696  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18697  			Spec: core.ReplicationControllerSpec{
 18698  				Selector: map[string]string{"foo": "bar"},
 18699  				Template: &validPodTemplate.Template,
 18700  			},
 18701  		},
 18702  		"invalid manifest": {
 18703  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18704  			Spec: core.ReplicationControllerSpec{
 18705  				Selector: validSelector,
 18706  			},
 18707  		},
 18708  		"read-write persistent disk with > 1 pod": {
 18709  			ObjectMeta: metav1.ObjectMeta{Name: "abc"},
 18710  			Spec: core.ReplicationControllerSpec{
 18711  				Replicas: 2,
 18712  				Selector: validSelector,
 18713  				Template: &readWriteVolumePodTemplate.Template,
 18714  			},
 18715  		},
 18716  		"negative_replicas": {
 18717  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 18718  			Spec: core.ReplicationControllerSpec{
 18719  				Replicas: -1,
 18720  				Selector: validSelector,
 18721  			},
 18722  		},
 18723  		"invalid_label": {
 18724  			ObjectMeta: metav1.ObjectMeta{
 18725  				Name:      "abc-123",
 18726  				Namespace: metav1.NamespaceDefault,
 18727  				Labels: map[string]string{
 18728  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18729  				},
 18730  			},
 18731  			Spec: core.ReplicationControllerSpec{
 18732  				Selector: validSelector,
 18733  				Template: &validPodTemplate.Template,
 18734  			},
 18735  		},
 18736  		"invalid_label 2": {
 18737  			ObjectMeta: metav1.ObjectMeta{
 18738  				Name:      "abc-123",
 18739  				Namespace: metav1.NamespaceDefault,
 18740  				Labels: map[string]string{
 18741  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18742  				},
 18743  			},
 18744  			Spec: core.ReplicationControllerSpec{
 18745  				Template: &invalidPodTemplate.Template,
 18746  			},
 18747  		},
 18748  		"invalid_annotation": {
 18749  			ObjectMeta: metav1.ObjectMeta{
 18750  				Name:      "abc-123",
 18751  				Namespace: metav1.NamespaceDefault,
 18752  				Annotations: map[string]string{
 18753  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 18754  				},
 18755  			},
 18756  			Spec: core.ReplicationControllerSpec{
 18757  				Selector: validSelector,
 18758  				Template: &validPodTemplate.Template,
 18759  			},
 18760  		},
 18761  		"invalid restart policy 1": {
 18762  			ObjectMeta: metav1.ObjectMeta{
 18763  				Name:      "abc-123",
 18764  				Namespace: metav1.NamespaceDefault,
 18765  			},
 18766  			Spec: core.ReplicationControllerSpec{
 18767  				Selector: validSelector,
 18768  				Template: &core.PodTemplateSpec{
 18769  					Spec: core.PodSpec{
 18770  						RestartPolicy: core.RestartPolicyOnFailure,
 18771  						DNSPolicy:     core.DNSClusterFirst,
 18772  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18773  					},
 18774  					ObjectMeta: metav1.ObjectMeta{
 18775  						Labels: validSelector,
 18776  					},
 18777  				},
 18778  			},
 18779  		},
 18780  		"invalid restart policy 2": {
 18781  			ObjectMeta: metav1.ObjectMeta{
 18782  				Name:      "abc-123",
 18783  				Namespace: metav1.NamespaceDefault,
 18784  			},
 18785  			Spec: core.ReplicationControllerSpec{
 18786  				Selector: validSelector,
 18787  				Template: &core.PodTemplateSpec{
 18788  					Spec: core.PodSpec{
 18789  						RestartPolicy: core.RestartPolicyNever,
 18790  						DNSPolicy:     core.DNSClusterFirst,
 18791  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18792  					},
 18793  					ObjectMeta: metav1.ObjectMeta{
 18794  						Labels: validSelector,
 18795  					},
 18796  				},
 18797  			},
 18798  		},
 18799  		"template may not contain ephemeral containers": {
 18800  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 18801  			Spec: core.ReplicationControllerSpec{
 18802  				Replicas: 1,
 18803  				Selector: validSelector,
 18804  				Template: &core.PodTemplateSpec{
 18805  					ObjectMeta: metav1.ObjectMeta{
 18806  						Labels: validSelector,
 18807  					},
 18808  					Spec: core.PodSpec{
 18809  						RestartPolicy:       core.RestartPolicyAlways,
 18810  						DNSPolicy:           core.DNSClusterFirst,
 18811  						Containers:          []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 18812  						EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
 18813  					},
 18814  				},
 18815  			},
 18816  		},
 18817  	}
 18818  	for k, v := range errorCases {
 18819  		errs := ValidateReplicationController(&v, PodValidationOptions{})
 18820  		if len(errs) == 0 {
 18821  			t.Errorf("expected failure for %s", k)
 18822  		}
 18823  		for i := range errs {
 18824  			field := errs[i].Field
 18825  			if !strings.HasPrefix(field, "spec.template.") &&
 18826  				field != "metadata.name" &&
 18827  				field != "metadata.namespace" &&
 18828  				field != "spec.selector" &&
 18829  				field != "spec.template" &&
 18830  				field != "GCEPersistentDisk.ReadOnly" &&
 18831  				field != "spec.replicas" &&
 18832  				field != "spec.template.labels" &&
 18833  				field != "metadata.annotations" &&
 18834  				field != "metadata.labels" &&
 18835  				field != "status.replicas" {
 18836  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
 18837  			}
 18838  		}
 18839  	}
 18840  }
 18841  
 18842  func TestValidateNode(t *testing.T) {
 18843  	validSelector := map[string]string{"a": "b"}
 18844  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 18845  	successCases := []core.Node{{
 18846  		ObjectMeta: metav1.ObjectMeta{
 18847  			Name:   "abc",
 18848  			Labels: validSelector,
 18849  		},
 18850  		Status: core.NodeStatus{
 18851  			Addresses: []core.NodeAddress{
 18852  				{Type: core.NodeExternalIP, Address: "something"},
 18853  			},
 18854  			Capacity: core.ResourceList{
 18855  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18856  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18857  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 18858  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 18859  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("0"),
 18860  			},
 18861  		},
 18862  	}, {
 18863  		ObjectMeta: metav1.ObjectMeta{
 18864  			Name: "abc",
 18865  		},
 18866  		Status: core.NodeStatus{
 18867  			Addresses: []core.NodeAddress{
 18868  				{Type: core.NodeExternalIP, Address: "something"},
 18869  			},
 18870  			Capacity: core.ResourceList{
 18871  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18872  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18873  			},
 18874  		},
 18875  	}, {
 18876  		ObjectMeta: metav1.ObjectMeta{
 18877  			Name:   "abc",
 18878  			Labels: validSelector,
 18879  		},
 18880  		Status: core.NodeStatus{
 18881  			Addresses: []core.NodeAddress{
 18882  				{Type: core.NodeExternalIP, Address: "something"},
 18883  			},
 18884  			Capacity: core.ResourceList{
 18885  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18886  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18887  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 18888  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 18889  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("10Gi"),
 18890  			},
 18891  		},
 18892  	}, {
 18893  		ObjectMeta: metav1.ObjectMeta{
 18894  			Name: "dedicated-node1",
 18895  		},
 18896  		Status: core.NodeStatus{
 18897  			Addresses: []core.NodeAddress{
 18898  				{Type: core.NodeExternalIP, Address: "something"},
 18899  			},
 18900  			Capacity: core.ResourceList{
 18901  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18902  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18903  			},
 18904  		},
 18905  		Spec: core.NodeSpec{
 18906  			// Add a valid taint to a node
 18907  			Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}},
 18908  		},
 18909  	}, {
 18910  		ObjectMeta: metav1.ObjectMeta{
 18911  			Name: "abc",
 18912  			Annotations: map[string]string{
 18913  				core.PreferAvoidPodsAnnotationKey: `
 18914  							{
 18915  							    "preferAvoidPods": [
 18916  							        {
 18917  							            "podSignature": {
 18918  							                "podController": {
 18919  							                    "apiVersion": "v1",
 18920  							                    "kind": "ReplicationController",
 18921  							                    "name": "foo",
 18922  							                    "uid": "abcdef123456",
 18923  							                    "controller": true
 18924  							                }
 18925  							            },
 18926  							            "reason": "some reason",
 18927  							            "message": "some message"
 18928  							        }
 18929  							    ]
 18930  							}`,
 18931  			},
 18932  		},
 18933  		Status: core.NodeStatus{
 18934  			Addresses: []core.NodeAddress{
 18935  				{Type: core.NodeExternalIP, Address: "something"},
 18936  			},
 18937  			Capacity: core.ResourceList{
 18938  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18939  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18940  			},
 18941  		},
 18942  	}, {
 18943  		ObjectMeta: metav1.ObjectMeta{
 18944  			Name: "abc",
 18945  		},
 18946  		Status: core.NodeStatus{
 18947  			Addresses: []core.NodeAddress{
 18948  				{Type: core.NodeExternalIP, Address: "something"},
 18949  			},
 18950  			Capacity: core.ResourceList{
 18951  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18952  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18953  			},
 18954  		},
 18955  		Spec: core.NodeSpec{
 18956  			PodCIDRs: []string{"192.168.0.0/16"},
 18957  		},
 18958  	},
 18959  	}
 18960  	for _, successCase := range successCases {
 18961  		if errs := ValidateNode(&successCase); len(errs) != 0 {
 18962  			t.Errorf("expected success: %v", errs)
 18963  		}
 18964  	}
 18965  
 18966  	errorCases := map[string]core.Node{
 18967  		"zero-length Name": {
 18968  			ObjectMeta: metav1.ObjectMeta{
 18969  				Name:   "",
 18970  				Labels: validSelector,
 18971  			},
 18972  			Status: core.NodeStatus{
 18973  				Addresses: []core.NodeAddress{},
 18974  				Capacity: core.ResourceList{
 18975  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18976  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18977  				},
 18978  			},
 18979  		},
 18980  		"invalid-labels": {
 18981  			ObjectMeta: metav1.ObjectMeta{
 18982  				Name:   "abc-123",
 18983  				Labels: invalidSelector,
 18984  			},
 18985  			Status: core.NodeStatus{
 18986  				Capacity: core.ResourceList{
 18987  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18988  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18989  				},
 18990  			},
 18991  		},
 18992  		"missing-taint-key": {
 18993  			ObjectMeta: metav1.ObjectMeta{
 18994  				Name: "dedicated-node1",
 18995  			},
 18996  			Spec: core.NodeSpec{
 18997  				// Add a taint with an empty key to a node
 18998  				Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}},
 18999  			},
 19000  		},
 19001  		"bad-taint-key": {
 19002  			ObjectMeta: metav1.ObjectMeta{
 19003  				Name: "dedicated-node1",
 19004  			},
 19005  			Spec: core.NodeSpec{
 19006  				// Add a taint with an invalid  key to a node
 19007  				Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}},
 19008  			},
 19009  		},
 19010  		"bad-taint-value": {
 19011  			ObjectMeta: metav1.ObjectMeta{
 19012  				Name: "dedicated-node2",
 19013  			},
 19014  			Status: core.NodeStatus{
 19015  				Addresses: []core.NodeAddress{
 19016  					{Type: core.NodeExternalIP, Address: "something"},
 19017  				},
 19018  				Capacity: core.ResourceList{
 19019  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19020  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19021  				},
 19022  			},
 19023  			Spec: core.NodeSpec{
 19024  				// Add a taint with a bad value to a node
 19025  				Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}},
 19026  			},
 19027  		},
 19028  		"missing-taint-effect": {
 19029  			ObjectMeta: metav1.ObjectMeta{
 19030  				Name: "dedicated-node3",
 19031  			},
 19032  			Status: core.NodeStatus{
 19033  				Addresses: []core.NodeAddress{
 19034  					{Type: core.NodeExternalIP, Address: "something"},
 19035  				},
 19036  				Capacity: core.ResourceList{
 19037  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19038  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19039  				},
 19040  			},
 19041  			Spec: core.NodeSpec{
 19042  				// Add a taint with an empty effect to a node
 19043  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}},
 19044  			},
 19045  		},
 19046  		"invalid-taint-effect": {
 19047  			ObjectMeta: metav1.ObjectMeta{
 19048  				Name: "dedicated-node3",
 19049  			},
 19050  			Status: core.NodeStatus{
 19051  				Addresses: []core.NodeAddress{
 19052  					{Type: core.NodeExternalIP, Address: "something"},
 19053  				},
 19054  				Capacity: core.ResourceList{
 19055  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19056  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19057  				},
 19058  			},
 19059  			Spec: core.NodeSpec{
 19060  				// Add a taint with NoExecute effect to a node
 19061  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}},
 19062  			},
 19063  		},
 19064  		"duplicated-taints-with-same-key-effect": {
 19065  			ObjectMeta: metav1.ObjectMeta{
 19066  				Name: "dedicated-node1",
 19067  			},
 19068  			Spec: core.NodeSpec{
 19069  				// Add two taints to the node with the same key and effect; should be rejected.
 19070  				Taints: []core.Taint{
 19071  					{Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"},
 19072  					{Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"},
 19073  				},
 19074  			},
 19075  		},
 19076  		"missing-podSignature": {
 19077  			ObjectMeta: metav1.ObjectMeta{
 19078  				Name: "abc-123",
 19079  				Annotations: map[string]string{
 19080  					core.PreferAvoidPodsAnnotationKey: `
 19081  							{
 19082  							    "preferAvoidPods": [
 19083  							        {
 19084  							            "reason": "some reason",
 19085  							            "message": "some message"
 19086  							        }
 19087  							    ]
 19088  							}`,
 19089  				},
 19090  			},
 19091  			Status: core.NodeStatus{
 19092  				Addresses: []core.NodeAddress{},
 19093  				Capacity: core.ResourceList{
 19094  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19095  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19096  				},
 19097  			},
 19098  		},
 19099  		"invalid-podController": {
 19100  			ObjectMeta: metav1.ObjectMeta{
 19101  				Name: "abc-123",
 19102  				Annotations: map[string]string{
 19103  					core.PreferAvoidPodsAnnotationKey: `
 19104  							{
 19105  							    "preferAvoidPods": [
 19106  							        {
 19107  							            "podSignature": {
 19108  							                "podController": {
 19109  							                    "apiVersion": "v1",
 19110  							                    "kind": "ReplicationController",
 19111  							                    "name": "foo",
 19112                                                                             "uid": "abcdef123456",
 19113                                                                             "controller": false
 19114  							                }
 19115  							            },
 19116  							            "reason": "some reason",
 19117  							            "message": "some message"
 19118  							        }
 19119  							    ]
 19120  							}`,
 19121  				},
 19122  			},
 19123  			Status: core.NodeStatus{
 19124  				Addresses: []core.NodeAddress{},
 19125  				Capacity: core.ResourceList{
 19126  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19127  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19128  				},
 19129  			},
 19130  		},
 19131  		"invalid-pod-cidr": {
 19132  			ObjectMeta: metav1.ObjectMeta{
 19133  				Name: "abc",
 19134  			},
 19135  			Status: core.NodeStatus{
 19136  				Addresses: []core.NodeAddress{
 19137  					{Type: core.NodeExternalIP, Address: "something"},
 19138  				},
 19139  				Capacity: core.ResourceList{
 19140  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19141  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19142  				},
 19143  			},
 19144  			Spec: core.NodeSpec{
 19145  				PodCIDRs: []string{"192.168.0.0"},
 19146  			},
 19147  		},
 19148  		"duplicate-pod-cidr": {
 19149  			ObjectMeta: metav1.ObjectMeta{
 19150  				Name: "abc",
 19151  			},
 19152  			Status: core.NodeStatus{
 19153  				Addresses: []core.NodeAddress{
 19154  					{Type: core.NodeExternalIP, Address: "something"},
 19155  				},
 19156  				Capacity: core.ResourceList{
 19157  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19158  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 19159  				},
 19160  			},
 19161  			Spec: core.NodeSpec{
 19162  				PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"},
 19163  			},
 19164  		},
 19165  	}
 19166  	for k, v := range errorCases {
 19167  		errs := ValidateNode(&v)
 19168  		if len(errs) == 0 {
 19169  			t.Errorf("expected failure for %s", k)
 19170  		}
 19171  		for i := range errs {
 19172  			field := errs[i].Field
 19173  			expectedFields := map[string]bool{
 19174  				"metadata.name":         true,
 19175  				"metadata.labels":       true,
 19176  				"metadata.annotations":  true,
 19177  				"metadata.namespace":    true,
 19178  				"spec.externalID":       true,
 19179  				"spec.taints[0].key":    true,
 19180  				"spec.taints[0].value":  true,
 19181  				"spec.taints[0].effect": true,
 19182  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature":                          true,
 19183  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true,
 19184  			}
 19185  			if val, ok := expectedFields[field]; ok {
 19186  				if !val {
 19187  					t.Errorf("%s: missing prefix for: %v", k, errs[i])
 19188  				}
 19189  			}
 19190  		}
 19191  	}
 19192  }
 19193  
 19194  func TestValidateNodeUpdate(t *testing.T) {
 19195  	tests := []struct {
 19196  		oldNode core.Node
 19197  		node    core.Node
 19198  		valid   bool
 19199  	}{
 19200  		{core.Node{}, core.Node{}, true},
 19201  		{core.Node{
 19202  			ObjectMeta: metav1.ObjectMeta{
 19203  				Name: "foo"}},
 19204  			core.Node{
 19205  				ObjectMeta: metav1.ObjectMeta{
 19206  					Name: "bar"},
 19207  			}, false},
 19208  		{core.Node{
 19209  			ObjectMeta: metav1.ObjectMeta{
 19210  				Name:   "foo",
 19211  				Labels: map[string]string{"foo": "bar"},
 19212  			},
 19213  		}, core.Node{
 19214  			ObjectMeta: metav1.ObjectMeta{
 19215  				Name:   "foo",
 19216  				Labels: map[string]string{"foo": "baz"},
 19217  			},
 19218  		}, true},
 19219  		{core.Node{
 19220  			ObjectMeta: metav1.ObjectMeta{
 19221  				Name: "foo",
 19222  			},
 19223  		}, core.Node{
 19224  			ObjectMeta: metav1.ObjectMeta{
 19225  				Name:   "foo",
 19226  				Labels: map[string]string{"foo": "baz"},
 19227  			},
 19228  		}, true},
 19229  		{core.Node{
 19230  			ObjectMeta: metav1.ObjectMeta{
 19231  				Name:   "foo",
 19232  				Labels: map[string]string{"bar": "foo"},
 19233  			},
 19234  		}, core.Node{
 19235  			ObjectMeta: metav1.ObjectMeta{
 19236  				Name:   "foo",
 19237  				Labels: map[string]string{"foo": "baz"},
 19238  			},
 19239  		}, true},
 19240  		{core.Node{
 19241  			ObjectMeta: metav1.ObjectMeta{
 19242  				Name: "foo",
 19243  			},
 19244  			Spec: core.NodeSpec{
 19245  				PodCIDRs: []string{},
 19246  			},
 19247  		}, core.Node{
 19248  			ObjectMeta: metav1.ObjectMeta{
 19249  				Name: "foo",
 19250  			},
 19251  			Spec: core.NodeSpec{
 19252  				PodCIDRs: []string{"192.168.0.0/16"},
 19253  			},
 19254  		}, true},
 19255  		{core.Node{
 19256  			ObjectMeta: metav1.ObjectMeta{
 19257  				Name: "foo",
 19258  			},
 19259  			Spec: core.NodeSpec{
 19260  				PodCIDRs: []string{"192.123.0.0/16"},
 19261  			},
 19262  		}, core.Node{
 19263  			ObjectMeta: metav1.ObjectMeta{
 19264  				Name: "foo",
 19265  			},
 19266  			Spec: core.NodeSpec{
 19267  				PodCIDRs: []string{"192.168.0.0/16"},
 19268  			},
 19269  		}, false},
 19270  		{core.Node{
 19271  			ObjectMeta: metav1.ObjectMeta{
 19272  				Name: "foo",
 19273  			},
 19274  			Status: core.NodeStatus{
 19275  				Capacity: core.ResourceList{
 19276  					core.ResourceCPU:    resource.MustParse("10000"),
 19277  					core.ResourceMemory: resource.MustParse("100"),
 19278  				},
 19279  			},
 19280  		}, core.Node{
 19281  			ObjectMeta: metav1.ObjectMeta{
 19282  				Name: "foo",
 19283  			},
 19284  			Status: core.NodeStatus{
 19285  				Capacity: core.ResourceList{
 19286  					core.ResourceCPU:    resource.MustParse("100"),
 19287  					core.ResourceMemory: resource.MustParse("10000"),
 19288  				},
 19289  			},
 19290  		}, true},
 19291  		{core.Node{
 19292  			ObjectMeta: metav1.ObjectMeta{
 19293  				Name:   "foo",
 19294  				Labels: map[string]string{"bar": "foo"},
 19295  			},
 19296  			Status: core.NodeStatus{
 19297  				Capacity: core.ResourceList{
 19298  					core.ResourceCPU:    resource.MustParse("10000"),
 19299  					core.ResourceMemory: resource.MustParse("100"),
 19300  				},
 19301  			},
 19302  		}, core.Node{
 19303  			ObjectMeta: metav1.ObjectMeta{
 19304  				Name:   "foo",
 19305  				Labels: map[string]string{"bar": "fooobaz"},
 19306  			},
 19307  			Status: core.NodeStatus{
 19308  				Capacity: core.ResourceList{
 19309  					core.ResourceCPU:    resource.MustParse("100"),
 19310  					core.ResourceMemory: resource.MustParse("10000"),
 19311  				},
 19312  			},
 19313  		}, true},
 19314  		{core.Node{
 19315  			ObjectMeta: metav1.ObjectMeta{
 19316  				Name:   "foo",
 19317  				Labels: map[string]string{"bar": "foo"},
 19318  			},
 19319  			Status: core.NodeStatus{
 19320  				Addresses: []core.NodeAddress{
 19321  					{Type: core.NodeExternalIP, Address: "1.2.3.4"},
 19322  				},
 19323  			},
 19324  		}, core.Node{
 19325  			ObjectMeta: metav1.ObjectMeta{
 19326  				Name:   "foo",
 19327  				Labels: map[string]string{"bar": "fooobaz"},
 19328  			},
 19329  		}, true},
 19330  		{core.Node{
 19331  			ObjectMeta: metav1.ObjectMeta{
 19332  				Name:   "foo",
 19333  				Labels: map[string]string{"foo": "baz"},
 19334  			},
 19335  		}, core.Node{
 19336  			ObjectMeta: metav1.ObjectMeta{
 19337  				Name:   "foo",
 19338  				Labels: map[string]string{"Foo": "baz"},
 19339  			},
 19340  		}, true},
 19341  		{core.Node{
 19342  			ObjectMeta: metav1.ObjectMeta{
 19343  				Name: "foo",
 19344  			},
 19345  			Spec: core.NodeSpec{
 19346  				Unschedulable: false,
 19347  			},
 19348  		}, core.Node{
 19349  			ObjectMeta: metav1.ObjectMeta{
 19350  				Name: "foo",
 19351  			},
 19352  			Spec: core.NodeSpec{
 19353  				Unschedulable: true,
 19354  			},
 19355  		}, true},
 19356  		{core.Node{
 19357  			ObjectMeta: metav1.ObjectMeta{
 19358  				Name: "foo",
 19359  			},
 19360  			Spec: core.NodeSpec{
 19361  				Unschedulable: false,
 19362  			},
 19363  		}, core.Node{
 19364  			ObjectMeta: metav1.ObjectMeta{
 19365  				Name: "foo",
 19366  			},
 19367  			Status: core.NodeStatus{
 19368  				Addresses: []core.NodeAddress{
 19369  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19370  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19371  				},
 19372  			},
 19373  		}, false},
 19374  		{core.Node{
 19375  			ObjectMeta: metav1.ObjectMeta{
 19376  				Name: "foo",
 19377  			},
 19378  			Spec: core.NodeSpec{
 19379  				Unschedulable: false,
 19380  			},
 19381  		}, core.Node{
 19382  			ObjectMeta: metav1.ObjectMeta{
 19383  				Name: "foo",
 19384  			},
 19385  			Status: core.NodeStatus{
 19386  				Addresses: []core.NodeAddress{
 19387  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 19388  					{Type: core.NodeInternalIP, Address: "10.1.1.1"},
 19389  				},
 19390  			},
 19391  		}, true},
 19392  		{core.Node{
 19393  			ObjectMeta: metav1.ObjectMeta{
 19394  				Name: "foo",
 19395  			},
 19396  		}, core.Node{
 19397  			ObjectMeta: metav1.ObjectMeta{
 19398  				Name: "foo",
 19399  				Annotations: map[string]string{
 19400  					core.PreferAvoidPodsAnnotationKey: `
 19401  							{
 19402  							    "preferAvoidPods": [
 19403  							        {
 19404  							            "podSignature": {
 19405  							                "podController": {
 19406  							                    "apiVersion": "v1",
 19407  							                    "kind": "ReplicationController",
 19408  							                    "name": "foo",
 19409                                                                             "uid": "abcdef123456",
 19410                                                                             "controller": true
 19411  							                }
 19412  							            },
 19413  							            "reason": "some reason",
 19414  							            "message": "some message"
 19415  							        }
 19416  							    ]
 19417  							}`,
 19418  				},
 19419  			},
 19420  			Spec: core.NodeSpec{
 19421  				Unschedulable: false,
 19422  			},
 19423  		}, true},
 19424  		{core.Node{
 19425  			ObjectMeta: metav1.ObjectMeta{
 19426  				Name: "foo",
 19427  			},
 19428  		}, core.Node{
 19429  			ObjectMeta: metav1.ObjectMeta{
 19430  				Name: "foo",
 19431  				Annotations: map[string]string{
 19432  					core.PreferAvoidPodsAnnotationKey: `
 19433  							{
 19434  							    "preferAvoidPods": [
 19435  							        {
 19436  							            "reason": "some reason",
 19437  							            "message": "some message"
 19438  							        }
 19439  							    ]
 19440  							}`,
 19441  				},
 19442  			},
 19443  		}, false},
 19444  		{core.Node{
 19445  			ObjectMeta: metav1.ObjectMeta{
 19446  				Name: "foo",
 19447  			},
 19448  		}, core.Node{
 19449  			ObjectMeta: metav1.ObjectMeta{
 19450  				Name: "foo",
 19451  				Annotations: map[string]string{
 19452  					core.PreferAvoidPodsAnnotationKey: `
 19453  							{
 19454  							    "preferAvoidPods": [
 19455  							        {
 19456  							            "podSignature": {
 19457  							                "podController": {
 19458  							                    "apiVersion": "v1",
 19459  							                    "kind": "ReplicationController",
 19460  							                    "name": "foo",
 19461  							                    "uid": "abcdef123456",
 19462  							                    "controller": false
 19463  							                }
 19464  							            },
 19465  							            "reason": "some reason",
 19466  							            "message": "some message"
 19467  							        }
 19468  							    ]
 19469  							}`,
 19470  				},
 19471  			},
 19472  		}, false},
 19473  		{core.Node{
 19474  			ObjectMeta: metav1.ObjectMeta{
 19475  				Name: "valid-extended-resources",
 19476  			},
 19477  		}, core.Node{
 19478  			ObjectMeta: metav1.ObjectMeta{
 19479  				Name: "valid-extended-resources",
 19480  			},
 19481  			Status: core.NodeStatus{
 19482  				Capacity: core.ResourceList{
 19483  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19484  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19485  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 19486  					core.ResourceName("example.com/b"):     resource.MustParse("10"),
 19487  				},
 19488  			},
 19489  		}, true},
 19490  		{core.Node{
 19491  			ObjectMeta: metav1.ObjectMeta{
 19492  				Name: "invalid-fractional-extended-capacity",
 19493  			},
 19494  		}, core.Node{
 19495  			ObjectMeta: metav1.ObjectMeta{
 19496  				Name: "invalid-fractional-extended-capacity",
 19497  			},
 19498  			Status: core.NodeStatus{
 19499  				Capacity: core.ResourceList{
 19500  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19501  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19502  					core.ResourceName("example.com/a"):     resource.MustParse("500m"),
 19503  				},
 19504  			},
 19505  		}, false},
 19506  		{core.Node{
 19507  			ObjectMeta: metav1.ObjectMeta{
 19508  				Name: "invalid-fractional-extended-allocatable",
 19509  			},
 19510  		}, core.Node{
 19511  			ObjectMeta: metav1.ObjectMeta{
 19512  				Name: "invalid-fractional-extended-allocatable",
 19513  			},
 19514  			Status: core.NodeStatus{
 19515  				Capacity: core.ResourceList{
 19516  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19517  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19518  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 19519  				},
 19520  				Allocatable: core.ResourceList{
 19521  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 19522  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 19523  					core.ResourceName("example.com/a"):     resource.MustParse("4.5"),
 19524  				},
 19525  			},
 19526  		}, false},
 19527  		{core.Node{
 19528  			ObjectMeta: metav1.ObjectMeta{
 19529  				Name: "update-provider-id-when-not-set",
 19530  			},
 19531  		}, core.Node{
 19532  			ObjectMeta: metav1.ObjectMeta{
 19533  				Name: "update-provider-id-when-not-set",
 19534  			},
 19535  			Spec: core.NodeSpec{
 19536  				ProviderID: "provider:///new",
 19537  			},
 19538  		}, true},
 19539  		{core.Node{
 19540  			ObjectMeta: metav1.ObjectMeta{
 19541  				Name: "update-provider-id-when-set",
 19542  			},
 19543  			Spec: core.NodeSpec{
 19544  				ProviderID: "provider:///old",
 19545  			},
 19546  		}, core.Node{
 19547  			ObjectMeta: metav1.ObjectMeta{
 19548  				Name: "update-provider-id-when-set",
 19549  			},
 19550  			Spec: core.NodeSpec{
 19551  				ProviderID: "provider:///new",
 19552  			},
 19553  		}, false},
 19554  		{core.Node{
 19555  			ObjectMeta: metav1.ObjectMeta{
 19556  				Name: "pod-cidrs-as-is",
 19557  			},
 19558  			Spec: core.NodeSpec{
 19559  				PodCIDRs: []string{"192.168.0.0/16"},
 19560  			},
 19561  		}, core.Node{
 19562  			ObjectMeta: metav1.ObjectMeta{
 19563  				Name: "pod-cidrs-as-is",
 19564  			},
 19565  			Spec: core.NodeSpec{
 19566  				PodCIDRs: []string{"192.168.0.0/16"},
 19567  			},
 19568  		}, true},
 19569  		{core.Node{
 19570  			ObjectMeta: metav1.ObjectMeta{
 19571  				Name: "pod-cidrs-as-is-2",
 19572  			},
 19573  			Spec: core.NodeSpec{
 19574  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19575  			},
 19576  		}, core.Node{
 19577  			ObjectMeta: metav1.ObjectMeta{
 19578  				Name: "pod-cidrs-as-is-2",
 19579  			},
 19580  			Spec: core.NodeSpec{
 19581  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19582  			},
 19583  		}, true},
 19584  		{core.Node{
 19585  			ObjectMeta: metav1.ObjectMeta{
 19586  				Name: "pod-cidrs-not-same-length",
 19587  			},
 19588  			Spec: core.NodeSpec{
 19589  				PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"},
 19590  			},
 19591  		}, core.Node{
 19592  			ObjectMeta: metav1.ObjectMeta{
 19593  				Name: "pod-cidrs-not-same-length",
 19594  			},
 19595  			Spec: core.NodeSpec{
 19596  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19597  			},
 19598  		}, false},
 19599  		{core.Node{
 19600  			ObjectMeta: metav1.ObjectMeta{
 19601  				Name: "pod-cidrs-not-same",
 19602  			},
 19603  			Spec: core.NodeSpec{
 19604  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 19605  			},
 19606  		}, core.Node{
 19607  			ObjectMeta: metav1.ObjectMeta{
 19608  				Name: "pod-cidrs-not-same",
 19609  			},
 19610  			Spec: core.NodeSpec{
 19611  				PodCIDRs: []string{"2000::/10", "192.168.0.0/16"},
 19612  			},
 19613  		}, false},
 19614  	}
 19615  	for i, test := range tests {
 19616  		test.oldNode.ObjectMeta.ResourceVersion = "1"
 19617  		test.node.ObjectMeta.ResourceVersion = "1"
 19618  		errs := ValidateNodeUpdate(&test.node, &test.oldNode)
 19619  		if test.valid && len(errs) > 0 {
 19620  			t.Errorf("%d: Unexpected error: %v", i, errs)
 19621  			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
 19622  		}
 19623  		if !test.valid && len(errs) == 0 {
 19624  			t.Errorf("%d: Unexpected non-error", i)
 19625  		}
 19626  	}
 19627  }
 19628  
 19629  func TestValidateServiceUpdate(t *testing.T) {
 19630  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 19631  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 19632  	singleStack := core.IPFamilyPolicySingleStack
 19633  	testCases := []struct {
 19634  		name     string
 19635  		tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
 19636  		numErrs  int
 19637  	}{{
 19638  		name: "no change",
 19639  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19640  			// do nothing
 19641  		},
 19642  		numErrs: 0,
 19643  	}, {
 19644  		name: "change name",
 19645  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19646  			newSvc.Name += "2"
 19647  		},
 19648  		numErrs: 1,
 19649  	}, {
 19650  		name: "change namespace",
 19651  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19652  			newSvc.Namespace += "2"
 19653  		},
 19654  		numErrs: 1,
 19655  	}, {
 19656  		name: "change label valid",
 19657  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19658  			newSvc.Labels["key"] = "other-value"
 19659  		},
 19660  		numErrs: 0,
 19661  	}, {
 19662  		name: "add label",
 19663  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19664  			newSvc.Labels["key2"] = "value2"
 19665  		},
 19666  		numErrs: 0,
 19667  	}, {
 19668  		name: "change cluster IP",
 19669  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19670  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19671  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19672  
 19673  			newSvc.Spec.ClusterIP = "8.6.7.5"
 19674  			newSvc.Spec.ClusterIPs = []string{"8.6.7.5"}
 19675  		},
 19676  		numErrs: 1,
 19677  	}, {
 19678  		name: "remove cluster IP",
 19679  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19680  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19681  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19682  
 19683  			newSvc.Spec.ClusterIP = ""
 19684  			newSvc.Spec.ClusterIPs = nil
 19685  		},
 19686  		numErrs: 1,
 19687  	}, {
 19688  		name: "change affinity",
 19689  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19690  			newSvc.Spec.SessionAffinity = "ClientIP"
 19691  			newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 19692  				ClientIP: &core.ClientIPConfig{
 19693  					TimeoutSeconds: utilpointer.Int32(90),
 19694  				},
 19695  			}
 19696  		},
 19697  		numErrs: 0,
 19698  	}, {
 19699  		name: "remove affinity",
 19700  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19701  			newSvc.Spec.SessionAffinity = ""
 19702  		},
 19703  		numErrs: 1,
 19704  	}, {
 19705  		name: "change type",
 19706  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19707  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19708  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19709  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19710  		},
 19711  		numErrs: 0,
 19712  	}, {
 19713  		name: "remove type",
 19714  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19715  			newSvc.Spec.Type = ""
 19716  		},
 19717  		numErrs: 1,
 19718  	}, {
 19719  		name: "change type -> nodeport",
 19720  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19721  			newSvc.Spec.Type = core.ServiceTypeNodePort
 19722  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19723  		},
 19724  		numErrs: 0,
 19725  	}, {
 19726  		name: "add loadBalancerSourceRanges",
 19727  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19728  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19729  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19730  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19731  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19732  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19733  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 19734  		},
 19735  		numErrs: 0,
 19736  	}, {
 19737  		name: "update loadBalancerSourceRanges",
 19738  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19739  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19740  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19741  			oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 19742  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19743  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19744  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19745  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"}
 19746  		},
 19747  		numErrs: 0,
 19748  	}, {
 19749  		name: "LoadBalancer type cannot have None ClusterIP",
 19750  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19751  			newSvc.Spec.ClusterIP = "None"
 19752  			newSvc.Spec.ClusterIPs = []string{"None"}
 19753  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19754  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19755  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19756  		},
 19757  		numErrs: 1,
 19758  	}, {
 19759  		name: "`None` ClusterIP can NOT be changed",
 19760  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19761  			oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19762  			newSvc.Spec.Type = core.ServiceTypeClusterIP
 19763  
 19764  			oldSvc.Spec.ClusterIP = "None"
 19765  			oldSvc.Spec.ClusterIPs = []string{"None"}
 19766  
 19767  			newSvc.Spec.ClusterIP = "1.2.3.4"
 19768  			newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19769  		},
 19770  		numErrs: 1,
 19771  	}, {
 19772  		name: "`None` ClusterIP can NOT be removed",
 19773  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19774  			oldSvc.Spec.ClusterIP = "None"
 19775  			oldSvc.Spec.ClusterIPs = []string{"None"}
 19776  
 19777  			newSvc.Spec.ClusterIP = ""
 19778  			newSvc.Spec.ClusterIPs = nil
 19779  		},
 19780  		numErrs: 1,
 19781  	}, {
 19782  		name: "ClusterIP can NOT be changed to None",
 19783  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 19784  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 19785  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19786  
 19787  			newSvc.Spec.ClusterIP = "None"
 19788  			newSvc.Spec.ClusterIPs = []string{"None"}
 19789  		},
 19790  		numErrs: 1,
 19791  	},
 19792  
 19793  		{
 19794  			name: "Service with ClusterIP type cannot change its set ClusterIP",
 19795  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19796  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19797  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19798  
 19799  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19800  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19801  
 19802  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19803  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19804  			},
 19805  			numErrs: 1,
 19806  		}, {
 19807  			name: "Service with ClusterIP type can change its empty ClusterIP",
 19808  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19809  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19810  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19811  
 19812  				oldSvc.Spec.ClusterIP = ""
 19813  				oldSvc.Spec.ClusterIPs = nil
 19814  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19815  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19816  			},
 19817  			numErrs: 0,
 19818  		}, {
 19819  			name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort",
 19820  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19821  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19822  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19823  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19824  
 19825  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19826  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19827  
 19828  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19829  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19830  			},
 19831  			numErrs: 1,
 19832  		}, {
 19833  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort",
 19834  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19835  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19836  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19837  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19838  
 19839  				oldSvc.Spec.ClusterIP = ""
 19840  				oldSvc.Spec.ClusterIPs = nil
 19841  
 19842  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19843  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19844  			},
 19845  			numErrs: 0,
 19846  		}, {
 19847  			name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer",
 19848  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19849  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19850  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19851  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19852  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19853  
 19854  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19855  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19856  
 19857  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19858  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19859  			},
 19860  			numErrs: 1,
 19861  		}, {
 19862  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer",
 19863  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19864  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19865  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19866  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19867  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19868  
 19869  				oldSvc.Spec.ClusterIP = ""
 19870  				oldSvc.Spec.ClusterIPs = nil
 19871  
 19872  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19873  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19874  			},
 19875  			numErrs: 0,
 19876  		}, {
 19877  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false",
 19878  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19879  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19880  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19881  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19882  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19883  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 19884  			},
 19885  			numErrs: 0,
 19886  		}, {
 19887  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true",
 19888  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19889  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19890  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 19891  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19892  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19893  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19894  			},
 19895  			numErrs: 0,
 19896  		}, {
 19897  			name: "Service with NodePort type cannot change its set ClusterIP",
 19898  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19899  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19900  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19901  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19902  
 19903  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19904  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19905  
 19906  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19907  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19908  			},
 19909  			numErrs: 1,
 19910  		}, {
 19911  			name: "Service with NodePort type can change its empty ClusterIP",
 19912  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19913  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19914  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19915  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19916  
 19917  				oldSvc.Spec.ClusterIP = ""
 19918  				oldSvc.Spec.ClusterIPs = nil
 19919  
 19920  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19921  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19922  			},
 19923  			numErrs: 0,
 19924  		}, {
 19925  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP",
 19926  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19927  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19928  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19929  
 19930  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19931  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19932  
 19933  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19934  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19935  			},
 19936  			numErrs: 1,
 19937  		}, {
 19938  			name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP",
 19939  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19940  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19941  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19942  
 19943  				oldSvc.Spec.ClusterIP = ""
 19944  				oldSvc.Spec.ClusterIPs = nil
 19945  
 19946  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19947  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19948  			},
 19949  			numErrs: 0,
 19950  		}, {
 19951  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer",
 19952  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19953  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19954  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19955  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19956  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19957  
 19958  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19959  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19960  
 19961  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19962  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19963  			},
 19964  			numErrs: 1,
 19965  		}, {
 19966  			name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer",
 19967  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19968  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19969  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19970  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19971  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19972  
 19973  				oldSvc.Spec.ClusterIP = ""
 19974  				oldSvc.Spec.ClusterIPs = nil
 19975  
 19976  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19977  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19978  			},
 19979  			numErrs: 0,
 19980  		}, {
 19981  			name: "Service with LoadBalancer type cannot change its set ClusterIP",
 19982  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19983  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19984  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19985  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19986  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19987  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19988  
 19989  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19990  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19991  
 19992  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19993  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19994  			},
 19995  			numErrs: 1,
 19996  		}, {
 19997  			name: "Service with LoadBalancer type can change its empty ClusterIP",
 19998  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19999  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20000  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20001  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20002  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20003  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20004  
 20005  				oldSvc.Spec.ClusterIP = ""
 20006  				oldSvc.Spec.ClusterIPs = nil
 20007  
 20008  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20009  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20010  			},
 20011  			numErrs: 0,
 20012  		}, {
 20013  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP",
 20014  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20015  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20016  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20017  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20018  
 20019  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20020  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20021  
 20022  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20023  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20024  			},
 20025  			numErrs: 1,
 20026  		}, {
 20027  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP",
 20028  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20029  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20030  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20031  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20032  
 20033  				oldSvc.Spec.ClusterIP = ""
 20034  				oldSvc.Spec.ClusterIPs = nil
 20035  
 20036  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20037  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20038  			},
 20039  			numErrs: 0,
 20040  		}, {
 20041  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort",
 20042  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20043  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20044  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20045  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20046  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20047  
 20048  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20049  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20050  
 20051  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20052  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20053  			},
 20054  			numErrs: 1,
 20055  		}, {
 20056  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort",
 20057  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20058  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20059  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20060  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20061  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20062  
 20063  				oldSvc.Spec.ClusterIP = ""
 20064  				oldSvc.Spec.ClusterIPs = nil
 20065  
 20066  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20067  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20068  			},
 20069  			numErrs: 0,
 20070  		}, {
 20071  			name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP",
 20072  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20073  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20074  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20075  
 20076  				oldSvc.Spec.ClusterIP = ""
 20077  				oldSvc.Spec.ClusterIPs = nil
 20078  
 20079  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20080  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20081  			},
 20082  			numErrs: 0,
 20083  		}, {
 20084  			name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP",
 20085  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20086  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20087  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20088  
 20089  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20090  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20091  
 20092  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20093  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20094  			},
 20095  			numErrs: 0,
 20096  		}, {
 20097  			name: "invalid node port with clusterIP None",
 20098  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20099  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 20100  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20101  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20102  
 20103  				oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 20104  				newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 20105  
 20106  				oldSvc.Spec.ClusterIP = ""
 20107  				oldSvc.Spec.ClusterIPs = nil
 20108  
 20109  				newSvc.Spec.ClusterIP = "None"
 20110  				newSvc.Spec.ClusterIPs = []string{"None"}
 20111  			},
 20112  			numErrs: 1,
 20113  		},
 20114  		/* Service IP Family */
 20115  		{
 20116  			name: "convert from ExternalName",
 20117  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20118  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20119  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20120  			},
 20121  			numErrs: 0,
 20122  		}, {
 20123  			name: "invalid: convert to ExternalName",
 20124  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20125  				singleStack := core.IPFamilyPolicySingleStack
 20126  
 20127  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20128  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 20129  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20130  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20131  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20132  
 20133  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20134  				newSvc.Spec.ExternalName = "foo"
 20135  				/*
 20136  					not removing these fields is a validation error
 20137  					strategy takes care of resetting Families & Policy if ClusterIPs
 20138  					were reset. But it does not get called in validation testing.
 20139  				*/
 20140  				newSvc.Spec.ClusterIP = "10.0.0.10"
 20141  				newSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20142  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20143  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20144  
 20145  			},
 20146  			numErrs: 3,
 20147  		}, {
 20148  			name: "valid: convert to ExternalName",
 20149  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20150  				singleStack := core.IPFamilyPolicySingleStack
 20151  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20152  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 20153  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 20154  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20155  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20156  
 20157  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20158  				newSvc.Spec.ExternalName = "foo"
 20159  			},
 20160  			numErrs: 0,
 20161  		},
 20162  
 20163  		{
 20164  			name: "same ServiceIPFamily",
 20165  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20166  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20167  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20168  
 20169  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20170  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20171  			},
 20172  			numErrs: 0,
 20173  		}, {
 20174  			name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack",
 20175  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20176  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20177  				oldSvc.Spec.IPFamilyPolicy = nil
 20178  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20179  
 20180  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20181  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20182  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20183  			},
 20184  			numErrs: 0,
 20185  		}, {
 20186  			name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack",
 20187  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20188  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20189  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20190  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20191  
 20192  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20193  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20194  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20195  			},
 20196  			numErrs: 0,
 20197  		},
 20198  
 20199  		{
 20200  			name: "add a new ServiceIPFamily",
 20201  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20202  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20203  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20204  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20205  
 20206  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20207  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20208  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20209  			},
 20210  			numErrs: 0,
 20211  		},
 20212  
 20213  		{
 20214  			name: "ExternalName while changing Service IPFamily",
 20215  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20216  				oldSvc.Spec.ExternalName = "somename"
 20217  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20218  
 20219  				newSvc.Spec.ExternalName = "somename"
 20220  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20221  			},
 20222  			numErrs: 0,
 20223  		}, {
 20224  			name: "setting ipfamily from nil to v4",
 20225  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20226  				oldSvc.Spec.IPFamilies = nil
 20227  
 20228  				newSvc.Spec.ExternalName = "somename"
 20229  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20230  			},
 20231  			numErrs: 0,
 20232  		}, {
 20233  			name: "setting ipfamily from nil to v6",
 20234  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20235  				oldSvc.Spec.IPFamilies = nil
 20236  
 20237  				newSvc.Spec.ExternalName = "somename"
 20238  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20239  			},
 20240  			numErrs: 0,
 20241  		}, {
 20242  			name: "change primary ServiceIPFamily",
 20243  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20244  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20245  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20246  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20247  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20248  
 20249  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20250  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20251  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20252  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20253  			},
 20254  			numErrs: 2,
 20255  		},
 20256  		/* upgrade + downgrade from/to dualstack tests */
 20257  		{
 20258  			name: "valid: upgrade to dual stack with requiredDualStack",
 20259  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20260  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20261  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20262  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20263  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20264  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20265  
 20266  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20267  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20268  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20269  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20270  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20271  			},
 20272  			numErrs: 0,
 20273  		}, {
 20274  			name: "valid: upgrade to dual stack with preferDualStack",
 20275  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20276  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20277  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20278  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20279  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20280  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20281  
 20282  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20283  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20284  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20285  				newSvc.Spec.IPFamilyPolicy = &preferDualStack
 20286  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20287  			},
 20288  			numErrs: 0,
 20289  		},
 20290  
 20291  		{
 20292  			name: "valid: upgrade to dual stack, no specific secondary ip",
 20293  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20294  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20295  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20296  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20297  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20298  
 20299  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20300  
 20301  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20302  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20303  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20304  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20305  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20306  			},
 20307  			numErrs: 0,
 20308  		}, {
 20309  			name: "valid: upgrade to dual stack, with specific secondary ip",
 20310  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20311  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20312  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20313  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20314  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20315  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20316  
 20317  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20318  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20319  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20320  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20321  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20322  			},
 20323  			numErrs: 0,
 20324  		}, {
 20325  			name: "valid: downgrade from dual to single",
 20326  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20327  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20328  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20329  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20330  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20331  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20332  
 20333  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20334  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20335  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20336  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20337  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20338  			},
 20339  			numErrs: 0,
 20340  		}, {
 20341  			name: "valid: change families for a headless service",
 20342  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20343  				oldSvc.Spec.ClusterIP = "None"
 20344  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20345  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20346  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20347  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20348  
 20349  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20350  				newSvc.Spec.ClusterIP = "None"
 20351  				newSvc.Spec.ClusterIPs = []string{"None"}
 20352  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20353  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20354  			},
 20355  			numErrs: 0,
 20356  		}, {
 20357  			name: "valid: upgrade a headless service",
 20358  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20359  				oldSvc.Spec.ClusterIP = "None"
 20360  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20361  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20362  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20363  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20364  
 20365  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20366  				newSvc.Spec.ClusterIP = "None"
 20367  				newSvc.Spec.ClusterIPs = []string{"None"}
 20368  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20369  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20370  			},
 20371  			numErrs: 0,
 20372  		}, {
 20373  			name: "valid: downgrade a headless service",
 20374  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20375  				oldSvc.Spec.ClusterIP = "None"
 20376  				oldSvc.Spec.ClusterIPs = []string{"None"}
 20377  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20378  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20379  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20380  
 20381  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20382  				newSvc.Spec.ClusterIP = "None"
 20383  				newSvc.Spec.ClusterIPs = []string{"None"}
 20384  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20385  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 20386  			},
 20387  			numErrs: 0,
 20388  		},
 20389  
 20390  		{
 20391  			name: "invalid flip families",
 20392  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20393  				oldSvc.Spec.ClusterIP = "1.2.3.40"
 20394  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20395  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20396  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20397  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20398  
 20399  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20400  				newSvc.Spec.ClusterIP = "2001::1"
 20401  				newSvc.Spec.ClusterIPs = []string{"2001::1", "1.2.3.5"}
 20402  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20403  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 20404  			},
 20405  			numErrs: 4,
 20406  		}, {
 20407  			name: "invalid change first ip, in dualstack service",
 20408  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20409  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20410  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20411  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20412  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20413  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20414  
 20415  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20416  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20417  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5", "2001::1"}
 20418  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20419  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20420  			},
 20421  			numErrs: 1,
 20422  		}, {
 20423  			name: "invalid, change second ip in dualstack service",
 20424  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20425  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20426  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20427  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20428  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20429  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20430  
 20431  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20432  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20433  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2002::1"}
 20434  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20435  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20436  			},
 20437  			numErrs: 1,
 20438  		}, {
 20439  			name: "downgrade keeping the families",
 20440  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20441  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20442  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20443  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20444  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20445  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20446  
 20447  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20448  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20449  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20450  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20451  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20452  			},
 20453  			numErrs: 0, // families and ips are trimmed in strategy
 20454  		}, {
 20455  			name: "invalid, downgrade without changing to singleStack",
 20456  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20457  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20458  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20459  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20460  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20461  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20462  
 20463  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20464  				newSvc.Spec.ClusterIP = "1.2.3.4"
 20465  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20466  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20467  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20468  			},
 20469  			numErrs: 2,
 20470  		}, {
 20471  			name: "invalid, downgrade and change primary ip",
 20472  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20473  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20474  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 20475  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20476  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 20477  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20478  
 20479  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20480  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20481  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20482  				newSvc.Spec.IPFamilyPolicy = &singleStack
 20483  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20484  			},
 20485  			numErrs: 1,
 20486  		}, {
 20487  			name: "invalid: upgrade to dual stack and change primary",
 20488  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20489  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 20490  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 20491  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20492  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 20493  
 20494  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 20495  
 20496  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20497  				newSvc.Spec.ClusterIP = "1.2.3.5"
 20498  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 20499  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 20500  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 20501  			},
 20502  			numErrs: 1,
 20503  		}, {
 20504  			name: "update to valid app protocol",
 20505  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20506  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 20507  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 20508  			},
 20509  			numErrs: 0,
 20510  		}, {
 20511  			name: "update to invalid app protocol",
 20512  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20513  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 20514  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 20515  			},
 20516  			numErrs: 1,
 20517  		}, {
 20518  			name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 20519  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20520  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20521  			},
 20522  			numErrs: 1,
 20523  		}, {
 20524  			name: "update LoadBalancer type of service without change LoadBalancerClass",
 20525  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20526  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20527  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20528  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20529  
 20530  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20531  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20532  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20533  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20534  			},
 20535  			numErrs: 0,
 20536  		}, {
 20537  			name: "invalid: change LoadBalancerClass when update service",
 20538  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20539  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20540  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20541  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20542  
 20543  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20544  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20545  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20546  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 20547  			},
 20548  			numErrs: 1,
 20549  		}, {
 20550  			name: "invalid: unset LoadBalancerClass when update service",
 20551  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20552  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20553  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20554  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 20555  
 20556  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20557  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20558  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20559  				newSvc.Spec.LoadBalancerClass = nil
 20560  			},
 20561  			numErrs: 1,
 20562  		}, {
 20563  			name: "invalid: set LoadBalancerClass when update service",
 20564  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20565  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20566  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20567  				oldSvc.Spec.LoadBalancerClass = nil
 20568  
 20569  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20570  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20571  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20572  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 20573  			},
 20574  			numErrs: 1,
 20575  		}, {
 20576  			name: "update to LoadBalancer type of service with valid LoadBalancerClass",
 20577  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20578  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20579  
 20580  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20581  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20582  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20583  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20584  			},
 20585  			numErrs: 0,
 20586  		}, {
 20587  			name: "update to LoadBalancer type of service without LoadBalancerClass",
 20588  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20589  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20590  
 20591  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20592  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20593  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20594  				newSvc.Spec.LoadBalancerClass = nil
 20595  			},
 20596  			numErrs: 0,
 20597  		}, {
 20598  			name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer",
 20599  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20600  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20601  
 20602  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20603  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20604  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20605  				newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass")
 20606  			},
 20607  			numErrs: 2,
 20608  		}, {
 20609  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20610  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20611  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 20612  
 20613  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20614  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20615  			},
 20616  			numErrs: 2,
 20617  		}, {
 20618  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20619  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20620  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 20621  
 20622  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20623  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20624  			},
 20625  			numErrs: 3,
 20626  		}, {
 20627  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 20628  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20629  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 20630  
 20631  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20632  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20633  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20634  			},
 20635  			numErrs: 2,
 20636  		}, {
 20637  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20638  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20639  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20640  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20641  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20642  
 20643  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 20644  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20645  			},
 20646  			numErrs: 2,
 20647  		}, {
 20648  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20649  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20650  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20651  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20652  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20653  
 20654  				newSvc.Spec.Type = core.ServiceTypeExternalName
 20655  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20656  			},
 20657  			numErrs: 3,
 20658  		}, {
 20659  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 20660  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20661  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 20662  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 20663  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20664  
 20665  				newSvc.Spec.Type = core.ServiceTypeNodePort
 20666  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 20667  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 20668  			},
 20669  			numErrs: 2,
 20670  		}, {
 20671  			name: "update internalTrafficPolicy from Cluster to Local",
 20672  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20673  				cluster := core.ServiceInternalTrafficPolicyCluster
 20674  				oldSvc.Spec.InternalTrafficPolicy = &cluster
 20675  
 20676  				local := core.ServiceInternalTrafficPolicyLocal
 20677  				newSvc.Spec.InternalTrafficPolicy = &local
 20678  			},
 20679  			numErrs: 0,
 20680  		}, {
 20681  			name: "update internalTrafficPolicy from Local to Cluster",
 20682  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20683  				local := core.ServiceInternalTrafficPolicyLocal
 20684  				oldSvc.Spec.InternalTrafficPolicy = &local
 20685  
 20686  				cluster := core.ServiceInternalTrafficPolicyCluster
 20687  				newSvc.Spec.InternalTrafficPolicy = &cluster
 20688  			},
 20689  			numErrs: 0,
 20690  		}, {
 20691  			name: "topology annotations are mismatched",
 20692  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 20693  				newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 20694  				newSvc.Annotations[core.AnnotationTopologyMode] = "different"
 20695  			},
 20696  			numErrs: 1,
 20697  		},
 20698  	}
 20699  
 20700  	for _, tc := range testCases {
 20701  		t.Run(tc.name, func(t *testing.T) {
 20702  			oldSvc := makeValidService()
 20703  			newSvc := makeValidService()
 20704  			tc.tweakSvc(&oldSvc, &newSvc)
 20705  			errs := ValidateServiceUpdate(&newSvc, &oldSvc)
 20706  			if len(errs) != tc.numErrs {
 20707  				t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate())
 20708  			}
 20709  		})
 20710  	}
 20711  }
 20712  
 20713  func TestValidateResourceNames(t *testing.T) {
 20714  	table := []struct {
 20715  		input   core.ResourceName
 20716  		success bool
 20717  		expect  string
 20718  	}{
 20719  		{"memory", true, ""},
 20720  		{"cpu", true, ""},
 20721  		{"storage", true, ""},
 20722  		{"requests.cpu", true, ""},
 20723  		{"requests.memory", true, ""},
 20724  		{"requests.storage", true, ""},
 20725  		{"limits.cpu", true, ""},
 20726  		{"limits.memory", true, ""},
 20727  		{"network", false, ""},
 20728  		{"disk", false, ""},
 20729  		{"", false, ""},
 20730  		{".", false, ""},
 20731  		{"..", false, ""},
 20732  		{"my.favorite.app.co/12345", true, ""},
 20733  		{"my.favorite.app.co/_12345", false, ""},
 20734  		{"my.favorite.app.co/12345_", false, ""},
 20735  		{"kubernetes.io/..", false, ""},
 20736  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)), true, ""},
 20737  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 64)), false, ""},
 20738  		{"kubernetes.io//", false, ""},
 20739  		{"kubernetes.io", false, ""},
 20740  		{"kubernetes.io/will/not/work/", false, ""},
 20741  	}
 20742  	for k, item := range table {
 20743  		err := validateResourceName(item.input, field.NewPath("field"))
 20744  		if len(err) != 0 && item.success {
 20745  			t.Errorf("expected no failure for input %q", item.input)
 20746  		} else if len(err) == 0 && !item.success {
 20747  			t.Errorf("expected failure for input %q", item.input)
 20748  			for i := range err {
 20749  				detail := err[i].Detail
 20750  				if detail != "" && !strings.Contains(detail, item.expect) {
 20751  					t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail)
 20752  				}
 20753  			}
 20754  		}
 20755  	}
 20756  }
 20757  
 20758  func getResourceList(cpu, memory string) core.ResourceList {
 20759  	res := core.ResourceList{}
 20760  	if cpu != "" {
 20761  		res[core.ResourceCPU] = resource.MustParse(cpu)
 20762  	}
 20763  	if memory != "" {
 20764  		res[core.ResourceMemory] = resource.MustParse(memory)
 20765  	}
 20766  	return res
 20767  }
 20768  
 20769  func getStorageResourceList(storage string) core.ResourceList {
 20770  	res := core.ResourceList{}
 20771  	if storage != "" {
 20772  		res[core.ResourceStorage] = resource.MustParse(storage)
 20773  	}
 20774  	return res
 20775  }
 20776  
 20777  func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList {
 20778  	res := core.ResourceList{}
 20779  	if ephemeralStorage != "" {
 20780  		res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
 20781  	}
 20782  	return res
 20783  }
 20784  
 20785  func TestValidateLimitRangeForLocalStorage(t *testing.T) {
 20786  	testCases := []struct {
 20787  		name string
 20788  		spec core.LimitRangeSpec
 20789  	}{{
 20790  		name: "all-fields-valid",
 20791  		spec: core.LimitRangeSpec{
 20792  			Limits: []core.LimitRangeItem{{
 20793  				Type:                 core.LimitTypePod,
 20794  				Max:                  getLocalStorageResourceList("10000Mi"),
 20795  				Min:                  getLocalStorageResourceList("100Mi"),
 20796  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 20797  			}, {
 20798  				Type:                 core.LimitTypeContainer,
 20799  				Max:                  getLocalStorageResourceList("10000Mi"),
 20800  				Min:                  getLocalStorageResourceList("100Mi"),
 20801  				Default:              getLocalStorageResourceList("500Mi"),
 20802  				DefaultRequest:       getLocalStorageResourceList("200Mi"),
 20803  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 20804  			}},
 20805  		},
 20806  	},
 20807  	}
 20808  
 20809  	for _, testCase := range testCases {
 20810  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec}
 20811  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 20812  			t.Errorf("Case %v, unexpected error: %v", testCase.name, errs)
 20813  		}
 20814  	}
 20815  }
 20816  
 20817  func TestValidateLimitRange(t *testing.T) {
 20818  	successCases := []struct {
 20819  		name string
 20820  		spec core.LimitRangeSpec
 20821  	}{{
 20822  		name: "all-fields-valid",
 20823  		spec: core.LimitRangeSpec{
 20824  			Limits: []core.LimitRangeItem{{
 20825  				Type:                 core.LimitTypePod,
 20826  				Max:                  getResourceList("100m", "10000Mi"),
 20827  				Min:                  getResourceList("5m", "100Mi"),
 20828  				MaxLimitRequestRatio: getResourceList("10", ""),
 20829  			}, {
 20830  				Type:                 core.LimitTypeContainer,
 20831  				Max:                  getResourceList("100m", "10000Mi"),
 20832  				Min:                  getResourceList("5m", "100Mi"),
 20833  				Default:              getResourceList("50m", "500Mi"),
 20834  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20835  				MaxLimitRequestRatio: getResourceList("10", ""),
 20836  			}, {
 20837  				Type: core.LimitTypePersistentVolumeClaim,
 20838  				Max:  getStorageResourceList("10Gi"),
 20839  				Min:  getStorageResourceList("5Gi"),
 20840  			}},
 20841  		},
 20842  	}, {
 20843  		name: "pvc-min-only",
 20844  		spec: core.LimitRangeSpec{
 20845  			Limits: []core.LimitRangeItem{{
 20846  				Type: core.LimitTypePersistentVolumeClaim,
 20847  				Min:  getStorageResourceList("5Gi"),
 20848  			}},
 20849  		},
 20850  	}, {
 20851  		name: "pvc-max-only",
 20852  		spec: core.LimitRangeSpec{
 20853  			Limits: []core.LimitRangeItem{{
 20854  				Type: core.LimitTypePersistentVolumeClaim,
 20855  				Max:  getStorageResourceList("10Gi"),
 20856  			}},
 20857  		},
 20858  	}, {
 20859  		name: "all-fields-valid-big-numbers",
 20860  		spec: core.LimitRangeSpec{
 20861  			Limits: []core.LimitRangeItem{{
 20862  				Type:                 core.LimitTypeContainer,
 20863  				Max:                  getResourceList("100m", "10000T"),
 20864  				Min:                  getResourceList("5m", "100Mi"),
 20865  				Default:              getResourceList("50m", "500Mi"),
 20866  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20867  				MaxLimitRequestRatio: getResourceList("10", ""),
 20868  			}},
 20869  		},
 20870  	}, {
 20871  		name: "thirdparty-fields-all-valid-standard-container-resources",
 20872  		spec: core.LimitRangeSpec{
 20873  			Limits: []core.LimitRangeItem{{
 20874  				Type:                 "thirdparty.com/foo",
 20875  				Max:                  getResourceList("100m", "10000T"),
 20876  				Min:                  getResourceList("5m", "100Mi"),
 20877  				Default:              getResourceList("50m", "500Mi"),
 20878  				DefaultRequest:       getResourceList("10m", "200Mi"),
 20879  				MaxLimitRequestRatio: getResourceList("10", ""),
 20880  			}},
 20881  		},
 20882  	}, {
 20883  		name: "thirdparty-fields-all-valid-storage-resources",
 20884  		spec: core.LimitRangeSpec{
 20885  			Limits: []core.LimitRangeItem{{
 20886  				Type:                 "thirdparty.com/foo",
 20887  				Max:                  getStorageResourceList("10000T"),
 20888  				Min:                  getStorageResourceList("100Mi"),
 20889  				Default:              getStorageResourceList("500Mi"),
 20890  				DefaultRequest:       getStorageResourceList("200Mi"),
 20891  				MaxLimitRequestRatio: getStorageResourceList(""),
 20892  			}},
 20893  		},
 20894  	},
 20895  	}
 20896  
 20897  	for _, successCase := range successCases {
 20898  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec}
 20899  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 20900  			t.Errorf("Case %v, unexpected error: %v", successCase.name, errs)
 20901  		}
 20902  	}
 20903  
 20904  	errorCases := map[string]struct {
 20905  		R core.LimitRange
 20906  		D string
 20907  	}{
 20908  		"zero-length-name": {
 20909  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 20910  			"name or generateName is required",
 20911  		},
 20912  		"zero-length-namespace": {
 20913  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}},
 20914  			"",
 20915  		},
 20916  		"invalid-name": {
 20917  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 20918  			dnsSubdomainLabelErrMsg,
 20919  		},
 20920  		"invalid-namespace": {
 20921  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}},
 20922  			dnsLabelErrMsg,
 20923  		},
 20924  		"duplicate-limit-type": {
 20925  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20926  				Limits: []core.LimitRangeItem{{
 20927  					Type: core.LimitTypePod,
 20928  					Max:  getResourceList("100m", "10000m"),
 20929  					Min:  getResourceList("0m", "100m"),
 20930  				}, {
 20931  					Type: core.LimitTypePod,
 20932  					Min:  getResourceList("0m", "100m"),
 20933  				}},
 20934  			}},
 20935  			"",
 20936  		},
 20937  		"default-limit-type-pod": {
 20938  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20939  				Limits: []core.LimitRangeItem{{
 20940  					Type:    core.LimitTypePod,
 20941  					Max:     getResourceList("100m", "10000m"),
 20942  					Min:     getResourceList("0m", "100m"),
 20943  					Default: getResourceList("10m", "100m"),
 20944  				}},
 20945  			}},
 20946  			"may not be specified when `type` is 'Pod'",
 20947  		},
 20948  		"default-request-limit-type-pod": {
 20949  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20950  				Limits: []core.LimitRangeItem{{
 20951  					Type:           core.LimitTypePod,
 20952  					Max:            getResourceList("100m", "10000m"),
 20953  					Min:            getResourceList("0m", "100m"),
 20954  					DefaultRequest: getResourceList("10m", "100m"),
 20955  				}},
 20956  			}},
 20957  			"may not be specified when `type` is 'Pod'",
 20958  		},
 20959  		"min value 100m is greater than max value 10m": {
 20960  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20961  				Limits: []core.LimitRangeItem{{
 20962  					Type: core.LimitTypePod,
 20963  					Max:  getResourceList("10m", ""),
 20964  					Min:  getResourceList("100m", ""),
 20965  				}},
 20966  			}},
 20967  			"min value 100m is greater than max value 10m",
 20968  		},
 20969  		"invalid spec default outside range": {
 20970  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20971  				Limits: []core.LimitRangeItem{{
 20972  					Type:    core.LimitTypeContainer,
 20973  					Max:     getResourceList("1", ""),
 20974  					Min:     getResourceList("100m", ""),
 20975  					Default: getResourceList("2000m", ""),
 20976  				}},
 20977  			}},
 20978  			"default value 2 is greater than max value 1",
 20979  		},
 20980  		"invalid spec default request outside range": {
 20981  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20982  				Limits: []core.LimitRangeItem{{
 20983  					Type:           core.LimitTypeContainer,
 20984  					Max:            getResourceList("1", ""),
 20985  					Min:            getResourceList("100m", ""),
 20986  					DefaultRequest: getResourceList("2000m", ""),
 20987  				}},
 20988  			}},
 20989  			"default request value 2 is greater than max value 1",
 20990  		},
 20991  		"invalid spec default request more than default": {
 20992  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20993  				Limits: []core.LimitRangeItem{{
 20994  					Type:           core.LimitTypeContainer,
 20995  					Max:            getResourceList("2", ""),
 20996  					Min:            getResourceList("100m", ""),
 20997  					Default:        getResourceList("500m", ""),
 20998  					DefaultRequest: getResourceList("800m", ""),
 20999  				}},
 21000  			}},
 21001  			"default request value 800m is greater than default limit value 500m",
 21002  		},
 21003  		"invalid spec maxLimitRequestRatio less than 1": {
 21004  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 21005  				Limits: []core.LimitRangeItem{{
 21006  					Type:                 core.LimitTypePod,
 21007  					MaxLimitRequestRatio: getResourceList("800m", ""),
 21008  				}},
 21009  			}},
 21010  			"ratio 800m is less than 1",
 21011  		},
 21012  		"invalid spec maxLimitRequestRatio greater than max/min": {
 21013  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 21014  				Limits: []core.LimitRangeItem{{
 21015  					Type:                 core.LimitTypeContainer,
 21016  					Max:                  getResourceList("", "2Gi"),
 21017  					Min:                  getResourceList("", "512Mi"),
 21018  					MaxLimitRequestRatio: getResourceList("", "10"),
 21019  				}},
 21020  			}},
 21021  			"ratio 10 is greater than max/min = 4.000000",
 21022  		},
 21023  		"invalid non standard limit type": {
 21024  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 21025  				Limits: []core.LimitRangeItem{{
 21026  					Type:                 "foo",
 21027  					Max:                  getStorageResourceList("10000T"),
 21028  					Min:                  getStorageResourceList("100Mi"),
 21029  					Default:              getStorageResourceList("500Mi"),
 21030  					DefaultRequest:       getStorageResourceList("200Mi"),
 21031  					MaxLimitRequestRatio: getStorageResourceList(""),
 21032  				}},
 21033  			}},
 21034  			"must be a standard limit type or fully qualified",
 21035  		},
 21036  		"min and max values missing, one required": {
 21037  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 21038  				Limits: []core.LimitRangeItem{{
 21039  					Type: core.LimitTypePersistentVolumeClaim,
 21040  				}},
 21041  			}},
 21042  			"either minimum or maximum storage value is required, but neither was provided",
 21043  		},
 21044  		"invalid min greater than max": {
 21045  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 21046  				Limits: []core.LimitRangeItem{{
 21047  					Type: core.LimitTypePersistentVolumeClaim,
 21048  					Min:  getStorageResourceList("10Gi"),
 21049  					Max:  getStorageResourceList("1Gi"),
 21050  				}},
 21051  			}},
 21052  			"min value 10Gi is greater than max value 1Gi",
 21053  		},
 21054  	}
 21055  
 21056  	for k, v := range errorCases {
 21057  		errs := ValidateLimitRange(&v.R)
 21058  		if len(errs) == 0 {
 21059  			t.Errorf("expected failure for %s", k)
 21060  		}
 21061  		for i := range errs {
 21062  			detail := errs[i].Detail
 21063  			if !strings.Contains(detail, v.D) {
 21064  				t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
 21065  			}
 21066  		}
 21067  	}
 21068  
 21069  }
 21070  
 21071  func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
 21072  	validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
 21073  		AccessModes: []core.PersistentVolumeAccessMode{
 21074  			core.ReadWriteOnce,
 21075  			core.ReadOnlyMany,
 21076  		},
 21077  		Resources: core.VolumeResourceRequirements{
 21078  			Requests: core.ResourceList{
 21079  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21080  			},
 21081  		},
 21082  	})
 21083  	validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21084  		AccessModes: []core.PersistentVolumeAccessMode{
 21085  			core.ReadWriteOnce,
 21086  			core.ReadOnlyMany,
 21087  		},
 21088  		Resources: core.VolumeResourceRequirements{
 21089  			Requests: core.ResourceList{
 21090  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21091  			},
 21092  		},
 21093  	}, core.PersistentVolumeClaimStatus{
 21094  		Phase: core.ClaimPending,
 21095  		Conditions: []core.PersistentVolumeClaimCondition{
 21096  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21097  		},
 21098  	})
 21099  	validAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21100  		AccessModes: []core.PersistentVolumeAccessMode{
 21101  			core.ReadWriteOnce,
 21102  			core.ReadOnlyMany,
 21103  		},
 21104  		Resources: core.VolumeResourceRequirements{
 21105  			Requests: core.ResourceList{
 21106  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21107  			},
 21108  		},
 21109  	}, core.PersistentVolumeClaimStatus{
 21110  		Phase: core.ClaimPending,
 21111  		Conditions: []core.PersistentVolumeClaimCondition{
 21112  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21113  		},
 21114  		AllocatedResources: core.ResourceList{
 21115  			core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21116  		},
 21117  	})
 21118  
 21119  	invalidAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21120  		AccessModes: []core.PersistentVolumeAccessMode{
 21121  			core.ReadWriteOnce,
 21122  			core.ReadOnlyMany,
 21123  		},
 21124  		Resources: core.VolumeResourceRequirements{
 21125  			Requests: core.ResourceList{
 21126  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21127  			},
 21128  		},
 21129  	}, core.PersistentVolumeClaimStatus{
 21130  		Phase: core.ClaimPending,
 21131  		Conditions: []core.PersistentVolumeClaimCondition{
 21132  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21133  		},
 21134  		AllocatedResources: core.ResourceList{
 21135  			core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
 21136  		},
 21137  	})
 21138  
 21139  	noStoraegeClaimStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21140  		AccessModes: []core.PersistentVolumeAccessMode{
 21141  			core.ReadWriteOnce,
 21142  		},
 21143  		Resources: core.VolumeResourceRequirements{
 21144  			Requests: core.ResourceList{
 21145  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21146  			},
 21147  		},
 21148  	}, core.PersistentVolumeClaimStatus{
 21149  		Phase: core.ClaimPending,
 21150  		AllocatedResources: core.ResourceList{
 21151  			core.ResourceName(core.ResourceCPU): resource.MustParse("10G"),
 21152  		},
 21153  	})
 21154  	progressResizeStatus := core.PersistentVolumeClaimControllerResizeInProgress
 21155  
 21156  	invalidResizeStatus := core.ClaimResourceStatus("foo")
 21157  	validResizeKeyCustom := core.ResourceName("example.com/foo")
 21158  	invalidNativeResizeKey := core.ResourceName("kubernetes.io/foo")
 21159  
 21160  	validResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21161  		AccessModes: []core.PersistentVolumeAccessMode{
 21162  			core.ReadWriteOnce,
 21163  		},
 21164  	}, core.PersistentVolumeClaimStatus{
 21165  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21166  			core.ResourceStorage: progressResizeStatus,
 21167  		},
 21168  	})
 21169  
 21170  	validResizeStatusControllerResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21171  		AccessModes: []core.PersistentVolumeAccessMode{
 21172  			core.ReadWriteOnce,
 21173  		},
 21174  	}, core.PersistentVolumeClaimStatus{
 21175  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21176  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 21177  		},
 21178  	})
 21179  
 21180  	validNodeResizePending := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21181  		AccessModes: []core.PersistentVolumeAccessMode{
 21182  			core.ReadWriteOnce,
 21183  		},
 21184  	}, core.PersistentVolumeClaimStatus{
 21185  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21186  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizePending,
 21187  		},
 21188  	})
 21189  
 21190  	validNodeResizeInProgress := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21191  		AccessModes: []core.PersistentVolumeAccessMode{
 21192  			core.ReadWriteOnce,
 21193  		},
 21194  	}, core.PersistentVolumeClaimStatus{
 21195  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21196  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInProgress,
 21197  		},
 21198  	})
 21199  
 21200  	validNodeResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21201  		AccessModes: []core.PersistentVolumeAccessMode{
 21202  			core.ReadWriteOnce,
 21203  		},
 21204  	}, core.PersistentVolumeClaimStatus{
 21205  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21206  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed,
 21207  		},
 21208  	})
 21209  
 21210  	invalidResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21211  		AccessModes: []core.PersistentVolumeAccessMode{
 21212  			core.ReadWriteOnce,
 21213  		},
 21214  	}, core.PersistentVolumeClaimStatus{
 21215  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21216  			core.ResourceStorage: invalidResizeStatus,
 21217  		},
 21218  	})
 21219  
 21220  	invalidNativeResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21221  		AccessModes: []core.PersistentVolumeAccessMode{
 21222  			core.ReadWriteOnce,
 21223  		},
 21224  	}, core.PersistentVolumeClaimStatus{
 21225  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21226  			invalidNativeResizeKey: core.PersistentVolumeClaimNodeResizePending,
 21227  		},
 21228  	})
 21229  
 21230  	validExternalResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21231  		AccessModes: []core.PersistentVolumeAccessMode{
 21232  			core.ReadWriteOnce,
 21233  		},
 21234  	}, core.PersistentVolumeClaimStatus{
 21235  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21236  			validResizeKeyCustom: core.PersistentVolumeClaimNodeResizePending,
 21237  		},
 21238  	})
 21239  
 21240  	multipleResourceStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21241  		AccessModes: []core.PersistentVolumeAccessMode{
 21242  			core.ReadWriteOnce,
 21243  		},
 21244  	}, core.PersistentVolumeClaimStatus{
 21245  		AllocatedResources: core.ResourceList{
 21246  			core.ResourceStorage: resource.MustParse("5Gi"),
 21247  			validResizeKeyCustom: resource.MustParse("10Gi"),
 21248  		},
 21249  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 21250  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 21251  			validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
 21252  		},
 21253  	})
 21254  
 21255  	invalidNativeResourceAllocatedKey := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21256  		AccessModes: []core.PersistentVolumeAccessMode{
 21257  			core.ReadWriteOnce,
 21258  			core.ReadOnlyMany,
 21259  		},
 21260  		Resources: core.VolumeResourceRequirements{
 21261  			Requests: core.ResourceList{
 21262  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21263  			},
 21264  		},
 21265  	}, core.PersistentVolumeClaimStatus{
 21266  		Phase: core.ClaimPending,
 21267  		Conditions: []core.PersistentVolumeClaimCondition{
 21268  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21269  		},
 21270  		AllocatedResources: core.ResourceList{
 21271  			invalidNativeResizeKey: resource.MustParse("14G"),
 21272  		},
 21273  	})
 21274  
 21275  	validExternalAllocatedResource := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 21276  		AccessModes: []core.PersistentVolumeAccessMode{
 21277  			core.ReadWriteOnce,
 21278  			core.ReadOnlyMany,
 21279  		},
 21280  		Resources: core.VolumeResourceRequirements{
 21281  			Requests: core.ResourceList{
 21282  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 21283  			},
 21284  		},
 21285  	}, core.PersistentVolumeClaimStatus{
 21286  		Phase: core.ClaimPending,
 21287  		Conditions: []core.PersistentVolumeClaimCondition{
 21288  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 21289  		},
 21290  		AllocatedResources: core.ResourceList{
 21291  			validResizeKeyCustom: resource.MustParse("14G"),
 21292  		},
 21293  	})
 21294  
 21295  	scenarios := map[string]struct {
 21296  		isExpectedFailure          bool
 21297  		oldClaim                   *core.PersistentVolumeClaim
 21298  		newClaim                   *core.PersistentVolumeClaim
 21299  		enableRecoverFromExpansion bool
 21300  	}{
 21301  		"condition-update-with-enabled-feature-gate": {
 21302  			isExpectedFailure: false,
 21303  			oldClaim:          validClaim,
 21304  			newClaim:          validConditionUpdate,
 21305  		},
 21306  		"status-update-with-valid-allocatedResources-feature-enabled": {
 21307  			isExpectedFailure:          false,
 21308  			oldClaim:                   validClaim,
 21309  			newClaim:                   validAllocatedResources,
 21310  			enableRecoverFromExpansion: true,
 21311  		},
 21312  		"status-update-with-invalid-allocatedResources-native-key-feature-enabled": {
 21313  			isExpectedFailure:          true,
 21314  			oldClaim:                   validClaim,
 21315  			newClaim:                   invalidNativeResourceAllocatedKey,
 21316  			enableRecoverFromExpansion: true,
 21317  		},
 21318  		"status-update-with-valid-allocatedResources-external-key-feature-enabled": {
 21319  			isExpectedFailure:          false,
 21320  			oldClaim:                   validClaim,
 21321  			newClaim:                   validExternalAllocatedResource,
 21322  			enableRecoverFromExpansion: true,
 21323  		},
 21324  
 21325  		"status-update-with-invalid-allocatedResources-feature-enabled": {
 21326  			isExpectedFailure:          true,
 21327  			oldClaim:                   validClaim,
 21328  			newClaim:                   invalidAllocatedResources,
 21329  			enableRecoverFromExpansion: true,
 21330  		},
 21331  		"status-update-with-no-storage-update": {
 21332  			isExpectedFailure:          true,
 21333  			oldClaim:                   validClaim,
 21334  			newClaim:                   noStoraegeClaimStatus,
 21335  			enableRecoverFromExpansion: true,
 21336  		},
 21337  		"staus-update-with-controller-resize-failed": {
 21338  			isExpectedFailure:          false,
 21339  			oldClaim:                   validClaim,
 21340  			newClaim:                   validResizeStatusControllerResizeFailed,
 21341  			enableRecoverFromExpansion: true,
 21342  		},
 21343  		"staus-update-with-node-resize-pending": {
 21344  			isExpectedFailure:          false,
 21345  			oldClaim:                   validClaim,
 21346  			newClaim:                   validNodeResizePending,
 21347  			enableRecoverFromExpansion: true,
 21348  		},
 21349  		"staus-update-with-node-resize-inprogress": {
 21350  			isExpectedFailure:          false,
 21351  			oldClaim:                   validClaim,
 21352  			newClaim:                   validNodeResizeInProgress,
 21353  			enableRecoverFromExpansion: true,
 21354  		},
 21355  		"staus-update-with-node-resize-failed": {
 21356  			isExpectedFailure:          false,
 21357  			oldClaim:                   validClaim,
 21358  			newClaim:                   validNodeResizeFailed,
 21359  			enableRecoverFromExpansion: true,
 21360  		},
 21361  		"staus-update-with-invalid-native-resource-status-key": {
 21362  			isExpectedFailure:          true,
 21363  			oldClaim:                   validClaim,
 21364  			newClaim:                   invalidNativeResizeStatusPVC,
 21365  			enableRecoverFromExpansion: true,
 21366  		},
 21367  		"staus-update-with-valid-external-resource-status-key": {
 21368  			isExpectedFailure:          false,
 21369  			oldClaim:                   validClaim,
 21370  			newClaim:                   validExternalResizeStatusPVC,
 21371  			enableRecoverFromExpansion: true,
 21372  		},
 21373  		"status-update-with-multiple-resources-key": {
 21374  			isExpectedFailure:          false,
 21375  			oldClaim:                   validClaim,
 21376  			newClaim:                   multipleResourceStatusPVC,
 21377  			enableRecoverFromExpansion: true,
 21378  		},
 21379  		"status-update-with-valid-pvc-resize-status": {
 21380  			isExpectedFailure:          false,
 21381  			oldClaim:                   validClaim,
 21382  			newClaim:                   validResizeStatusPVC,
 21383  			enableRecoverFromExpansion: true,
 21384  		},
 21385  		"status-update-with-invalid-pvc-resize-status": {
 21386  			isExpectedFailure:          true,
 21387  			oldClaim:                   validClaim,
 21388  			newClaim:                   invalidResizeStatusPVC,
 21389  			enableRecoverFromExpansion: true,
 21390  		},
 21391  		"status-update-with-old-pvc-valid-resourcestatus-newpvc-invalid-recovery-disabled": {
 21392  			isExpectedFailure:          true,
 21393  			oldClaim:                   validResizeStatusPVC,
 21394  			newClaim:                   invalidResizeStatusPVC,
 21395  			enableRecoverFromExpansion: false,
 21396  		},
 21397  		"status-update-with-old-pvc-valid-allocatedResource-newpvc-invalid-recovery-disabled": {
 21398  			isExpectedFailure:          true,
 21399  			oldClaim:                   validExternalAllocatedResource,
 21400  			newClaim:                   invalidNativeResourceAllocatedKey,
 21401  			enableRecoverFromExpansion: false,
 21402  		},
 21403  	}
 21404  	for name, scenario := range scenarios {
 21405  		t.Run(name, func(t *testing.T) {
 21406  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
 21407  
 21408  			validateOpts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
 21409  
 21410  			// ensure we have a resource version specified for updates
 21411  			scenario.oldClaim.ResourceVersion = "1"
 21412  			scenario.newClaim.ResourceVersion = "1"
 21413  			errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim, validateOpts)
 21414  			if len(errs) == 0 && scenario.isExpectedFailure {
 21415  				t.Errorf("Unexpected success for scenario: %s", name)
 21416  			}
 21417  			if len(errs) > 0 && !scenario.isExpectedFailure {
 21418  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
 21419  			}
 21420  		})
 21421  	}
 21422  }
 21423  
 21424  func TestValidateResourceQuota(t *testing.T) {
 21425  	spec := core.ResourceQuotaSpec{
 21426  		Hard: core.ResourceList{
 21427  			core.ResourceCPU:                    resource.MustParse("100"),
 21428  			core.ResourceMemory:                 resource.MustParse("10000"),
 21429  			core.ResourceRequestsCPU:            resource.MustParse("100"),
 21430  			core.ResourceRequestsMemory:         resource.MustParse("10000"),
 21431  			core.ResourceLimitsCPU:              resource.MustParse("100"),
 21432  			core.ResourceLimitsMemory:           resource.MustParse("10000"),
 21433  			core.ResourcePods:                   resource.MustParse("10"),
 21434  			core.ResourceServices:               resource.MustParse("0"),
 21435  			core.ResourceReplicationControllers: resource.MustParse("10"),
 21436  			core.ResourceQuotas:                 resource.MustParse("10"),
 21437  			core.ResourceConfigMaps:             resource.MustParse("10"),
 21438  			core.ResourceSecrets:                resource.MustParse("10"),
 21439  		},
 21440  	}
 21441  
 21442  	terminatingSpec := core.ResourceQuotaSpec{
 21443  		Hard: core.ResourceList{
 21444  			core.ResourceCPU:       resource.MustParse("100"),
 21445  			core.ResourceLimitsCPU: resource.MustParse("200"),
 21446  		},
 21447  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating},
 21448  	}
 21449  
 21450  	nonTerminatingSpec := core.ResourceQuotaSpec{
 21451  		Hard: core.ResourceList{
 21452  			core.ResourceCPU: resource.MustParse("100"),
 21453  		},
 21454  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating},
 21455  	}
 21456  
 21457  	bestEffortSpec := core.ResourceQuotaSpec{
 21458  		Hard: core.ResourceList{
 21459  			core.ResourcePods: resource.MustParse("100"),
 21460  		},
 21461  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort},
 21462  	}
 21463  
 21464  	nonBestEffortSpec := core.ResourceQuotaSpec{
 21465  		Hard: core.ResourceList{
 21466  			core.ResourceCPU: resource.MustParse("100"),
 21467  		},
 21468  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
 21469  	}
 21470  
 21471  	crossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 21472  		Hard: core.ResourceList{
 21473  			core.ResourceCPU:       resource.MustParse("100"),
 21474  			core.ResourceLimitsCPU: resource.MustParse("200"),
 21475  		},
 21476  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeCrossNamespacePodAffinity},
 21477  	}
 21478  
 21479  	scopeSelectorSpec := core.ResourceQuotaSpec{
 21480  		ScopeSelector: &core.ScopeSelector{
 21481  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 21482  				ScopeName: core.ResourceQuotaScopePriorityClass,
 21483  				Operator:  core.ScopeSelectorOpIn,
 21484  				Values:    []string{"cluster-services"},
 21485  			}},
 21486  		},
 21487  	}
 21488  
 21489  	// storage is not yet supported as a quota tracked resource
 21490  	invalidQuotaResourceSpec := core.ResourceQuotaSpec{
 21491  		Hard: core.ResourceList{
 21492  			core.ResourceStorage: resource.MustParse("10"),
 21493  		},
 21494  	}
 21495  
 21496  	negativeSpec := core.ResourceQuotaSpec{
 21497  		Hard: core.ResourceList{
 21498  			core.ResourceCPU:                    resource.MustParse("-100"),
 21499  			core.ResourceMemory:                 resource.MustParse("-10000"),
 21500  			core.ResourcePods:                   resource.MustParse("-10"),
 21501  			core.ResourceServices:               resource.MustParse("-10"),
 21502  			core.ResourceReplicationControllers: resource.MustParse("-10"),
 21503  			core.ResourceQuotas:                 resource.MustParse("-10"),
 21504  			core.ResourceConfigMaps:             resource.MustParse("-10"),
 21505  			core.ResourceSecrets:                resource.MustParse("-10"),
 21506  		},
 21507  	}
 21508  
 21509  	fractionalComputeSpec := core.ResourceQuotaSpec{
 21510  		Hard: core.ResourceList{
 21511  			core.ResourceCPU: resource.MustParse("100m"),
 21512  		},
 21513  	}
 21514  
 21515  	fractionalPodSpec := core.ResourceQuotaSpec{
 21516  		Hard: core.ResourceList{
 21517  			core.ResourcePods:                   resource.MustParse(".1"),
 21518  			core.ResourceServices:               resource.MustParse(".5"),
 21519  			core.ResourceReplicationControllers: resource.MustParse("1.25"),
 21520  			core.ResourceQuotas:                 resource.MustParse("2.5"),
 21521  		},
 21522  	}
 21523  
 21524  	invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{
 21525  		Hard: core.ResourceList{
 21526  			core.ResourceCPU: resource.MustParse("100"),
 21527  		},
 21528  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating},
 21529  	}
 21530  
 21531  	invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{
 21532  		Hard: core.ResourceList{
 21533  			core.ResourcePods: resource.MustParse("100"),
 21534  		},
 21535  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort},
 21536  	}
 21537  
 21538  	invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 21539  		ScopeSelector: &core.ScopeSelector{
 21540  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 21541  				ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity,
 21542  				Operator:  core.ScopeSelectorOpIn,
 21543  				Values:    []string{"cluster-services"},
 21544  			}},
 21545  		},
 21546  	}
 21547  
 21548  	invalidScopeNameSpec := core.ResourceQuotaSpec{
 21549  		Hard: core.ResourceList{
 21550  			core.ResourceCPU: resource.MustParse("100"),
 21551  		},
 21552  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")},
 21553  	}
 21554  
 21555  	testCases := map[string]struct {
 21556  		rq        core.ResourceQuota
 21557  		errDetail string
 21558  		errField  string
 21559  	}{
 21560  		"no-scope": {
 21561  			rq: core.ResourceQuota{
 21562  				ObjectMeta: metav1.ObjectMeta{
 21563  					Name:      "abc",
 21564  					Namespace: "foo",
 21565  				},
 21566  				Spec: spec,
 21567  			},
 21568  		},
 21569  		"fractional-compute-spec": {
 21570  			rq: core.ResourceQuota{
 21571  				ObjectMeta: metav1.ObjectMeta{
 21572  					Name:      "abc",
 21573  					Namespace: "foo",
 21574  				},
 21575  				Spec: fractionalComputeSpec,
 21576  			},
 21577  		},
 21578  		"terminating-spec": {
 21579  			rq: core.ResourceQuota{
 21580  				ObjectMeta: metav1.ObjectMeta{
 21581  					Name:      "abc",
 21582  					Namespace: "foo",
 21583  				},
 21584  				Spec: terminatingSpec,
 21585  			},
 21586  		},
 21587  		"non-terminating-spec": {
 21588  			rq: core.ResourceQuota{
 21589  				ObjectMeta: metav1.ObjectMeta{
 21590  					Name:      "abc",
 21591  					Namespace: "foo",
 21592  				},
 21593  				Spec: nonTerminatingSpec,
 21594  			},
 21595  		},
 21596  		"best-effort-spec": {
 21597  			rq: core.ResourceQuota{
 21598  				ObjectMeta: metav1.ObjectMeta{
 21599  					Name:      "abc",
 21600  					Namespace: "foo",
 21601  				},
 21602  				Spec: bestEffortSpec,
 21603  			},
 21604  		},
 21605  		"cross-namespace-affinity-spec": {
 21606  			rq: core.ResourceQuota{
 21607  				ObjectMeta: metav1.ObjectMeta{
 21608  					Name:      "abc",
 21609  					Namespace: "foo",
 21610  				},
 21611  				Spec: crossNamespaceAffinitySpec,
 21612  			},
 21613  		},
 21614  		"scope-selector-spec": {
 21615  			rq: core.ResourceQuota{
 21616  				ObjectMeta: metav1.ObjectMeta{
 21617  					Name:      "abc",
 21618  					Namespace: "foo",
 21619  				},
 21620  				Spec: scopeSelectorSpec,
 21621  			},
 21622  		},
 21623  		"non-best-effort-spec": {
 21624  			rq: core.ResourceQuota{
 21625  				ObjectMeta: metav1.ObjectMeta{
 21626  					Name:      "abc",
 21627  					Namespace: "foo",
 21628  				},
 21629  				Spec: nonBestEffortSpec,
 21630  			},
 21631  		},
 21632  		"zero-length Name": {
 21633  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
 21634  			errDetail: "name or generateName is required",
 21635  		},
 21636  		"zero-length Namespace": {
 21637  			rq:       core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
 21638  			errField: "metadata.namespace",
 21639  		},
 21640  		"invalid Name": {
 21641  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
 21642  			errDetail: dnsSubdomainLabelErrMsg,
 21643  		},
 21644  		"invalid Namespace": {
 21645  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
 21646  			errDetail: dnsLabelErrMsg,
 21647  		},
 21648  		"negative-limits": {
 21649  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec},
 21650  			errDetail: isNegativeErrorMsg,
 21651  		},
 21652  		"fractional-api-resource": {
 21653  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
 21654  			errDetail: isNotIntegerErrorMsg,
 21655  		},
 21656  		"invalid-quota-resource": {
 21657  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
 21658  			errDetail: isInvalidQuotaResource,
 21659  		},
 21660  		"invalid-quota-terminating-pair": {
 21661  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
 21662  			errDetail: "conflicting scopes",
 21663  		},
 21664  		"invalid-quota-besteffort-pair": {
 21665  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
 21666  			errDetail: "conflicting scopes",
 21667  		},
 21668  		"invalid-quota-scope-name": {
 21669  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
 21670  			errDetail: "unsupported scope",
 21671  		},
 21672  		"invalid-cross-namespace-affinity": {
 21673  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec},
 21674  			errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity",
 21675  		},
 21676  	}
 21677  	for name, tc := range testCases {
 21678  		t.Run(name, func(t *testing.T) {
 21679  			errs := ValidateResourceQuota(&tc.rq)
 21680  			if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
 21681  				t.Errorf("expected success: %v", errs)
 21682  			} else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {
 21683  				t.Errorf("expected failure")
 21684  			} else {
 21685  				for i := range errs {
 21686  					if !strings.Contains(errs[i].Detail, tc.errDetail) {
 21687  						t.Errorf("expected error detail either empty or %s, got %s", tc.errDetail, errs[i].Detail)
 21688  					}
 21689  				}
 21690  			}
 21691  		})
 21692  	}
 21693  }
 21694  
 21695  func TestValidateNamespace(t *testing.T) {
 21696  	validLabels := map[string]string{"a": "b"}
 21697  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 21698  	successCases := []core.Namespace{{
 21699  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels},
 21700  	}, {
 21701  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 21702  		Spec: core.NamespaceSpec{
 21703  			Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"},
 21704  		},
 21705  	},
 21706  	}
 21707  	for _, successCase := range successCases {
 21708  		if errs := ValidateNamespace(&successCase); len(errs) != 0 {
 21709  			t.Errorf("expected success: %v", errs)
 21710  		}
 21711  	}
 21712  	errorCases := map[string]struct {
 21713  		R core.Namespace
 21714  		D string
 21715  	}{
 21716  		"zero-length name": {
 21717  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}},
 21718  			"",
 21719  		},
 21720  		"defined-namespace": {
 21721  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
 21722  			"",
 21723  		},
 21724  		"invalid-labels": {
 21725  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}},
 21726  			"",
 21727  		},
 21728  	}
 21729  	for k, v := range errorCases {
 21730  		errs := ValidateNamespace(&v.R)
 21731  		if len(errs) == 0 {
 21732  			t.Errorf("expected failure for %s", k)
 21733  		}
 21734  	}
 21735  }
 21736  
 21737  func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
 21738  	tests := []struct {
 21739  		oldNamespace core.Namespace
 21740  		namespace    core.Namespace
 21741  		valid        bool
 21742  	}{
 21743  		{core.Namespace{}, core.Namespace{}, true},
 21744  		{core.Namespace{
 21745  			ObjectMeta: metav1.ObjectMeta{
 21746  				Name: "foo"}},
 21747  			core.Namespace{
 21748  				ObjectMeta: metav1.ObjectMeta{
 21749  					Name: "foo"},
 21750  				Spec: core.NamespaceSpec{
 21751  					Finalizers: []core.FinalizerName{"Foo"},
 21752  				},
 21753  			}, false},
 21754  		{core.Namespace{
 21755  			ObjectMeta: metav1.ObjectMeta{
 21756  				Name: "foo"},
 21757  			Spec: core.NamespaceSpec{
 21758  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 21759  			},
 21760  		},
 21761  			core.Namespace{
 21762  				ObjectMeta: metav1.ObjectMeta{
 21763  					Name: "foo"},
 21764  				Spec: core.NamespaceSpec{
 21765  					Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"},
 21766  				},
 21767  			}, true},
 21768  		{core.Namespace{
 21769  			ObjectMeta: metav1.ObjectMeta{
 21770  				Name: "fooemptyfinalizer"},
 21771  			Spec: core.NamespaceSpec{
 21772  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 21773  			},
 21774  		},
 21775  			core.Namespace{
 21776  				ObjectMeta: metav1.ObjectMeta{
 21777  					Name: "fooemptyfinalizer"},
 21778  				Spec: core.NamespaceSpec{
 21779  					Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"},
 21780  				},
 21781  			}, false},
 21782  	}
 21783  	for i, test := range tests {
 21784  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21785  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21786  		errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
 21787  		if test.valid && len(errs) > 0 {
 21788  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21789  			t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
 21790  		}
 21791  		if !test.valid && len(errs) == 0 {
 21792  			t.Errorf("%d: Unexpected non-error", i)
 21793  		}
 21794  	}
 21795  }
 21796  
 21797  func TestValidateNamespaceStatusUpdate(t *testing.T) {
 21798  	now := metav1.Now()
 21799  
 21800  	tests := []struct {
 21801  		oldNamespace core.Namespace
 21802  		namespace    core.Namespace
 21803  		valid        bool
 21804  	}{
 21805  		{core.Namespace{}, core.Namespace{
 21806  			Status: core.NamespaceStatus{
 21807  				Phase: core.NamespaceActive,
 21808  			},
 21809  		}, true},
 21810  		// Cannot set deletionTimestamp via status update
 21811  		{core.Namespace{
 21812  			ObjectMeta: metav1.ObjectMeta{
 21813  				Name: "foo"}},
 21814  			core.Namespace{
 21815  				ObjectMeta: metav1.ObjectMeta{
 21816  					Name:              "foo",
 21817  					DeletionTimestamp: &now},
 21818  				Status: core.NamespaceStatus{
 21819  					Phase: core.NamespaceTerminating,
 21820  				},
 21821  			}, false},
 21822  		// Can update phase via status update
 21823  		{core.Namespace{
 21824  			ObjectMeta: metav1.ObjectMeta{
 21825  				Name:              "foo",
 21826  				DeletionTimestamp: &now}},
 21827  			core.Namespace{
 21828  				ObjectMeta: metav1.ObjectMeta{
 21829  					Name:              "foo",
 21830  					DeletionTimestamp: &now},
 21831  				Status: core.NamespaceStatus{
 21832  					Phase: core.NamespaceTerminating,
 21833  				},
 21834  			}, true},
 21835  		{core.Namespace{
 21836  			ObjectMeta: metav1.ObjectMeta{
 21837  				Name: "foo"}},
 21838  			core.Namespace{
 21839  				ObjectMeta: metav1.ObjectMeta{
 21840  					Name: "foo"},
 21841  				Status: core.NamespaceStatus{
 21842  					Phase: core.NamespaceTerminating,
 21843  				},
 21844  			}, false},
 21845  		{core.Namespace{
 21846  			ObjectMeta: metav1.ObjectMeta{
 21847  				Name: "foo"}},
 21848  			core.Namespace{
 21849  				ObjectMeta: metav1.ObjectMeta{
 21850  					Name: "bar"},
 21851  				Status: core.NamespaceStatus{
 21852  					Phase: core.NamespaceTerminating,
 21853  				},
 21854  			}, false},
 21855  	}
 21856  	for i, test := range tests {
 21857  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21858  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21859  		errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace)
 21860  		if test.valid && len(errs) > 0 {
 21861  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21862  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 21863  		}
 21864  		if !test.valid && len(errs) == 0 {
 21865  			t.Errorf("%d: Unexpected non-error", i)
 21866  		}
 21867  	}
 21868  }
 21869  
 21870  func TestValidateNamespaceUpdate(t *testing.T) {
 21871  	tests := []struct {
 21872  		oldNamespace core.Namespace
 21873  		namespace    core.Namespace
 21874  		valid        bool
 21875  	}{
 21876  		{core.Namespace{}, core.Namespace{}, true},
 21877  		{core.Namespace{
 21878  			ObjectMeta: metav1.ObjectMeta{
 21879  				Name: "foo1"}},
 21880  			core.Namespace{
 21881  				ObjectMeta: metav1.ObjectMeta{
 21882  					Name: "bar1"},
 21883  			}, false},
 21884  		{core.Namespace{
 21885  			ObjectMeta: metav1.ObjectMeta{
 21886  				Name:   "foo2",
 21887  				Labels: map[string]string{"foo": "bar"},
 21888  			},
 21889  		}, core.Namespace{
 21890  			ObjectMeta: metav1.ObjectMeta{
 21891  				Name:   "foo2",
 21892  				Labels: map[string]string{"foo": "baz"},
 21893  			},
 21894  		}, true},
 21895  		{core.Namespace{
 21896  			ObjectMeta: metav1.ObjectMeta{
 21897  				Name: "foo3",
 21898  			},
 21899  		}, core.Namespace{
 21900  			ObjectMeta: metav1.ObjectMeta{
 21901  				Name:   "foo3",
 21902  				Labels: map[string]string{"foo": "baz"},
 21903  			},
 21904  		}, true},
 21905  		{core.Namespace{
 21906  			ObjectMeta: metav1.ObjectMeta{
 21907  				Name:   "foo4",
 21908  				Labels: map[string]string{"bar": "foo"},
 21909  			},
 21910  		}, core.Namespace{
 21911  			ObjectMeta: metav1.ObjectMeta{
 21912  				Name:   "foo4",
 21913  				Labels: map[string]string{"foo": "baz"},
 21914  			},
 21915  		}, true},
 21916  		{core.Namespace{
 21917  			ObjectMeta: metav1.ObjectMeta{
 21918  				Name:   "foo5",
 21919  				Labels: map[string]string{"foo": "baz"},
 21920  			},
 21921  		}, core.Namespace{
 21922  			ObjectMeta: metav1.ObjectMeta{
 21923  				Name:   "foo5",
 21924  				Labels: map[string]string{"Foo": "baz"},
 21925  			},
 21926  		}, true},
 21927  		{core.Namespace{
 21928  			ObjectMeta: metav1.ObjectMeta{
 21929  				Name:   "foo6",
 21930  				Labels: map[string]string{"foo": "baz"},
 21931  			},
 21932  		}, core.Namespace{
 21933  			ObjectMeta: metav1.ObjectMeta{
 21934  				Name:   "foo6",
 21935  				Labels: map[string]string{"Foo": "baz"},
 21936  			},
 21937  			Spec: core.NamespaceSpec{
 21938  				Finalizers: []core.FinalizerName{"kubernetes"},
 21939  			},
 21940  			Status: core.NamespaceStatus{
 21941  				Phase: core.NamespaceTerminating,
 21942  			},
 21943  		}, true},
 21944  	}
 21945  	for i, test := range tests {
 21946  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21947  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21948  		errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace)
 21949  		if test.valid && len(errs) > 0 {
 21950  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21951  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 21952  		}
 21953  		if !test.valid && len(errs) == 0 {
 21954  			t.Errorf("%d: Unexpected non-error", i)
 21955  		}
 21956  	}
 21957  }
 21958  
 21959  func TestValidateSecret(t *testing.T) {
 21960  	// Opaque secret validation
 21961  	validSecret := func() core.Secret {
 21962  		return core.Secret{
 21963  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21964  			Data: map[string][]byte{
 21965  				"data-1": []byte("bar"),
 21966  			},
 21967  		}
 21968  	}
 21969  
 21970  	var (
 21971  		emptyName     = validSecret()
 21972  		invalidName   = validSecret()
 21973  		emptyNs       = validSecret()
 21974  		invalidNs     = validSecret()
 21975  		overMaxSize   = validSecret()
 21976  		invalidKey    = validSecret()
 21977  		leadingDotKey = validSecret()
 21978  		dotKey        = validSecret()
 21979  		doubleDotKey  = validSecret()
 21980  	)
 21981  
 21982  	emptyName.Name = ""
 21983  	invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals"
 21984  	emptyNs.Namespace = ""
 21985  	invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals"
 21986  	overMaxSize.Data = map[string][]byte{
 21987  		"over": make([]byte, core.MaxSecretSize+1),
 21988  	}
 21989  	invalidKey.Data["a*b"] = []byte("whoops")
 21990  	leadingDotKey.Data[".key"] = []byte("bar")
 21991  	dotKey.Data["."] = []byte("bar")
 21992  	doubleDotKey.Data[".."] = []byte("bar")
 21993  
 21994  	// kubernetes.io/service-account-token secret validation
 21995  	validServiceAccountTokenSecret := func() core.Secret {
 21996  		return core.Secret{
 21997  			ObjectMeta: metav1.ObjectMeta{
 21998  				Name:      "foo",
 21999  				Namespace: "bar",
 22000  				Annotations: map[string]string{
 22001  					core.ServiceAccountNameKey: "foo",
 22002  				},
 22003  			},
 22004  			Type: core.SecretTypeServiceAccountToken,
 22005  			Data: map[string][]byte{
 22006  				"data-1": []byte("bar"),
 22007  			},
 22008  		}
 22009  	}
 22010  
 22011  	var (
 22012  		emptyTokenAnnotation    = validServiceAccountTokenSecret()
 22013  		missingTokenAnnotation  = validServiceAccountTokenSecret()
 22014  		missingTokenAnnotations = validServiceAccountTokenSecret()
 22015  	)
 22016  	emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = ""
 22017  	delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey)
 22018  	missingTokenAnnotations.Annotations = nil
 22019  
 22020  	tests := map[string]struct {
 22021  		secret core.Secret
 22022  		valid  bool
 22023  	}{
 22024  		"valid":                                     {validSecret(), true},
 22025  		"empty name":                                {emptyName, false},
 22026  		"invalid name":                              {invalidName, false},
 22027  		"empty namespace":                           {emptyNs, false},
 22028  		"invalid namespace":                         {invalidNs, false},
 22029  		"over max size":                             {overMaxSize, false},
 22030  		"invalid key":                               {invalidKey, false},
 22031  		"valid service-account-token secret":        {validServiceAccountTokenSecret(), true},
 22032  		"empty service-account-token annotation":    {emptyTokenAnnotation, false},
 22033  		"missing service-account-token annotation":  {missingTokenAnnotation, false},
 22034  		"missing service-account-token annotations": {missingTokenAnnotations, false},
 22035  		"leading dot key":                           {leadingDotKey, true},
 22036  		"dot key":                                   {dotKey, false},
 22037  		"double dot key":                            {doubleDotKey, false},
 22038  	}
 22039  
 22040  	for name, tc := range tests {
 22041  		errs := ValidateSecret(&tc.secret)
 22042  		if tc.valid && len(errs) > 0 {
 22043  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22044  		}
 22045  		if !tc.valid && len(errs) == 0 {
 22046  			t.Errorf("%v: Unexpected non-error", name)
 22047  		}
 22048  	}
 22049  }
 22050  
 22051  func TestValidateSecretUpdate(t *testing.T) {
 22052  	validSecret := func() core.Secret {
 22053  		return core.Secret{
 22054  			ObjectMeta: metav1.ObjectMeta{
 22055  				Name:            "foo",
 22056  				Namespace:       "bar",
 22057  				ResourceVersion: "20",
 22058  			},
 22059  			Data: map[string][]byte{
 22060  				"data-1": []byte("bar"),
 22061  			},
 22062  		}
 22063  	}
 22064  
 22065  	falseVal := false
 22066  	trueVal := true
 22067  
 22068  	secret := validSecret()
 22069  	immutableSecret := validSecret()
 22070  	immutableSecret.Immutable = &trueVal
 22071  	mutableSecret := validSecret()
 22072  	mutableSecret.Immutable = &falseVal
 22073  
 22074  	secretWithData := validSecret()
 22075  	secretWithData.Data["data-2"] = []byte("baz")
 22076  	immutableSecretWithData := validSecret()
 22077  	immutableSecretWithData.Immutable = &trueVal
 22078  	immutableSecretWithData.Data["data-2"] = []byte("baz")
 22079  
 22080  	secretWithChangedData := validSecret()
 22081  	secretWithChangedData.Data["data-1"] = []byte("foo")
 22082  	immutableSecretWithChangedData := validSecret()
 22083  	immutableSecretWithChangedData.Immutable = &trueVal
 22084  	immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
 22085  
 22086  	tests := []struct {
 22087  		name      string
 22088  		oldSecret core.Secret
 22089  		newSecret core.Secret
 22090  		valid     bool
 22091  	}{{
 22092  		name:      "mark secret immutable",
 22093  		oldSecret: secret,
 22094  		newSecret: immutableSecret,
 22095  		valid:     true,
 22096  	}, {
 22097  		name:      "revert immutable secret",
 22098  		oldSecret: immutableSecret,
 22099  		newSecret: secret,
 22100  		valid:     false,
 22101  	}, {
 22102  		name:      "makr immutable secret mutable",
 22103  		oldSecret: immutableSecret,
 22104  		newSecret: mutableSecret,
 22105  		valid:     false,
 22106  	}, {
 22107  		name:      "add data in secret",
 22108  		oldSecret: secret,
 22109  		newSecret: secretWithData,
 22110  		valid:     true,
 22111  	}, {
 22112  		name:      "add data in immutable secret",
 22113  		oldSecret: immutableSecret,
 22114  		newSecret: immutableSecretWithData,
 22115  		valid:     false,
 22116  	}, {
 22117  		name:      "change data in secret",
 22118  		oldSecret: secret,
 22119  		newSecret: secretWithChangedData,
 22120  		valid:     true,
 22121  	}, {
 22122  		name:      "change data in immutable secret",
 22123  		oldSecret: immutableSecret,
 22124  		newSecret: immutableSecretWithChangedData,
 22125  		valid:     false,
 22126  	},
 22127  	}
 22128  
 22129  	for _, tc := range tests {
 22130  		t.Run(tc.name, func(t *testing.T) {
 22131  			errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
 22132  			if tc.valid && len(errs) > 0 {
 22133  				t.Errorf("Unexpected error: %v", errs)
 22134  			}
 22135  			if !tc.valid && len(errs) == 0 {
 22136  				t.Errorf("Unexpected lack of error")
 22137  			}
 22138  		})
 22139  	}
 22140  }
 22141  
 22142  func TestValidateDockerConfigSecret(t *testing.T) {
 22143  	validDockerSecret := func() core.Secret {
 22144  		return core.Secret{
 22145  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22146  			Type:       core.SecretTypeDockercfg,
 22147  			Data: map[string][]byte{
 22148  				core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`),
 22149  			},
 22150  		}
 22151  	}
 22152  	validDockerSecret2 := func() core.Secret {
 22153  		return core.Secret{
 22154  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22155  			Type:       core.SecretTypeDockerConfigJSON,
 22156  			Data: map[string][]byte{
 22157  				core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`),
 22158  			},
 22159  		}
 22160  	}
 22161  
 22162  	var (
 22163  		missingDockerConfigKey  = validDockerSecret()
 22164  		emptyDockerConfigKey    = validDockerSecret()
 22165  		invalidDockerConfigKey  = validDockerSecret()
 22166  		missingDockerConfigKey2 = validDockerSecret2()
 22167  		emptyDockerConfigKey2   = validDockerSecret2()
 22168  		invalidDockerConfigKey2 = validDockerSecret2()
 22169  	)
 22170  
 22171  	delete(missingDockerConfigKey.Data, core.DockerConfigKey)
 22172  	emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("")
 22173  	invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad")
 22174  	delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey)
 22175  	emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("")
 22176  	invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad")
 22177  
 22178  	tests := map[string]struct {
 22179  		secret core.Secret
 22180  		valid  bool
 22181  	}{
 22182  		"valid dockercfg":     {validDockerSecret(), true},
 22183  		"missing dockercfg":   {missingDockerConfigKey, false},
 22184  		"empty dockercfg":     {emptyDockerConfigKey, false},
 22185  		"invalid dockercfg":   {invalidDockerConfigKey, false},
 22186  		"valid config.json":   {validDockerSecret2(), true},
 22187  		"missing config.json": {missingDockerConfigKey2, false},
 22188  		"empty config.json":   {emptyDockerConfigKey2, false},
 22189  		"invalid config.json": {invalidDockerConfigKey2, false},
 22190  	}
 22191  
 22192  	for name, tc := range tests {
 22193  		errs := ValidateSecret(&tc.secret)
 22194  		if tc.valid && len(errs) > 0 {
 22195  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22196  		}
 22197  		if !tc.valid && len(errs) == 0 {
 22198  			t.Errorf("%v: Unexpected non-error", name)
 22199  		}
 22200  	}
 22201  }
 22202  
 22203  func TestValidateBasicAuthSecret(t *testing.T) {
 22204  	validBasicAuthSecret := func() core.Secret {
 22205  		return core.Secret{
 22206  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22207  			Type:       core.SecretTypeBasicAuth,
 22208  			Data: map[string][]byte{
 22209  				core.BasicAuthUsernameKey: []byte("username"),
 22210  				core.BasicAuthPasswordKey: []byte("password"),
 22211  			},
 22212  		}
 22213  	}
 22214  
 22215  	var (
 22216  		missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret()
 22217  	)
 22218  
 22219  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey)
 22220  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey)
 22221  
 22222  	tests := map[string]struct {
 22223  		secret core.Secret
 22224  		valid  bool
 22225  	}{
 22226  		"valid":                         {validBasicAuthSecret(), true},
 22227  		"missing username and password": {missingBasicAuthUsernamePasswordKeys, false},
 22228  	}
 22229  
 22230  	for name, tc := range tests {
 22231  		errs := ValidateSecret(&tc.secret)
 22232  		if tc.valid && len(errs) > 0 {
 22233  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22234  		}
 22235  		if !tc.valid && len(errs) == 0 {
 22236  			t.Errorf("%v: Unexpected non-error", name)
 22237  		}
 22238  	}
 22239  }
 22240  
 22241  func TestValidateSSHAuthSecret(t *testing.T) {
 22242  	validSSHAuthSecret := func() core.Secret {
 22243  		return core.Secret{
 22244  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 22245  			Type:       core.SecretTypeSSHAuth,
 22246  			Data: map[string][]byte{
 22247  				core.SSHAuthPrivateKey: []byte("foo-bar-baz"),
 22248  			},
 22249  		}
 22250  	}
 22251  
 22252  	missingSSHAuthPrivateKey := validSSHAuthSecret()
 22253  
 22254  	delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey)
 22255  
 22256  	tests := map[string]struct {
 22257  		secret core.Secret
 22258  		valid  bool
 22259  	}{
 22260  		"valid":               {validSSHAuthSecret(), true},
 22261  		"missing private key": {missingSSHAuthPrivateKey, false},
 22262  	}
 22263  
 22264  	for name, tc := range tests {
 22265  		errs := ValidateSecret(&tc.secret)
 22266  		if tc.valid && len(errs) > 0 {
 22267  			t.Errorf("%v: Unexpected error: %v", name, errs)
 22268  		}
 22269  		if !tc.valid && len(errs) == 0 {
 22270  			t.Errorf("%v: Unexpected non-error", name)
 22271  		}
 22272  	}
 22273  }
 22274  
 22275  func TestValidateEndpointsCreate(t *testing.T) {
 22276  	successCases := map[string]struct {
 22277  		endpoints core.Endpoints
 22278  	}{
 22279  		"simple endpoint": {
 22280  			endpoints: core.Endpoints{
 22281  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22282  				Subsets: []core.EndpointSubset{{
 22283  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
 22284  					Ports:     []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 22285  				}, {
 22286  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 22287  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
 22288  				}},
 22289  			},
 22290  		},
 22291  		"empty subsets": {
 22292  			endpoints: core.Endpoints{
 22293  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22294  			},
 22295  		},
 22296  		"no name required for singleton port": {
 22297  			endpoints: core.Endpoints{
 22298  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22299  				Subsets: []core.EndpointSubset{{
 22300  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22301  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
 22302  				}},
 22303  			},
 22304  		},
 22305  		"valid appProtocol": {
 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{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}},
 22311  				}},
 22312  			},
 22313  		},
 22314  		"empty ports": {
 22315  			endpoints: core.Endpoints{
 22316  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22317  				Subsets: []core.EndpointSubset{{
 22318  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 22319  				}},
 22320  			},
 22321  		},
 22322  	}
 22323  
 22324  	for name, tc := range successCases {
 22325  		t.Run(name, func(t *testing.T) {
 22326  			errs := ValidateEndpointsCreate(&tc.endpoints)
 22327  			if len(errs) != 0 {
 22328  				t.Errorf("Expected no validation errors, got %v", errs)
 22329  			}
 22330  
 22331  		})
 22332  	}
 22333  
 22334  	errorCases := map[string]struct {
 22335  		endpoints   core.Endpoints
 22336  		errorType   field.ErrorType
 22337  		errorDetail string
 22338  	}{
 22339  		"missing namespace": {
 22340  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
 22341  			errorType: "FieldValueRequired",
 22342  		},
 22343  		"missing name": {
 22344  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}},
 22345  			errorType: "FieldValueRequired",
 22346  		},
 22347  		"invalid namespace": {
 22348  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}},
 22349  			errorType:   "FieldValueInvalid",
 22350  			errorDetail: dnsLabelErrMsg,
 22351  		},
 22352  		"invalid name": {
 22353  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}},
 22354  			errorType:   "FieldValueInvalid",
 22355  			errorDetail: dnsSubdomainLabelErrMsg,
 22356  		},
 22357  		"empty addresses": {
 22358  			endpoints: core.Endpoints{
 22359  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22360  				Subsets: []core.EndpointSubset{{
 22361  					Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22362  				}},
 22363  			},
 22364  			errorType: "FieldValueRequired",
 22365  		},
 22366  		"invalid IP": {
 22367  			endpoints: core.Endpoints{
 22368  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22369  				Subsets: []core.EndpointSubset{{
 22370  					Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}},
 22371  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22372  				}},
 22373  			},
 22374  			errorType:   "FieldValueInvalid",
 22375  			errorDetail: "must be a valid IP address",
 22376  		},
 22377  		"Multiple ports, one without name": {
 22378  			endpoints: core.Endpoints{
 22379  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22380  				Subsets: []core.EndpointSubset{{
 22381  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22382  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 22383  				}},
 22384  			},
 22385  			errorType: "FieldValueRequired",
 22386  		},
 22387  		"Invalid port number": {
 22388  			endpoints: core.Endpoints{
 22389  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22390  				Subsets: []core.EndpointSubset{{
 22391  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22392  					Ports:     []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}},
 22393  				}},
 22394  			},
 22395  			errorType:   "FieldValueInvalid",
 22396  			errorDetail: "between",
 22397  		},
 22398  		"Invalid protocol": {
 22399  			endpoints: core.Endpoints{
 22400  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22401  				Subsets: []core.EndpointSubset{{
 22402  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22403  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}},
 22404  				}},
 22405  			},
 22406  			errorType: "FieldValueNotSupported",
 22407  		},
 22408  		"Address missing IP": {
 22409  			endpoints: core.Endpoints{
 22410  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22411  				Subsets: []core.EndpointSubset{{
 22412  					Addresses: []core.EndpointAddress{{}},
 22413  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 22414  				}},
 22415  			},
 22416  			errorType:   "FieldValueInvalid",
 22417  			errorDetail: "must be a valid IP address",
 22418  		},
 22419  		"Port missing number": {
 22420  			endpoints: core.Endpoints{
 22421  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22422  				Subsets: []core.EndpointSubset{{
 22423  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22424  					Ports:     []core.EndpointPort{{Name: "a", Protocol: "TCP"}},
 22425  				}},
 22426  			},
 22427  			errorType:   "FieldValueInvalid",
 22428  			errorDetail: "between",
 22429  		},
 22430  		"Port missing protocol": {
 22431  			endpoints: core.Endpoints{
 22432  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22433  				Subsets: []core.EndpointSubset{{
 22434  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22435  					Ports:     []core.EndpointPort{{Name: "a", Port: 93}},
 22436  				}},
 22437  			},
 22438  			errorType: "FieldValueRequired",
 22439  		},
 22440  		"Address is loopback": {
 22441  			endpoints: core.Endpoints{
 22442  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22443  				Subsets: []core.EndpointSubset{{
 22444  					Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}},
 22445  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22446  				}},
 22447  			},
 22448  			errorType:   "FieldValueInvalid",
 22449  			errorDetail: "loopback",
 22450  		},
 22451  		"Address is link-local": {
 22452  			endpoints: core.Endpoints{
 22453  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22454  				Subsets: []core.EndpointSubset{{
 22455  					Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}},
 22456  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22457  				}},
 22458  			},
 22459  			errorType:   "FieldValueInvalid",
 22460  			errorDetail: "link-local",
 22461  		},
 22462  		"Address is link-local multicast": {
 22463  			endpoints: core.Endpoints{
 22464  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22465  				Subsets: []core.EndpointSubset{{
 22466  					Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}},
 22467  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 22468  				}},
 22469  			},
 22470  			errorType:   "FieldValueInvalid",
 22471  			errorDetail: "link-local multicast",
 22472  		},
 22473  		"Invalid AppProtocol": {
 22474  			endpoints: core.Endpoints{
 22475  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 22476  				Subsets: []core.EndpointSubset{{
 22477  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 22478  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}},
 22479  				}},
 22480  			},
 22481  			errorType:   "FieldValueInvalid",
 22482  			errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character",
 22483  		},
 22484  	}
 22485  
 22486  	for k, v := range errorCases {
 22487  		t.Run(k, func(t *testing.T) {
 22488  			if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22489  				t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs)
 22490  			}
 22491  		})
 22492  	}
 22493  }
 22494  
 22495  func TestValidateEndpointsUpdate(t *testing.T) {
 22496  	baseEndpoints := core.Endpoints{
 22497  		ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"},
 22498  		Subsets: []core.EndpointSubset{{
 22499  			Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}},
 22500  		}},
 22501  	}
 22502  
 22503  	testCases := map[string]struct {
 22504  		tweakOldEndpoints func(ep *core.Endpoints)
 22505  		tweakNewEndpoints func(ep *core.Endpoints)
 22506  		numExpectedErrors int
 22507  	}{
 22508  		"update to valid app protocol": {
 22509  			tweakOldEndpoints: func(ep *core.Endpoints) {
 22510  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 22511  			},
 22512  			tweakNewEndpoints: func(ep *core.Endpoints) {
 22513  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 22514  			},
 22515  			numExpectedErrors: 0,
 22516  		},
 22517  		"update to invalid app protocol": {
 22518  			tweakOldEndpoints: func(ep *core.Endpoints) {
 22519  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 22520  			},
 22521  			tweakNewEndpoints: func(ep *core.Endpoints) {
 22522  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 22523  			},
 22524  			numExpectedErrors: 1,
 22525  		},
 22526  	}
 22527  
 22528  	for name, tc := range testCases {
 22529  		t.Run(name, func(t *testing.T) {
 22530  			oldEndpoints := baseEndpoints.DeepCopy()
 22531  			tc.tweakOldEndpoints(oldEndpoints)
 22532  			newEndpoints := baseEndpoints.DeepCopy()
 22533  			tc.tweakNewEndpoints(newEndpoints)
 22534  
 22535  			errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints)
 22536  			if len(errs) != tc.numExpectedErrors {
 22537  				t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs)
 22538  			}
 22539  
 22540  		})
 22541  	}
 22542  }
 22543  
 22544  func TestValidateWindowsSecurityContext(t *testing.T) {
 22545  	tests := []struct {
 22546  		name        string
 22547  		sc          *core.PodSpec
 22548  		expectError bool
 22549  		errorMsg    string
 22550  		errorType   field.ErrorType
 22551  	}{{
 22552  		name:        "pod with SELinux Options",
 22553  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}},
 22554  		expectError: true,
 22555  		errorMsg:    "cannot be set for a windows pod",
 22556  		errorType:   "FieldValueForbidden",
 22557  	}, {
 22558  		name:        "pod with SeccompProfile",
 22559  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}},
 22560  		expectError: true,
 22561  		errorMsg:    "cannot be set for a windows pod",
 22562  		errorType:   "FieldValueForbidden",
 22563  	}, {
 22564  		name:        "pod with AppArmorProfile",
 22565  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{AppArmorProfile: &core.AppArmorProfile{Type: core.AppArmorProfileTypeRuntimeDefault}}}}},
 22566  		expectError: true,
 22567  		errorMsg:    "cannot be set for a windows pod",
 22568  		errorType:   "FieldValueForbidden",
 22569  	}, {
 22570  		name:        "pod with WindowsOptions, no error",
 22571  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}},
 22572  		expectError: false,
 22573  	},
 22574  	}
 22575  	for _, test := range tests {
 22576  		t.Run(test.name, func(t *testing.T) {
 22577  			errs := validateWindows(test.sc, field.NewPath("field"))
 22578  			if test.expectError && len(errs) > 0 {
 22579  				if errs[0].Type != test.errorType {
 22580  					t.Errorf("expected error type %q got %q", test.errorType, errs[0].Type)
 22581  				}
 22582  				if errs[0].Detail != test.errorMsg {
 22583  					t.Errorf("expected error detail %q, got %q", test.errorMsg, errs[0].Detail)
 22584  				}
 22585  			} else if test.expectError && len(errs) == 0 {
 22586  				t.Error("Unexpected success")
 22587  			}
 22588  			if !test.expectError && len(errs) != 0 {
 22589  				t.Errorf("Unexpected error(s): %v", errs)
 22590  			}
 22591  		})
 22592  	}
 22593  }
 22594  
 22595  func TestValidateOSFields(t *testing.T) {
 22596  	// Contains the list of OS specific fields within pod spec.
 22597  	// All the fields in pod spec should be either osSpecific or osNeutral field
 22598  	// To make a field OS specific:
 22599  	// - Add documentation to the os specific field indicating which os it can/cannot be set for
 22600  	// - Add documentation to the os field in the api
 22601  	// - Add validation logic validateLinux, validateWindows functions to make sure the field is only set for eligible OSes
 22602  	osSpecificFields := sets.NewString(
 22603  		"Containers[*].SecurityContext.AppArmorProfile",
 22604  		"Containers[*].SecurityContext.AllowPrivilegeEscalation",
 22605  		"Containers[*].SecurityContext.Capabilities",
 22606  		"Containers[*].SecurityContext.Privileged",
 22607  		"Containers[*].SecurityContext.ProcMount",
 22608  		"Containers[*].SecurityContext.ReadOnlyRootFilesystem",
 22609  		"Containers[*].SecurityContext.RunAsGroup",
 22610  		"Containers[*].SecurityContext.RunAsUser",
 22611  		"Containers[*].SecurityContext.SELinuxOptions",
 22612  		"Containers[*].SecurityContext.SeccompProfile",
 22613  		"Containers[*].SecurityContext.WindowsOptions",
 22614  		"InitContainers[*].SecurityContext.AppArmorProfile",
 22615  		"InitContainers[*].SecurityContext.AllowPrivilegeEscalation",
 22616  		"InitContainers[*].SecurityContext.Capabilities",
 22617  		"InitContainers[*].SecurityContext.Privileged",
 22618  		"InitContainers[*].SecurityContext.ProcMount",
 22619  		"InitContainers[*].SecurityContext.ReadOnlyRootFilesystem",
 22620  		"InitContainers[*].SecurityContext.RunAsGroup",
 22621  		"InitContainers[*].SecurityContext.RunAsUser",
 22622  		"InitContainers[*].SecurityContext.SELinuxOptions",
 22623  		"InitContainers[*].SecurityContext.SeccompProfile",
 22624  		"InitContainers[*].SecurityContext.WindowsOptions",
 22625  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AppArmorProfile",
 22626  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation",
 22627  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities",
 22628  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged",
 22629  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ProcMount",
 22630  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ReadOnlyRootFilesystem",
 22631  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsGroup",
 22632  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsUser",
 22633  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SELinuxOptions",
 22634  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
 22635  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
 22636  		"OS",
 22637  		"SecurityContext.AppArmorProfile",
 22638  		"SecurityContext.FSGroup",
 22639  		"SecurityContext.FSGroupChangePolicy",
 22640  		"SecurityContext.HostIPC",
 22641  		"SecurityContext.HostNetwork",
 22642  		"SecurityContext.HostPID",
 22643  		"SecurityContext.HostUsers",
 22644  		"SecurityContext.RunAsGroup",
 22645  		"SecurityContext.RunAsUser",
 22646  		"SecurityContext.SELinuxOptions",
 22647  		"SecurityContext.SeccompProfile",
 22648  		"SecurityContext.ShareProcessNamespace",
 22649  		"SecurityContext.SupplementalGroups",
 22650  		"SecurityContext.Sysctls",
 22651  		"SecurityContext.WindowsOptions",
 22652  	)
 22653  	osNeutralFields := sets.NewString(
 22654  		"ActiveDeadlineSeconds",
 22655  		"Affinity",
 22656  		"AutomountServiceAccountToken",
 22657  		"Containers[*].Args",
 22658  		"Containers[*].Command",
 22659  		"Containers[*].Env",
 22660  		"Containers[*].EnvFrom",
 22661  		"Containers[*].Image",
 22662  		"Containers[*].ImagePullPolicy",
 22663  		"Containers[*].Lifecycle",
 22664  		"Containers[*].LivenessProbe",
 22665  		"Containers[*].Name",
 22666  		"Containers[*].Ports",
 22667  		"Containers[*].ReadinessProbe",
 22668  		"Containers[*].Resources",
 22669  		"Containers[*].ResizePolicy[*].RestartPolicy",
 22670  		"Containers[*].ResizePolicy[*].ResourceName",
 22671  		"Containers[*].RestartPolicy",
 22672  		"Containers[*].SecurityContext.RunAsNonRoot",
 22673  		"Containers[*].Stdin",
 22674  		"Containers[*].StdinOnce",
 22675  		"Containers[*].StartupProbe",
 22676  		"Containers[*].VolumeDevices[*]",
 22677  		"Containers[*].VolumeMounts[*]",
 22678  		"Containers[*].TTY",
 22679  		"Containers[*].TerminationMessagePath",
 22680  		"Containers[*].TerminationMessagePolicy",
 22681  		"Containers[*].WorkingDir",
 22682  		"DNSPolicy",
 22683  		"EnableServiceLinks",
 22684  		"EphemeralContainers[*].EphemeralContainerCommon.Args",
 22685  		"EphemeralContainers[*].EphemeralContainerCommon.Command",
 22686  		"EphemeralContainers[*].EphemeralContainerCommon.Env",
 22687  		"EphemeralContainers[*].EphemeralContainerCommon.EnvFrom",
 22688  		"EphemeralContainers[*].EphemeralContainerCommon.Image",
 22689  		"EphemeralContainers[*].EphemeralContainerCommon.ImagePullPolicy",
 22690  		"EphemeralContainers[*].EphemeralContainerCommon.Lifecycle",
 22691  		"EphemeralContainers[*].EphemeralContainerCommon.LivenessProbe",
 22692  		"EphemeralContainers[*].EphemeralContainerCommon.Name",
 22693  		"EphemeralContainers[*].EphemeralContainerCommon.Ports",
 22694  		"EphemeralContainers[*].EphemeralContainerCommon.ReadinessProbe",
 22695  		"EphemeralContainers[*].EphemeralContainerCommon.Resources",
 22696  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy",
 22697  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName",
 22698  		"EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy",
 22699  		"EphemeralContainers[*].EphemeralContainerCommon.Stdin",
 22700  		"EphemeralContainers[*].EphemeralContainerCommon.StdinOnce",
 22701  		"EphemeralContainers[*].EphemeralContainerCommon.TTY",
 22702  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePath",
 22703  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePolicy",
 22704  		"EphemeralContainers[*].EphemeralContainerCommon.WorkingDir",
 22705  		"EphemeralContainers[*].TargetContainerName",
 22706  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsNonRoot",
 22707  		"EphemeralContainers[*].EphemeralContainerCommon.StartupProbe",
 22708  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeDevices[*]",
 22709  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeMounts[*]",
 22710  		"HostAliases",
 22711  		"Hostname",
 22712  		"ImagePullSecrets",
 22713  		"InitContainers[*].Args",
 22714  		"InitContainers[*].Command",
 22715  		"InitContainers[*].Env",
 22716  		"InitContainers[*].EnvFrom",
 22717  		"InitContainers[*].Image",
 22718  		"InitContainers[*].ImagePullPolicy",
 22719  		"InitContainers[*].Lifecycle",
 22720  		"InitContainers[*].LivenessProbe",
 22721  		"InitContainers[*].Name",
 22722  		"InitContainers[*].Ports",
 22723  		"InitContainers[*].ReadinessProbe",
 22724  		"InitContainers[*].Resources",
 22725  		"InitContainers[*].ResizePolicy[*].RestartPolicy",
 22726  		"InitContainers[*].ResizePolicy[*].ResourceName",
 22727  		"InitContainers[*].RestartPolicy",
 22728  		"InitContainers[*].Stdin",
 22729  		"InitContainers[*].StdinOnce",
 22730  		"InitContainers[*].TTY",
 22731  		"InitContainers[*].TerminationMessagePath",
 22732  		"InitContainers[*].TerminationMessagePolicy",
 22733  		"InitContainers[*].WorkingDir",
 22734  		"InitContainers[*].SecurityContext.RunAsNonRoot",
 22735  		"InitContainers[*].StartupProbe",
 22736  		"InitContainers[*].VolumeDevices[*]",
 22737  		"InitContainers[*].VolumeMounts[*]",
 22738  		"NodeName",
 22739  		"NodeSelector",
 22740  		"PreemptionPolicy",
 22741  		"Priority",
 22742  		"PriorityClassName",
 22743  		"ReadinessGates",
 22744  		"ResourceClaims[*].Name",
 22745  		"ResourceClaims[*].Source.ResourceClaimName",
 22746  		"ResourceClaims[*].Source.ResourceClaimTemplateName",
 22747  		"RestartPolicy",
 22748  		"RuntimeClassName",
 22749  		"SchedulerName",
 22750  		"SchedulingGates[*].Name",
 22751  		"SecurityContext.RunAsNonRoot",
 22752  		"ServiceAccountName",
 22753  		"SetHostnameAsFQDN",
 22754  		"Subdomain",
 22755  		"TerminationGracePeriodSeconds",
 22756  		"Volumes",
 22757  		"DNSConfig",
 22758  		"Overhead",
 22759  		"Tolerations",
 22760  		"TopologySpreadConstraints",
 22761  	)
 22762  
 22763  	expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields)
 22764  
 22765  	result := collectResourcePaths(t, expect, reflect.TypeOf(&core.PodSpec{}), nil)
 22766  
 22767  	if !expect.Equal(result) {
 22768  		// expected fields missing from result
 22769  		missing := expect.Difference(result)
 22770  		// unexpected fields in result but not specified in expect
 22771  		unexpected := result.Difference(expect)
 22772  		if len(missing) > 0 {
 22773  			t.Errorf("the following fields were expected, but missing from the result. "+
 22774  				"If the field has been removed, please remove it from the osNeutralFields set "+
 22775  				"or remove it from the osSpecificFields set, as appropriate:\n%s",
 22776  				strings.Join(missing.List(), "\n"))
 22777  		}
 22778  		if len(unexpected) > 0 {
 22779  			t.Errorf("the following fields were in the result, but unexpected. "+
 22780  				"If the field is new, please add it to the osNeutralFields set "+
 22781  				"or add it to the osSpecificFields set, as appropriate:\n%s",
 22782  				strings.Join(unexpected.List(), "\n"))
 22783  		}
 22784  	}
 22785  }
 22786  
 22787  func TestValidateSchedulingGates(t *testing.T) {
 22788  	fieldPath := field.NewPath("field")
 22789  
 22790  	tests := []struct {
 22791  		name            string
 22792  		schedulingGates []core.PodSchedulingGate
 22793  		wantFieldErrors field.ErrorList
 22794  	}{{
 22795  		name:            "nil gates",
 22796  		schedulingGates: nil,
 22797  		wantFieldErrors: field.ErrorList{},
 22798  	}, {
 22799  		name: "empty string in gates",
 22800  		schedulingGates: []core.PodSchedulingGate{
 22801  			{Name: "foo"},
 22802  			{Name: ""},
 22803  		},
 22804  		wantFieldErrors: field.ErrorList{
 22805  			field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"),
 22806  			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]')"),
 22807  		},
 22808  	}, {
 22809  		name: "legal gates",
 22810  		schedulingGates: []core.PodSchedulingGate{
 22811  			{Name: "foo"},
 22812  			{Name: "bar"},
 22813  		},
 22814  		wantFieldErrors: field.ErrorList{},
 22815  	}, {
 22816  		name: "illegal gates",
 22817  		schedulingGates: []core.PodSchedulingGate{
 22818  			{Name: "foo"},
 22819  			{Name: "\nbar"},
 22820  		},
 22821  		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]')")},
 22822  	}, {
 22823  		name: "duplicated gates (single duplication)",
 22824  		schedulingGates: []core.PodSchedulingGate{
 22825  			{Name: "foo"},
 22826  			{Name: "bar"},
 22827  			{Name: "bar"},
 22828  		},
 22829  		wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
 22830  	}, {
 22831  		name: "duplicated gates (multiple duplications)",
 22832  		schedulingGates: []core.PodSchedulingGate{
 22833  			{Name: "foo"},
 22834  			{Name: "bar"},
 22835  			{Name: "foo"},
 22836  			{Name: "baz"},
 22837  			{Name: "foo"},
 22838  			{Name: "bar"},
 22839  		},
 22840  		wantFieldErrors: field.ErrorList{
 22841  			field.Duplicate(fieldPath.Index(2), "foo"),
 22842  			field.Duplicate(fieldPath.Index(4), "foo"),
 22843  			field.Duplicate(fieldPath.Index(5), "bar"),
 22844  		},
 22845  	},
 22846  	}
 22847  	for _, tt := range tests {
 22848  		t.Run(tt.name, func(t *testing.T) {
 22849  			errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
 22850  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 22851  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 22852  			}
 22853  		})
 22854  	}
 22855  }
 22856  
 22857  // collectResourcePaths traverses the object, computing all the struct paths.
 22858  func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
 22859  	if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
 22860  		return sets.NewString(pathStr)
 22861  	}
 22862  
 22863  	paths := sets.NewString()
 22864  	switch tp.Kind() {
 22865  	case reflect.Pointer:
 22866  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path).List()...)
 22867  	case reflect.Struct:
 22868  		for i := 0; i < tp.NumField(); i++ {
 22869  			field := tp.Field(i)
 22870  			paths.Insert(collectResourcePaths(t, skipRecurseList, field.Type, path.Child(field.Name)).List()...)
 22871  		}
 22872  	case reflect.Map, reflect.Slice:
 22873  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path.Key("*")).List()...)
 22874  	case reflect.Interface:
 22875  		t.Fatalf("unexpected interface{} field %s", path.String())
 22876  	default:
 22877  		// if we hit a primitive type, we're at a leaf
 22878  		paths.Insert(path.String())
 22879  	}
 22880  	return paths
 22881  }
 22882  
 22883  func TestValidateTLSSecret(t *testing.T) {
 22884  	successCases := map[string]core.Secret{
 22885  		"empty certificate chain": {
 22886  			ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"},
 22887  			Data: map[string][]byte{
 22888  				core.TLSCertKey:       []byte("public key"),
 22889  				core.TLSPrivateKeyKey: []byte("private key"),
 22890  			},
 22891  		},
 22892  	}
 22893  	for k, v := range successCases {
 22894  		if errs := ValidateSecret(&v); len(errs) != 0 {
 22895  			t.Errorf("Expected success for %s, got %v", k, errs)
 22896  		}
 22897  	}
 22898  	errorCases := map[string]struct {
 22899  		secrets     core.Secret
 22900  		errorType   field.ErrorType
 22901  		errorDetail string
 22902  	}{
 22903  		"missing public key": {
 22904  			secrets: core.Secret{
 22905  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 22906  				Data: map[string][]byte{
 22907  					core.TLSCertKey: []byte("public key"),
 22908  				},
 22909  			},
 22910  			errorType: "FieldValueRequired",
 22911  		},
 22912  		"missing private key": {
 22913  			secrets: core.Secret{
 22914  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 22915  				Data: map[string][]byte{
 22916  					core.TLSCertKey: []byte("public key"),
 22917  				},
 22918  			},
 22919  			errorType: "FieldValueRequired",
 22920  		},
 22921  	}
 22922  	for k, v := range errorCases {
 22923  		if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22924  			t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22925  		}
 22926  	}
 22927  }
 22928  
 22929  func TestValidateLinuxSecurityContext(t *testing.T) {
 22930  	runAsUser := int64(1)
 22931  	validLinuxSC := &core.SecurityContext{
 22932  		Privileged: utilpointer.Bool(false),
 22933  		Capabilities: &core.Capabilities{
 22934  			Add:  []core.Capability{"foo"},
 22935  			Drop: []core.Capability{"bar"},
 22936  		},
 22937  		SELinuxOptions: &core.SELinuxOptions{
 22938  			User:  "user",
 22939  			Role:  "role",
 22940  			Type:  "type",
 22941  			Level: "level",
 22942  		},
 22943  		RunAsUser: &runAsUser,
 22944  	}
 22945  	invalidLinuxSC := &core.SecurityContext{
 22946  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
 22947  	}
 22948  	cases := map[string]struct {
 22949  		sc          *core.PodSpec
 22950  		expectErr   bool
 22951  		errorType   field.ErrorType
 22952  		errorDetail string
 22953  	}{
 22954  		"valid SC, linux, no error": {
 22955  			sc:        &core.PodSpec{Containers: []core.Container{{SecurityContext: validLinuxSC}}},
 22956  			expectErr: false,
 22957  		},
 22958  		"invalid SC, linux, error": {
 22959  			sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: invalidLinuxSC}}},
 22960  			errorType:   "FieldValueForbidden",
 22961  			errorDetail: "windows options cannot be set for a linux pod",
 22962  			expectErr:   true,
 22963  		},
 22964  	}
 22965  	for k, v := range cases {
 22966  		t.Run(k, func(t *testing.T) {
 22967  			errs := validateLinux(v.sc, field.NewPath("field"))
 22968  			if v.expectErr && len(errs) > 0 {
 22969  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22970  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22971  				}
 22972  			} else if v.expectErr && len(errs) == 0 {
 22973  				t.Errorf("Unexpected success")
 22974  			}
 22975  			if !v.expectErr && len(errs) != 0 {
 22976  				t.Errorf("Unexpected error(s): %v", errs)
 22977  			}
 22978  		})
 22979  	}
 22980  }
 22981  
 22982  func TestValidateSecurityContext(t *testing.T) {
 22983  	runAsUser := int64(1)
 22984  	fullValidSC := func() *core.SecurityContext {
 22985  		return &core.SecurityContext{
 22986  			Privileged: utilpointer.Bool(false),
 22987  			Capabilities: &core.Capabilities{
 22988  				Add:  []core.Capability{"foo"},
 22989  				Drop: []core.Capability{"bar"},
 22990  			},
 22991  			SELinuxOptions: &core.SELinuxOptions{
 22992  				User:  "user",
 22993  				Role:  "role",
 22994  				Type:  "type",
 22995  				Level: "level",
 22996  			},
 22997  			RunAsUser: &runAsUser,
 22998  		}
 22999  	}
 23000  
 23001  	// setup data
 23002  	allSettings := fullValidSC()
 23003  	noCaps := fullValidSC()
 23004  	noCaps.Capabilities = nil
 23005  
 23006  	noSELinux := fullValidSC()
 23007  	noSELinux.SELinuxOptions = nil
 23008  
 23009  	noPrivRequest := fullValidSC()
 23010  	noPrivRequest.Privileged = nil
 23011  
 23012  	noRunAsUser := fullValidSC()
 23013  	noRunAsUser.RunAsUser = nil
 23014  
 23015  	procMountSet := fullValidSC()
 23016  	defPmt := core.DefaultProcMount
 23017  	procMountSet.ProcMount = &defPmt
 23018  
 23019  	umPmt := core.UnmaskedProcMount
 23020  	procMountUnmasked := fullValidSC()
 23021  	procMountUnmasked.ProcMount = &umPmt
 23022  
 23023  	successCases := map[string]struct {
 23024  		sc        *core.SecurityContext
 23025  		hostUsers bool
 23026  	}{
 23027  		"all settings":        {allSettings, false},
 23028  		"no capabilities":     {noCaps, false},
 23029  		"no selinux":          {noSELinux, false},
 23030  		"no priv request":     {noPrivRequest, false},
 23031  		"no run as user":      {noRunAsUser, false},
 23032  		"proc mount set":      {procMountSet, true},
 23033  		"proc mount unmasked": {procMountUnmasked, false},
 23034  	}
 23035  	for k, v := range successCases {
 23036  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) != 0 {
 23037  			t.Errorf("[%s] Expected success, got %v", k, errs)
 23038  		}
 23039  	}
 23040  
 23041  	privRequestWithGlobalDeny := fullValidSC()
 23042  	privRequestWithGlobalDeny.Privileged = utilpointer.Bool(true)
 23043  
 23044  	negativeRunAsUser := fullValidSC()
 23045  	negativeUser := int64(-1)
 23046  	negativeRunAsUser.RunAsUser = &negativeUser
 23047  
 23048  	privWithoutEscalation := fullValidSC()
 23049  	privWithoutEscalation.Privileged = utilpointer.Bool(true)
 23050  	privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 23051  
 23052  	capSysAdminWithoutEscalation := fullValidSC()
 23053  	capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
 23054  	capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 23055  
 23056  	errorCases := map[string]struct {
 23057  		sc           *core.SecurityContext
 23058  		errorType    field.ErrorType
 23059  		errorDetail  string
 23060  		capAllowPriv bool
 23061  	}{
 23062  		"request privileged when capabilities forbids": {
 23063  			sc:          privRequestWithGlobalDeny,
 23064  			errorType:   "FieldValueForbidden",
 23065  			errorDetail: "disallowed by cluster policy",
 23066  		},
 23067  		"negative RunAsUser": {
 23068  			sc:          negativeRunAsUser,
 23069  			errorType:   "FieldValueInvalid",
 23070  			errorDetail: "must be between",
 23071  		},
 23072  		"with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
 23073  			sc:          capSysAdminWithoutEscalation,
 23074  			errorType:   "FieldValueInvalid",
 23075  			errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
 23076  		},
 23077  		"with privileged and allowPrivilegeEscalation false": {
 23078  			sc:           privWithoutEscalation,
 23079  			errorType:    "FieldValueInvalid",
 23080  			errorDetail:  "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
 23081  			capAllowPriv: true,
 23082  		},
 23083  		"with unmasked proc mount type and no user namespace": {
 23084  			sc:          procMountUnmasked,
 23085  			errorType:   "FieldValueInvalid",
 23086  			errorDetail: "`hostUsers` must be false to use `Unmasked`",
 23087  		},
 23088  	}
 23089  	for k, v := range errorCases {
 23090  		capabilities.SetForTests(capabilities.Capabilities{
 23091  			AllowPrivileged: v.capAllowPriv,
 23092  		})
 23093  		// note the unconditional `true` here for hostUsers. The failure case to test for ProcMount only includes it being true,
 23094  		// and the field is ignored if ProcMount isn't set. Thus, we can unconditionally set to `true` and simplify the test matrix setup.
 23095  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), true); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 23096  			t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 23097  		}
 23098  	}
 23099  }
 23100  
 23101  func fakeValidSecurityContext(priv bool) *core.SecurityContext {
 23102  	return &core.SecurityContext{
 23103  		Privileged: &priv,
 23104  	}
 23105  }
 23106  
 23107  func TestValidPodLogOptions(t *testing.T) {
 23108  	now := metav1.Now()
 23109  	negative := int64(-1)
 23110  	zero := int64(0)
 23111  	positive := int64(1)
 23112  	tests := []struct {
 23113  		opt  core.PodLogOptions
 23114  		errs int
 23115  	}{
 23116  		{core.PodLogOptions{}, 0},
 23117  		{core.PodLogOptions{Previous: true}, 0},
 23118  		{core.PodLogOptions{Follow: true}, 0},
 23119  		{core.PodLogOptions{TailLines: &zero}, 0},
 23120  		{core.PodLogOptions{TailLines: &negative}, 1},
 23121  		{core.PodLogOptions{TailLines: &positive}, 0},
 23122  		{core.PodLogOptions{LimitBytes: &zero}, 1},
 23123  		{core.PodLogOptions{LimitBytes: &negative}, 1},
 23124  		{core.PodLogOptions{LimitBytes: &positive}, 0},
 23125  		{core.PodLogOptions{SinceSeconds: &negative}, 1},
 23126  		{core.PodLogOptions{SinceSeconds: &positive}, 0},
 23127  		{core.PodLogOptions{SinceSeconds: &zero}, 1},
 23128  		{core.PodLogOptions{SinceTime: &now}, 0},
 23129  	}
 23130  	for i, test := range tests {
 23131  		errs := ValidatePodLogOptions(&test.opt)
 23132  		if test.errs != len(errs) {
 23133  			t.Errorf("%d: Unexpected errors: %v", i, errs)
 23134  		}
 23135  	}
 23136  }
 23137  
 23138  func TestValidateConfigMap(t *testing.T) {
 23139  	newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap {
 23140  		return core.ConfigMap{
 23141  			ObjectMeta: metav1.ObjectMeta{
 23142  				Name:      name,
 23143  				Namespace: namespace,
 23144  			},
 23145  			Data:       data,
 23146  			BinaryData: binaryData,
 23147  		}
 23148  	}
 23149  
 23150  	var (
 23151  		validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")})
 23152  		maxKeyLength   = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil)
 23153  
 23154  		emptyName               = newConfigMap("", "validns", nil, nil)
 23155  		invalidName             = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil)
 23156  		emptyNs                 = newConfigMap("validname", "", nil, nil)
 23157  		invalidNs               = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil)
 23158  		invalidKey              = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil)
 23159  		leadingDotKey           = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil)
 23160  		dotKey                  = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil)
 23161  		doubleDotKey            = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil)
 23162  		overMaxKeyLength        = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil)
 23163  		overMaxSize             = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil)
 23164  		duplicatedKey           = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")})
 23165  		binDataInvalidKey       = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")})
 23166  		binDataLeadingDotKey    = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")})
 23167  		binDataDotKey           = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")})
 23168  		binDataDoubleDotKey     = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")})
 23169  		binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")})
 23170  		binDataOverMaxSize      = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)})
 23171  		binNonUtf8Value         = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}})
 23172  	)
 23173  
 23174  	tests := map[string]struct {
 23175  		cfg     core.ConfigMap
 23176  		isValid bool
 23177  	}{
 23178  		"valid":                           {validConfigMap, true},
 23179  		"max key length":                  {maxKeyLength, true},
 23180  		"leading dot key":                 {leadingDotKey, true},
 23181  		"empty name":                      {emptyName, false},
 23182  		"invalid name":                    {invalidName, false},
 23183  		"invalid key":                     {invalidKey, false},
 23184  		"empty namespace":                 {emptyNs, false},
 23185  		"invalid namespace":               {invalidNs, false},
 23186  		"dot key":                         {dotKey, false},
 23187  		"double dot key":                  {doubleDotKey, false},
 23188  		"over max key length":             {overMaxKeyLength, false},
 23189  		"over max size":                   {overMaxSize, false},
 23190  		"duplicated key":                  {duplicatedKey, false},
 23191  		"binary data invalid key":         {binDataInvalidKey, false},
 23192  		"binary data leading dot key":     {binDataLeadingDotKey, true},
 23193  		"binary data dot key":             {binDataDotKey, false},
 23194  		"binary data double dot key":      {binDataDoubleDotKey, false},
 23195  		"binary data over max key length": {binDataOverMaxKeyLength, false},
 23196  		"binary data max size":            {binDataOverMaxSize, false},
 23197  		"binary data non utf-8 bytes":     {binNonUtf8Value, true},
 23198  	}
 23199  
 23200  	for name, tc := range tests {
 23201  		errs := ValidateConfigMap(&tc.cfg)
 23202  		if tc.isValid && len(errs) > 0 {
 23203  			t.Errorf("%v: unexpected error: %v", name, errs)
 23204  		}
 23205  		if !tc.isValid && len(errs) == 0 {
 23206  			t.Errorf("%v: unexpected non-error", name)
 23207  		}
 23208  	}
 23209  }
 23210  
 23211  func TestValidateConfigMapUpdate(t *testing.T) {
 23212  	newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap {
 23213  		return core.ConfigMap{
 23214  			ObjectMeta: metav1.ObjectMeta{
 23215  				Name:            name,
 23216  				Namespace:       namespace,
 23217  				ResourceVersion: version,
 23218  			},
 23219  			Data: data,
 23220  		}
 23221  	}
 23222  	validConfigMap := func() core.ConfigMap {
 23223  		return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
 23224  	}
 23225  
 23226  	falseVal := false
 23227  	trueVal := true
 23228  
 23229  	configMap := validConfigMap()
 23230  	immutableConfigMap := validConfigMap()
 23231  	immutableConfigMap.Immutable = &trueVal
 23232  	mutableConfigMap := validConfigMap()
 23233  	mutableConfigMap.Immutable = &falseVal
 23234  
 23235  	configMapWithData := validConfigMap()
 23236  	configMapWithData.Data["key-2"] = "value-2"
 23237  	immutableConfigMapWithData := validConfigMap()
 23238  	immutableConfigMapWithData.Immutable = &trueVal
 23239  	immutableConfigMapWithData.Data["key-2"] = "value-2"
 23240  
 23241  	configMapWithChangedData := validConfigMap()
 23242  	configMapWithChangedData.Data["key"] = "foo"
 23243  	immutableConfigMapWithChangedData := validConfigMap()
 23244  	immutableConfigMapWithChangedData.Immutable = &trueVal
 23245  	immutableConfigMapWithChangedData.Data["key"] = "foo"
 23246  
 23247  	noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
 23248  
 23249  	cases := []struct {
 23250  		name   string
 23251  		newCfg core.ConfigMap
 23252  		oldCfg core.ConfigMap
 23253  		valid  bool
 23254  	}{{
 23255  		name:   "valid",
 23256  		newCfg: configMap,
 23257  		oldCfg: configMap,
 23258  		valid:  true,
 23259  	}, {
 23260  		name:   "invalid",
 23261  		newCfg: noVersion,
 23262  		oldCfg: configMap,
 23263  		valid:  false,
 23264  	}, {
 23265  		name:   "mark configmap immutable",
 23266  		oldCfg: configMap,
 23267  		newCfg: immutableConfigMap,
 23268  		valid:  true,
 23269  	}, {
 23270  		name:   "revert immutable configmap",
 23271  		oldCfg: immutableConfigMap,
 23272  		newCfg: configMap,
 23273  		valid:  false,
 23274  	}, {
 23275  		name:   "mark immutable configmap mutable",
 23276  		oldCfg: immutableConfigMap,
 23277  		newCfg: mutableConfigMap,
 23278  		valid:  false,
 23279  	}, {
 23280  		name:   "add data in configmap",
 23281  		oldCfg: configMap,
 23282  		newCfg: configMapWithData,
 23283  		valid:  true,
 23284  	}, {
 23285  		name:   "add data in immutable configmap",
 23286  		oldCfg: immutableConfigMap,
 23287  		newCfg: immutableConfigMapWithData,
 23288  		valid:  false,
 23289  	}, {
 23290  		name:   "change data in configmap",
 23291  		oldCfg: configMap,
 23292  		newCfg: configMapWithChangedData,
 23293  		valid:  true,
 23294  	}, {
 23295  		name:   "change data in immutable configmap",
 23296  		oldCfg: immutableConfigMap,
 23297  		newCfg: immutableConfigMapWithChangedData,
 23298  		valid:  false,
 23299  	},
 23300  	}
 23301  
 23302  	for _, tc := range cases {
 23303  		t.Run(tc.name, func(t *testing.T) {
 23304  			errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
 23305  			if tc.valid && len(errs) > 0 {
 23306  				t.Errorf("Unexpected error: %v", errs)
 23307  			}
 23308  			if !tc.valid && len(errs) == 0 {
 23309  				t.Errorf("Unexpected lack of error")
 23310  			}
 23311  		})
 23312  	}
 23313  }
 23314  
 23315  func TestValidateHasLabel(t *testing.T) {
 23316  	successCase := metav1.ObjectMeta{
 23317  		Name:      "123",
 23318  		Namespace: "ns",
 23319  		Labels: map[string]string{
 23320  			"other": "blah",
 23321  			"foo":   "bar",
 23322  		},
 23323  	}
 23324  	if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 {
 23325  		t.Errorf("expected success: %v", errs)
 23326  	}
 23327  
 23328  	missingCase := metav1.ObjectMeta{
 23329  		Name:      "123",
 23330  		Namespace: "ns",
 23331  		Labels: map[string]string{
 23332  			"other": "blah",
 23333  		},
 23334  	}
 23335  	if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 23336  		t.Errorf("expected failure")
 23337  	}
 23338  
 23339  	wrongValueCase := metav1.ObjectMeta{
 23340  		Name:      "123",
 23341  		Namespace: "ns",
 23342  		Labels: map[string]string{
 23343  			"other": "blah",
 23344  			"foo":   "notbar",
 23345  		},
 23346  	}
 23347  	if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 23348  		t.Errorf("expected failure")
 23349  	}
 23350  }
 23351  
 23352  func TestIsValidSysctlName(t *testing.T) {
 23353  	valid := []string{
 23354  		"a.b.c.d",
 23355  		"a",
 23356  		"a_b",
 23357  		"a-b",
 23358  		"abc",
 23359  		"abc.def",
 23360  		"a/b/c/d",
 23361  		"a/b.c",
 23362  	}
 23363  	invalid := []string{
 23364  		"",
 23365  		"*",
 23366  		"ä",
 23367  		"a_",
 23368  		"_",
 23369  		"__",
 23370  		"_a",
 23371  		"_a._b",
 23372  		"-",
 23373  		".",
 23374  		"a.",
 23375  		".a",
 23376  		"a.b.",
 23377  		"a*.b",
 23378  		"a*b",
 23379  		"*a",
 23380  		"a.*",
 23381  		"*",
 23382  		"abc*",
 23383  		"a.abc*",
 23384  		"a.b.*",
 23385  		"Abc",
 23386  		"/",
 23387  		"/a",
 23388  		"a/abc*",
 23389  		"a/b/*",
 23390  		func(n int) string {
 23391  			x := make([]byte, n)
 23392  			for i := range x {
 23393  				x[i] = byte('a')
 23394  			}
 23395  			return string(x)
 23396  		}(256),
 23397  	}
 23398  
 23399  	for _, s := range valid {
 23400  		if !IsValidSysctlName(s) {
 23401  			t.Errorf("%q expected to be a valid sysctl name", s)
 23402  		}
 23403  	}
 23404  	for _, s := range invalid {
 23405  		if IsValidSysctlName(s) {
 23406  			t.Errorf("%q expected to be an invalid sysctl name", s)
 23407  		}
 23408  	}
 23409  }
 23410  
 23411  func TestValidateSysctls(t *testing.T) {
 23412  	valid := []string{
 23413  		"net.foo.bar",
 23414  		"kernel.shmmax",
 23415  		"net.ipv4.conf.enp3s0/200.forwarding",
 23416  		"net/ipv4/conf/enp3s0.200/forwarding",
 23417  	}
 23418  	invalid := []string{
 23419  		"i..nvalid",
 23420  		"_invalid",
 23421  	}
 23422  
 23423  	invalidWithHostNet := []string{
 23424  		"net.ipv4.conf.enp3s0/200.forwarding",
 23425  		"net/ipv4/conf/enp3s0.200/forwarding",
 23426  	}
 23427  
 23428  	invalidWithHostIPC := []string{
 23429  		"kernel.shmmax",
 23430  		"kernel.msgmax",
 23431  	}
 23432  
 23433  	duplicates := []string{
 23434  		"kernel.shmmax",
 23435  		"kernel.shmmax",
 23436  	}
 23437  	opts := PodValidationOptions{
 23438  		AllowNamespacedSysctlsForHostNetAndHostIPC: false,
 23439  	}
 23440  
 23441  	sysctls := make([]core.Sysctl, len(valid))
 23442  	validSecurityContext := &core.PodSecurityContext{
 23443  		Sysctls: sysctls,
 23444  	}
 23445  	for i, sysctl := range valid {
 23446  		sysctls[i].Name = sysctl
 23447  	}
 23448  	errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts)
 23449  	if len(errs) != 0 {
 23450  		t.Errorf("unexpected validation errors: %v", errs)
 23451  	}
 23452  
 23453  	sysctls = make([]core.Sysctl, len(invalid))
 23454  	for i, sysctl := range invalid {
 23455  		sysctls[i].Name = sysctl
 23456  	}
 23457  	inValidSecurityContext := &core.PodSecurityContext{
 23458  		Sysctls: sysctls,
 23459  	}
 23460  	errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts)
 23461  	if len(errs) != 2 {
 23462  		t.Errorf("expected 2 validation errors. Got: %v", errs)
 23463  	} else {
 23464  		if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) {
 23465  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 23466  		}
 23467  		if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) {
 23468  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 23469  		}
 23470  	}
 23471  
 23472  	sysctls = make([]core.Sysctl, len(duplicates))
 23473  	for i, sysctl := range duplicates {
 23474  		sysctls[i].Name = sysctl
 23475  	}
 23476  	securityContextWithDup := &core.PodSecurityContext{
 23477  		Sysctls: sysctls,
 23478  	}
 23479  	errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts)
 23480  	if len(errs) != 1 {
 23481  		t.Errorf("unexpected validation errors: %v", errs)
 23482  	} else if errs[0].Type != field.ErrorTypeDuplicate {
 23483  		t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
 23484  	}
 23485  
 23486  	sysctls = make([]core.Sysctl, len(invalidWithHostNet))
 23487  	for i, sysctl := range invalidWithHostNet {
 23488  		sysctls[i].Name = sysctl
 23489  	}
 23490  	invalidSecurityContextWithHostNet := &core.PodSecurityContext{
 23491  		Sysctls:     sysctls,
 23492  		HostIPC:     false,
 23493  		HostNetwork: true,
 23494  	}
 23495  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 23496  	if len(errs) != 2 {
 23497  		t.Errorf("unexpected validation errors: %v", errs)
 23498  	}
 23499  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 23500  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 23501  	if len(errs) != 0 {
 23502  		t.Errorf("unexpected validation errors: %v", errs)
 23503  	}
 23504  
 23505  	sysctls = make([]core.Sysctl, len(invalidWithHostIPC))
 23506  	for i, sysctl := range invalidWithHostIPC {
 23507  		sysctls[i].Name = sysctl
 23508  	}
 23509  	invalidSecurityContextWithHostIPC := &core.PodSecurityContext{
 23510  		Sysctls:     sysctls,
 23511  		HostIPC:     true,
 23512  		HostNetwork: false,
 23513  	}
 23514  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false
 23515  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 23516  	if len(errs) != 2 {
 23517  		t.Errorf("unexpected validation errors: %v", errs)
 23518  	}
 23519  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 23520  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 23521  	if len(errs) != 0 {
 23522  		t.Errorf("unexpected validation errors: %v", errs)
 23523  	}
 23524  }
 23525  
 23526  func newNodeNameEndpoint(nodeName string) *core.Endpoints {
 23527  	ep := &core.Endpoints{
 23528  		ObjectMeta: metav1.ObjectMeta{
 23529  			Name:            "foo",
 23530  			Namespace:       metav1.NamespaceDefault,
 23531  			ResourceVersion: "1",
 23532  		},
 23533  		Subsets: []core.EndpointSubset{{
 23534  			NotReadyAddresses: []core.EndpointAddress{},
 23535  			Ports:             []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}},
 23536  			Addresses: []core.EndpointAddress{{
 23537  				IP:       "8.8.8.8",
 23538  				Hostname: "zookeeper1",
 23539  				NodeName: &nodeName}}}}}
 23540  	return ep
 23541  }
 23542  
 23543  func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
 23544  	oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend")
 23545  	updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
 23546  	// Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused.
 23547  	// The same ip will now have a different nodeName.
 23548  	errList := ValidateEndpoints(updatedEndpoint)
 23549  	errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
 23550  	if len(errList) != 0 {
 23551  		t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
 23552  	}
 23553  }
 23554  
 23555  func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
 23556  	// Check NodeName DNS validation
 23557  	endpoint := newNodeNameEndpoint("illegal*.nodename")
 23558  	errList := ValidateEndpoints(endpoint)
 23559  	if len(errList) == 0 {
 23560  		t.Error("Endpoint should reject invalid NodeName")
 23561  	}
 23562  }
 23563  
 23564  func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
 23565  	endpoint := newNodeNameEndpoint("10.10.1.1")
 23566  	errList := ValidateEndpoints(endpoint)
 23567  	if len(errList) != 0 {
 23568  		t.Error("Endpoint should accept a NodeName that is an IP address")
 23569  	}
 23570  }
 23571  
 23572  func TestValidateFlexVolumeSource(t *testing.T) {
 23573  	testcases := map[string]struct {
 23574  		source       *core.FlexVolumeSource
 23575  		expectedErrs map[string]string
 23576  	}{
 23577  		"valid": {
 23578  			source:       &core.FlexVolumeSource{Driver: "foo"},
 23579  			expectedErrs: map[string]string{},
 23580  		},
 23581  		"valid with options": {
 23582  			source:       &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
 23583  			expectedErrs: map[string]string{},
 23584  		},
 23585  		"no driver": {
 23586  			source:       &core.FlexVolumeSource{Driver: ""},
 23587  			expectedErrs: map[string]string{"driver": "Required value"},
 23588  		},
 23589  		"reserved option keys": {
 23590  			source: &core.FlexVolumeSource{
 23591  				Driver: "foo",
 23592  				Options: map[string]string{
 23593  					// valid options
 23594  					"myns.io":               "A",
 23595  					"myns.io/bar":           "A",
 23596  					"myns.io/kubernetes.io": "A",
 23597  
 23598  					// invalid options
 23599  					"KUBERNETES.IO":     "A",
 23600  					"kubernetes.io":     "A",
 23601  					"kubernetes.io/":    "A",
 23602  					"kubernetes.io/foo": "A",
 23603  
 23604  					"alpha.kubernetes.io":     "A",
 23605  					"alpha.kubernetes.io/":    "A",
 23606  					"alpha.kubernetes.io/foo": "A",
 23607  
 23608  					"k8s.io":     "A",
 23609  					"k8s.io/":    "A",
 23610  					"k8s.io/foo": "A",
 23611  
 23612  					"alpha.k8s.io":     "A",
 23613  					"alpha.k8s.io/":    "A",
 23614  					"alpha.k8s.io/foo": "A",
 23615  				},
 23616  			},
 23617  			expectedErrs: map[string]string{
 23618  				"options[KUBERNETES.IO]":           "reserved",
 23619  				"options[kubernetes.io]":           "reserved",
 23620  				"options[kubernetes.io/]":          "reserved",
 23621  				"options[kubernetes.io/foo]":       "reserved",
 23622  				"options[alpha.kubernetes.io]":     "reserved",
 23623  				"options[alpha.kubernetes.io/]":    "reserved",
 23624  				"options[alpha.kubernetes.io/foo]": "reserved",
 23625  				"options[k8s.io]":                  "reserved",
 23626  				"options[k8s.io/]":                 "reserved",
 23627  				"options[k8s.io/foo]":              "reserved",
 23628  				"options[alpha.k8s.io]":            "reserved",
 23629  				"options[alpha.k8s.io/]":           "reserved",
 23630  				"options[alpha.k8s.io/foo]":        "reserved",
 23631  			},
 23632  		},
 23633  	}
 23634  
 23635  	for k, tc := range testcases {
 23636  		errs := validateFlexVolumeSource(tc.source, nil)
 23637  		for _, err := range errs {
 23638  			expectedErr, ok := tc.expectedErrs[err.Field]
 23639  			if !ok {
 23640  				t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
 23641  				continue
 23642  			}
 23643  			if !strings.Contains(err.Error(), expectedErr) {
 23644  				t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
 23645  				continue
 23646  			}
 23647  		}
 23648  		if len(errs) != len(tc.expectedErrs) {
 23649  			t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
 23650  			continue
 23651  		}
 23652  	}
 23653  }
 23654  
 23655  func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
 23656  	successCases := map[string]*core.SessionAffinityConfig{
 23657  		"non-empty config, valid timeout: 1": {
 23658  			ClientIP: &core.ClientIPConfig{
 23659  				TimeoutSeconds: utilpointer.Int32(1),
 23660  			},
 23661  		},
 23662  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": {
 23663  			ClientIP: &core.ClientIPConfig{
 23664  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds - 1),
 23665  			},
 23666  		},
 23667  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": {
 23668  			ClientIP: &core.ClientIPConfig{
 23669  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds),
 23670  			},
 23671  		},
 23672  	}
 23673  
 23674  	for name, test := range successCases {
 23675  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
 23676  			t.Errorf("case: %s, expected success: %v", name, errs)
 23677  		}
 23678  	}
 23679  
 23680  	errorCases := map[string]*core.SessionAffinityConfig{
 23681  		"empty session affinity config": nil,
 23682  		"empty client IP config": {
 23683  			ClientIP: nil,
 23684  		},
 23685  		"empty timeoutSeconds": {
 23686  			ClientIP: &core.ClientIPConfig{
 23687  				TimeoutSeconds: nil,
 23688  			},
 23689  		},
 23690  		"non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": {
 23691  			ClientIP: &core.ClientIPConfig{
 23692  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds + 1),
 23693  			},
 23694  		},
 23695  		"non-empty config, invalid timeout: -1": {
 23696  			ClientIP: &core.ClientIPConfig{
 23697  				TimeoutSeconds: utilpointer.Int32(-1),
 23698  			},
 23699  		},
 23700  		"non-empty config, invalid timeout: 0": {
 23701  			ClientIP: &core.ClientIPConfig{
 23702  				TimeoutSeconds: utilpointer.Int32(0),
 23703  			},
 23704  		},
 23705  	}
 23706  
 23707  	for name, test := range errorCases {
 23708  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
 23709  			t.Errorf("case: %v, expected failures: %v", name, errs)
 23710  		}
 23711  	}
 23712  }
 23713  
 23714  func TestValidateWindowsSecurityContextOptions(t *testing.T) {
 23715  	toPtr := func(s string) *string {
 23716  		return &s
 23717  	}
 23718  
 23719  	testCases := []struct {
 23720  		testName string
 23721  
 23722  		windowsOptions         *core.WindowsSecurityContextOptions
 23723  		expectedErrorSubstring string
 23724  	}{{
 23725  		testName: "a nil pointer",
 23726  	}, {
 23727  		testName:       "an empty struct",
 23728  		windowsOptions: &core.WindowsSecurityContextOptions{},
 23729  	}, {
 23730  		testName: "a valid input",
 23731  		windowsOptions: &core.WindowsSecurityContextOptions{
 23732  			GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"),
 23733  			GMSACredentialSpec:     toPtr("dummy-gmsa-crep-spec-contents"),
 23734  		},
 23735  	}, {
 23736  		testName: "a GMSA cred spec name that is not a valid resource name",
 23737  		windowsOptions: &core.WindowsSecurityContextOptions{
 23738  			// invalid because of the underscore
 23739  			GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"),
 23740  		},
 23741  		expectedErrorSubstring: dnsSubdomainLabelErrMsg,
 23742  	}, {
 23743  		testName: "empty GMSA cred spec contents",
 23744  		windowsOptions: &core.WindowsSecurityContextOptions{
 23745  			GMSACredentialSpec: toPtr(""),
 23746  		},
 23747  		expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string",
 23748  	}, {
 23749  		testName: "GMSA cred spec contents that are too long",
 23750  		windowsOptions: &core.WindowsSecurityContextOptions{
 23751  			GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)),
 23752  		},
 23753  		expectedErrorSubstring: "gmsaCredentialSpec size must be under",
 23754  	}, {
 23755  		testName: "RunAsUserName is nil",
 23756  		windowsOptions: &core.WindowsSecurityContextOptions{
 23757  			RunAsUserName: nil,
 23758  		},
 23759  	}, {
 23760  		testName: "a valid RunAsUserName",
 23761  		windowsOptions: &core.WindowsSecurityContextOptions{
 23762  			RunAsUserName: toPtr("Container. User"),
 23763  		},
 23764  	}, {
 23765  		testName: "a valid RunAsUserName with NetBios Domain",
 23766  		windowsOptions: &core.WindowsSecurityContextOptions{
 23767  			RunAsUserName: toPtr("Network Service\\Container. User"),
 23768  		},
 23769  	}, {
 23770  		testName: "a valid RunAsUserName with DNS Domain",
 23771  		windowsOptions: &core.WindowsSecurityContextOptions{
 23772  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"),
 23773  		},
 23774  	}, {
 23775  		testName: "a valid RunAsUserName with DNS Domain with a single character segment",
 23776  		windowsOptions: &core.WindowsSecurityContextOptions{
 23777  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"),
 23778  		},
 23779  	}, {
 23780  		testName: "a valid RunAsUserName with a long single segment DNS Domain",
 23781  		windowsOptions: &core.WindowsSecurityContextOptions{
 23782  			RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"),
 23783  		},
 23784  	}, {
 23785  		testName: "an empty RunAsUserName",
 23786  		windowsOptions: &core.WindowsSecurityContextOptions{
 23787  			RunAsUserName: toPtr(""),
 23788  		},
 23789  		expectedErrorSubstring: "runAsUserName cannot be an empty string",
 23790  	}, {
 23791  		testName: "RunAsUserName containing a control character",
 23792  		windowsOptions: &core.WindowsSecurityContextOptions{
 23793  			RunAsUserName: toPtr("Container\tUser"),
 23794  		},
 23795  		expectedErrorSubstring: "runAsUserName cannot contain control characters",
 23796  	}, {
 23797  		testName: "RunAsUserName containing too many backslashes",
 23798  		windowsOptions: &core.WindowsSecurityContextOptions{
 23799  			RunAsUserName: toPtr("Container\\Foo\\Lish"),
 23800  		},
 23801  		expectedErrorSubstring: "runAsUserName cannot contain more than one backslash",
 23802  	}, {
 23803  		testName: "RunAsUserName containing backslash but empty Domain",
 23804  		windowsOptions: &core.WindowsSecurityContextOptions{
 23805  			RunAsUserName: toPtr("\\User"),
 23806  		},
 23807  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 23808  	}, {
 23809  		testName: "RunAsUserName containing backslash but empty User",
 23810  		windowsOptions: &core.WindowsSecurityContextOptions{
 23811  			RunAsUserName: toPtr("Container\\"),
 23812  		},
 23813  		expectedErrorSubstring: "runAsUserName's User cannot be empty",
 23814  	}, {
 23815  		testName: "RunAsUserName's NetBios Domain is too long",
 23816  		windowsOptions: &core.WindowsSecurityContextOptions{
 23817  			RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"),
 23818  		},
 23819  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23820  	}, {
 23821  		testName: "RunAsUserName's DNS Domain is too long",
 23822  		windowsOptions: &core.WindowsSecurityContextOptions{
 23823  			// even if this tests the max Domain length, the Domain should still be "valid".
 23824  			RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"),
 23825  		},
 23826  		expectedErrorSubstring: "runAsUserName's Domain length must be under",
 23827  	}, {
 23828  		testName: "RunAsUserName's User is too long",
 23829  		windowsOptions: &core.WindowsSecurityContextOptions{
 23830  			RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)),
 23831  		},
 23832  		expectedErrorSubstring: "runAsUserName's User length must not be longer than",
 23833  	}, {
 23834  		testName: "RunAsUserName's User cannot contain only spaces or periods",
 23835  		windowsOptions: &core.WindowsSecurityContextOptions{
 23836  			RunAsUserName: toPtr("... ..."),
 23837  		},
 23838  		expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces",
 23839  	}, {
 23840  		testName: "RunAsUserName's NetBios Domain cannot start with a dot",
 23841  		windowsOptions: &core.WindowsSecurityContextOptions{
 23842  			RunAsUserName: toPtr(".FooLish\\User"),
 23843  		},
 23844  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23845  	}, {
 23846  		testName: "RunAsUserName's NetBios Domain cannot contain invalid characters",
 23847  		windowsOptions: &core.WindowsSecurityContextOptions{
 23848  			RunAsUserName: toPtr("Foo? Lish?\\User"),
 23849  		},
 23850  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 23851  	}, {
 23852  		testName: "RunAsUserName's DNS Domain cannot contain invalid characters",
 23853  		windowsOptions: &core.WindowsSecurityContextOptions{
 23854  			RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"),
 23855  		},
 23856  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 23857  	}, {
 23858  		testName: "RunAsUserName's User cannot contain invalid characters",
 23859  		windowsOptions: &core.WindowsSecurityContextOptions{
 23860  			RunAsUserName: toPtr("Container/User"),
 23861  		},
 23862  		expectedErrorSubstring: "runAsUserName's User cannot contain the following characters",
 23863  	},
 23864  	}
 23865  
 23866  	for _, testCase := range testCases {
 23867  		t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) {
 23868  			errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field"))
 23869  
 23870  			switch len(errs) {
 23871  			case 0:
 23872  				if testCase.expectedErrorSubstring != "" {
 23873  					t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring)
 23874  				}
 23875  			case 1:
 23876  				if testCase.expectedErrorSubstring == "" {
 23877  					t.Errorf("didn't expect a failure, got: %q", errs[0].Error())
 23878  				} else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) {
 23879  					t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error())
 23880  				}
 23881  			default:
 23882  				t.Errorf("got %d failures", len(errs))
 23883  				for i, err := range errs {
 23884  					t.Errorf("error %d: %q", i, err.Error())
 23885  				}
 23886  			}
 23887  		})
 23888  	}
 23889  }
 23890  
 23891  func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec {
 23892  	scName := "csi-plugin"
 23893  	dataSourceInSpec := core.PersistentVolumeClaimSpec{
 23894  		AccessModes: []core.PersistentVolumeAccessMode{
 23895  			core.ReadOnlyMany,
 23896  		},
 23897  		Resources: core.VolumeResourceRequirements{
 23898  			Requests: core.ResourceList{
 23899  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 23900  			},
 23901  		},
 23902  		StorageClassName: &scName,
 23903  		DataSource: &core.TypedLocalObjectReference{
 23904  			APIGroup: &apiGroup,
 23905  			Kind:     kind,
 23906  			Name:     name,
 23907  		},
 23908  	}
 23909  
 23910  	return &dataSourceInSpec
 23911  }
 23912  
 23913  func TestAlphaVolumePVCDataSource(t *testing.T) {
 23914  	testCases := []struct {
 23915  		testName     string
 23916  		claimSpec    core.PersistentVolumeClaimSpec
 23917  		expectedFail bool
 23918  	}{{
 23919  		testName:  "test create from valid snapshot source",
 23920  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23921  	}, {
 23922  		testName:  "test create from valid pvc source",
 23923  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 23924  	}, {
 23925  		testName:     "test missing name in snapshot datasource should fail",
 23926  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23927  		expectedFail: true,
 23928  	}, {
 23929  		testName:     "test missing kind in snapshot datasource should fail",
 23930  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 23931  		expectedFail: true,
 23932  	}, {
 23933  		testName:  "test create from valid generic custom resource source",
 23934  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 23935  	}, {
 23936  		testName:     "test invalid datasource should fail",
 23937  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 23938  		expectedFail: true,
 23939  	},
 23940  	}
 23941  
 23942  	for _, tc := range testCases {
 23943  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23944  		if tc.expectedFail {
 23945  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23946  				t.Errorf("expected failure: %v", errs)
 23947  			}
 23948  
 23949  		} else {
 23950  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23951  				t.Errorf("expected success: %v", errs)
 23952  			}
 23953  		}
 23954  	}
 23955  }
 23956  
 23957  func testAnyDataSource(t *testing.T, ds, dsRef bool) {
 23958  	testCases := []struct {
 23959  		testName     string
 23960  		claimSpec    core.PersistentVolumeClaimSpec
 23961  		expectedFail bool
 23962  	}{{
 23963  		testName:  "test create from valid snapshot source",
 23964  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23965  	}, {
 23966  		testName:  "test create from valid pvc source",
 23967  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 23968  	}, {
 23969  		testName:     "test missing name in snapshot datasource should fail",
 23970  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23971  		expectedFail: true,
 23972  	}, {
 23973  		testName:     "test missing kind in snapshot datasource should fail",
 23974  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 23975  		expectedFail: true,
 23976  	}, {
 23977  		testName:  "test create from valid generic custom resource source",
 23978  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 23979  	}, {
 23980  		testName:     "test invalid datasource should fail",
 23981  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 23982  		expectedFail: true,
 23983  	},
 23984  	}
 23985  
 23986  	for _, tc := range testCases {
 23987  		if dsRef {
 23988  			tc.claimSpec.DataSourceRef = &core.TypedObjectReference{
 23989  				APIGroup: tc.claimSpec.DataSource.APIGroup,
 23990  				Kind:     tc.claimSpec.DataSource.Kind,
 23991  				Name:     tc.claimSpec.DataSource.Name,
 23992  			}
 23993  		}
 23994  		if !ds {
 23995  			tc.claimSpec.DataSource = nil
 23996  		}
 23997  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23998  		if tc.expectedFail {
 23999  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 24000  				t.Errorf("expected failure: %v", errs)
 24001  			}
 24002  		} else {
 24003  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 24004  				t.Errorf("expected success: %v", errs)
 24005  			}
 24006  		}
 24007  	}
 24008  }
 24009  
 24010  func TestAnyDataSource(t *testing.T) {
 24011  	testAnyDataSource(t, true, false)
 24012  	testAnyDataSource(t, false, true)
 24013  	testAnyDataSource(t, true, false)
 24014  }
 24015  
 24016  func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec {
 24017  	scName := "csi-plugin"
 24018  	spec := core.PersistentVolumeClaimSpec{
 24019  		AccessModes: []core.PersistentVolumeAccessMode{
 24020  			core.ReadOnlyMany,
 24021  		},
 24022  		Resources: core.VolumeResourceRequirements{
 24023  			Requests: core.ResourceList{
 24024  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 24025  			},
 24026  		},
 24027  		StorageClassName: &scName,
 24028  		DataSourceRef: &core.TypedObjectReference{
 24029  			APIGroup:  apiGroup,
 24030  			Kind:      kind,
 24031  			Namespace: namespace,
 24032  			Name:      name,
 24033  		},
 24034  	}
 24035  
 24036  	if isDataSourceSet {
 24037  		spec.DataSource = &core.TypedLocalObjectReference{
 24038  			APIGroup: apiGroup,
 24039  			Kind:     kind,
 24040  			Name:     name,
 24041  		}
 24042  	}
 24043  	return &spec
 24044  }
 24045  
 24046  func TestCrossNamespaceSource(t *testing.T) {
 24047  	snapAPIGroup := "snapshot.storage.k8s.io"
 24048  	coreAPIGroup := ""
 24049  	unsupportedAPIGroup := "unsupported.example.com"
 24050  	snapKind := "VolumeSnapshot"
 24051  	pvcKind := "PersistentVolumeClaim"
 24052  	goodNS := "ns1"
 24053  	badNS := "a*b"
 24054  	emptyNS := ""
 24055  	goodName := "snapshot1"
 24056  
 24057  	testCases := []struct {
 24058  		testName     string
 24059  		expectedFail bool
 24060  		claimSpec    *core.PersistentVolumeClaimSpec
 24061  	}{{
 24062  		testName:     "Feature gate enabled and valid xns DataSourceRef specified",
 24063  		expectedFail: false,
 24064  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false),
 24065  	}, {
 24066  		testName:     "Feature gate enabled and xns DataSourceRef with PVC source specified",
 24067  		expectedFail: false,
 24068  		claimSpec:    pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false),
 24069  	}, {
 24070  		testName:     "Feature gate enabled and xns DataSourceRef with unsupported source specified",
 24071  		expectedFail: false,
 24072  		claimSpec:    pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false),
 24073  	}, {
 24074  		testName:     "Feature gate enabled and xns DataSourceRef with nil apiGroup",
 24075  		expectedFail: true,
 24076  		claimSpec:    pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false),
 24077  	}, {
 24078  		testName:     "Feature gate enabled and xns DataSourceRef with invalid namspace specified",
 24079  		expectedFail: true,
 24080  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false),
 24081  	}, {
 24082  		testName:     "Feature gate enabled and xns DataSourceRef with nil namspace specified",
 24083  		expectedFail: false,
 24084  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false),
 24085  	}, {
 24086  		testName:     "Feature gate enabled and xns DataSourceRef with empty namspace specified",
 24087  		expectedFail: false,
 24088  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false),
 24089  	}, {
 24090  		testName:     "Feature gate enabled and both xns DataSourceRef and DataSource specified",
 24091  		expectedFail: true,
 24092  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true),
 24093  	},
 24094  	}
 24095  
 24096  	for _, tc := range testCases {
 24097  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)()
 24098  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)()
 24099  		opts := PersistentVolumeClaimSpecValidationOptions{}
 24100  		if tc.expectedFail {
 24101  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 24102  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 24103  			}
 24104  		} else {
 24105  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 24106  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 24107  			}
 24108  		}
 24109  	}
 24110  }
 24111  
 24112  func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec {
 24113  	scName := "csi-plugin"
 24114  	spec := core.PersistentVolumeClaimSpec{
 24115  		AccessModes: []core.PersistentVolumeAccessMode{
 24116  			core.ReadOnlyMany,
 24117  		},
 24118  		Resources: core.VolumeResourceRequirements{
 24119  			Requests: core.ResourceList{
 24120  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 24121  			},
 24122  		},
 24123  		StorageClassName:          &scName,
 24124  		VolumeAttributesClassName: vacName,
 24125  	}
 24126  	return &spec
 24127  }
 24128  
 24129  func TestVolumeAttributesClass(t *testing.T) {
 24130  	testCases := []struct {
 24131  		testName                    string
 24132  		expectedFail                bool
 24133  		enableVolumeAttributesClass bool
 24134  		claimSpec                   *core.PersistentVolumeClaimSpec
 24135  	}{
 24136  		{
 24137  			testName:                    "Feature gate enabled and valid no volumeAttributesClassName specified",
 24138  			expectedFail:                false,
 24139  			enableVolumeAttributesClass: true,
 24140  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(nil),
 24141  		},
 24142  		{
 24143  			testName:                    "Feature gate enabled and an empty volumeAttributesClassName specified",
 24144  			expectedFail:                false,
 24145  			enableVolumeAttributesClass: true,
 24146  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("")),
 24147  		},
 24148  		{
 24149  			testName:                    "Feature gate enabled and valid volumeAttributesClassName specified",
 24150  			expectedFail:                false,
 24151  			enableVolumeAttributesClass: true,
 24152  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")),
 24153  		},
 24154  		{
 24155  			testName:                    "Feature gate enabled and invalid volumeAttributesClassName specified",
 24156  			expectedFail:                true,
 24157  			enableVolumeAttributesClass: true,
 24158  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")),
 24159  		},
 24160  	}
 24161  	for _, tc := range testCases {
 24162  		opts := PersistentVolumeClaimSpecValidationOptions{
 24163  			EnableVolumeAttributesClass: tc.enableVolumeAttributesClass,
 24164  		}
 24165  		if tc.expectedFail {
 24166  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 24167  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 24168  			}
 24169  		} else {
 24170  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 24171  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 24172  			}
 24173  		}
 24174  	}
 24175  }
 24176  
 24177  func TestValidateTopologySpreadConstraints(t *testing.T) {
 24178  	fieldPath := field.NewPath("field")
 24179  	subFldPath0 := fieldPath.Index(0)
 24180  	fieldPathMinDomains := subFldPath0.Child("minDomains")
 24181  	fieldPathMaxSkew := subFldPath0.Child("maxSkew")
 24182  	fieldPathTopologyKey := subFldPath0.Child("topologyKey")
 24183  	fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable")
 24184  	fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}")
 24185  	fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys")
 24186  	nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy")
 24187  	nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy")
 24188  	labelSelectorField := subFldPath0.Child("labelSelector")
 24189  	unknown := core.NodeInclusionPolicy("Unknown")
 24190  	ignore := core.NodeInclusionPolicyIgnore
 24191  	honor := core.NodeInclusionPolicyHonor
 24192  
 24193  	testCases := []struct {
 24194  		name            string
 24195  		constraints     []core.TopologySpreadConstraint
 24196  		wantFieldErrors field.ErrorList
 24197  		opts            PodValidationOptions
 24198  	}{{
 24199  		name: "all required fields ok",
 24200  		constraints: []core.TopologySpreadConstraint{{
 24201  			MaxSkew:           1,
 24202  			TopologyKey:       "k8s.io/zone",
 24203  			WhenUnsatisfiable: core.DoNotSchedule,
 24204  			MinDomains:        utilpointer.Int32(3),
 24205  		}},
 24206  		wantFieldErrors: field.ErrorList{},
 24207  	}, {
 24208  		name: "missing MaxSkew",
 24209  		constraints: []core.TopologySpreadConstraint{
 24210  			{TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24211  		},
 24212  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)},
 24213  	}, {
 24214  		name: "negative MaxSkew",
 24215  		constraints: []core.TopologySpreadConstraint{
 24216  			{MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24217  		},
 24218  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)},
 24219  	}, {
 24220  		name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil",
 24221  		constraints: []core.TopologySpreadConstraint{{
 24222  			MaxSkew:           1,
 24223  			TopologyKey:       "k8s.io/zone",
 24224  			WhenUnsatisfiable: core.ScheduleAnyway,
 24225  			MinDomains:        nil,
 24226  		}},
 24227  		wantFieldErrors: field.ErrorList{},
 24228  	}, {
 24229  		name: "negative minDomains is invalid",
 24230  		constraints: []core.TopologySpreadConstraint{{
 24231  			MaxSkew:           1,
 24232  			TopologyKey:       "k8s.io/zone",
 24233  			WhenUnsatisfiable: core.DoNotSchedule,
 24234  			MinDomains:        utilpointer.Int32(-1),
 24235  		}},
 24236  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)},
 24237  	}, {
 24238  		name: "cannot use non-nil MinDomains with ScheduleAnyway",
 24239  		constraints: []core.TopologySpreadConstraint{{
 24240  			MaxSkew:           1,
 24241  			TopologyKey:       "k8s.io/zone",
 24242  			WhenUnsatisfiable: core.ScheduleAnyway,
 24243  			MinDomains:        utilpointer.Int32(10),
 24244  		}},
 24245  		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)))},
 24246  	}, {
 24247  		name: "use negative MinDomains with ScheduleAnyway(invalid)",
 24248  		constraints: []core.TopologySpreadConstraint{{
 24249  			MaxSkew:           1,
 24250  			TopologyKey:       "k8s.io/zone",
 24251  			WhenUnsatisfiable: core.ScheduleAnyway,
 24252  			MinDomains:        utilpointer.Int32(-1),
 24253  		}},
 24254  		wantFieldErrors: []*field.Error{
 24255  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg),
 24256  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))),
 24257  		},
 24258  	}, {
 24259  		name: "missing TopologyKey",
 24260  		constraints: []core.TopologySpreadConstraint{
 24261  			{MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule},
 24262  		},
 24263  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 24264  	}, {
 24265  		name: "missing scheduling mode",
 24266  		constraints: []core.TopologySpreadConstraint{
 24267  			{MaxSkew: 1, TopologyKey: "k8s.io/zone"},
 24268  		},
 24269  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), sets.List(supportedScheduleActions))},
 24270  	}, {
 24271  		name: "unsupported scheduling mode",
 24272  		constraints: []core.TopologySpreadConstraint{
 24273  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")},
 24274  		},
 24275  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), sets.List(supportedScheduleActions))},
 24276  	}, {
 24277  		name: "multiple constraints ok with all required fields",
 24278  		constraints: []core.TopologySpreadConstraint{
 24279  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24280  			{MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway},
 24281  		},
 24282  		wantFieldErrors: field.ErrorList{},
 24283  	}, {
 24284  		name: "multiple constraints missing TopologyKey on partial ones",
 24285  		constraints: []core.TopologySpreadConstraint{
 24286  			{MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway},
 24287  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24288  		},
 24289  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 24290  	}, {
 24291  		name: "duplicate constraints",
 24292  		constraints: []core.TopologySpreadConstraint{
 24293  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24294  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 24295  		},
 24296  		wantFieldErrors: []*field.Error{
 24297  			field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)),
 24298  		},
 24299  	}, {
 24300  		name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy",
 24301  		constraints: []core.TopologySpreadConstraint{{
 24302  			MaxSkew:            1,
 24303  			TopologyKey:        "k8s.io/zone",
 24304  			WhenUnsatisfiable:  core.DoNotSchedule,
 24305  			NodeAffinityPolicy: &honor,
 24306  			NodeTaintsPolicy:   &ignore,
 24307  		}},
 24308  		wantFieldErrors: []*field.Error{},
 24309  	}, {
 24310  		name: "unsupported policy name set on NodeAffinityPolicy",
 24311  		constraints: []core.TopologySpreadConstraint{{
 24312  			MaxSkew:            1,
 24313  			TopologyKey:        "k8s.io/zone",
 24314  			WhenUnsatisfiable:  core.DoNotSchedule,
 24315  			NodeAffinityPolicy: &unknown,
 24316  			NodeTaintsPolicy:   &ignore,
 24317  		}},
 24318  		wantFieldErrors: []*field.Error{
 24319  			field.NotSupported(nodeAffinityField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 24320  		},
 24321  	}, {
 24322  		name: "unsupported policy name set on NodeTaintsPolicy",
 24323  		constraints: []core.TopologySpreadConstraint{{
 24324  			MaxSkew:            1,
 24325  			TopologyKey:        "k8s.io/zone",
 24326  			WhenUnsatisfiable:  core.DoNotSchedule,
 24327  			NodeAffinityPolicy: &honor,
 24328  			NodeTaintsPolicy:   &unknown,
 24329  		}},
 24330  		wantFieldErrors: []*field.Error{
 24331  			field.NotSupported(nodeTaintsField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 24332  		},
 24333  	}, {
 24334  		name: "key in MatchLabelKeys isn't correctly defined",
 24335  		constraints: []core.TopologySpreadConstraint{{
 24336  			MaxSkew:           1,
 24337  			TopologyKey:       "k8s.io/zone",
 24338  			LabelSelector:     &metav1.LabelSelector{},
 24339  			WhenUnsatisfiable: core.DoNotSchedule,
 24340  			MatchLabelKeys:    []string{"/simple"},
 24341  		}},
 24342  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")},
 24343  	}, {
 24344  		name: "key exists in both matchLabelKeys and labelSelector",
 24345  		constraints: []core.TopologySpreadConstraint{{
 24346  			MaxSkew:           1,
 24347  			TopologyKey:       "k8s.io/zone",
 24348  			WhenUnsatisfiable: core.DoNotSchedule,
 24349  			MatchLabelKeys:    []string{"foo"},
 24350  			LabelSelector: &metav1.LabelSelector{
 24351  				MatchExpressions: []metav1.LabelSelectorRequirement{
 24352  					{
 24353  						Key:      "foo",
 24354  						Operator: metav1.LabelSelectorOpNotIn,
 24355  						Values:   []string{"value1", "value2"},
 24356  					},
 24357  				},
 24358  			},
 24359  		}},
 24360  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},
 24361  	}, {
 24362  		name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set",
 24363  		constraints: []core.TopologySpreadConstraint{{
 24364  			MaxSkew:           1,
 24365  			TopologyKey:       "k8s.io/zone",
 24366  			WhenUnsatisfiable: core.DoNotSchedule,
 24367  			MatchLabelKeys:    []string{"foo"},
 24368  		}},
 24369  		wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")},
 24370  	}, {
 24371  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 24372  		constraints: []core.TopologySpreadConstraint{{
 24373  			MaxSkew:           1,
 24374  			TopologyKey:       "k8s.io/zone",
 24375  			WhenUnsatisfiable: core.DoNotSchedule,
 24376  			MinDomains:        nil,
 24377  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 24378  		}},
 24379  		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]')")},
 24380  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 24381  	}, {
 24382  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true",
 24383  		constraints: []core.TopologySpreadConstraint{{
 24384  			MaxSkew:           1,
 24385  			TopologyKey:       "k8s.io/zone",
 24386  			WhenUnsatisfiable: core.DoNotSchedule,
 24387  			MinDomains:        nil,
 24388  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 24389  		}},
 24390  		wantFieldErrors: []*field.Error{},
 24391  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true},
 24392  	}, {
 24393  		name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 24394  		constraints: []core.TopologySpreadConstraint{{
 24395  			MaxSkew:           1,
 24396  			TopologyKey:       "k8s.io/zone",
 24397  			WhenUnsatisfiable: core.DoNotSchedule,
 24398  			MinDomains:        nil,
 24399  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}},
 24400  		}},
 24401  		wantFieldErrors: []*field.Error{},
 24402  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 24403  	},
 24404  	}
 24405  
 24406  	for _, tc := range testCases {
 24407  		t.Run(tc.name, func(t *testing.T) {
 24408  			errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
 24409  			if diff := cmp.Diff(tc.wantFieldErrors, errs); diff != "" {
 24410  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 24411  			}
 24412  		})
 24413  	}
 24414  }
 24415  
 24416  func TestValidateOverhead(t *testing.T) {
 24417  	successCase := []struct {
 24418  		Name     string
 24419  		overhead core.ResourceList
 24420  	}{{
 24421  		Name: "Valid Overhead for CPU + Memory",
 24422  		overhead: core.ResourceList{
 24423  			core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 24424  			core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 24425  		},
 24426  	},
 24427  	}
 24428  	for _, tc := range successCase {
 24429  		if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 {
 24430  			t.Errorf("%q unexpected error: %v", tc.Name, errs)
 24431  		}
 24432  	}
 24433  
 24434  	errorCase := []struct {
 24435  		Name     string
 24436  		overhead core.ResourceList
 24437  	}{{
 24438  		Name: "Invalid Overhead Resources",
 24439  		overhead: core.ResourceList{
 24440  			core.ResourceName("my.org"): resource.MustParse("10m"),
 24441  		},
 24442  	},
 24443  	}
 24444  	for _, tc := range errorCase {
 24445  		if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 {
 24446  			t.Errorf("%q expected error", tc.Name)
 24447  		}
 24448  	}
 24449  }
 24450  
 24451  // helper creates a pod with name, namespace and IPs
 24452  func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod {
 24453  	return core.Pod{
 24454  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 24455  		Spec: core.PodSpec{
 24456  			Containers: []core.Container{{
 24457  				Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 24458  			}},
 24459  			RestartPolicy: core.RestartPolicyAlways,
 24460  			DNSPolicy:     core.DNSClusterFirst,
 24461  		},
 24462  		Status: core.PodStatus{
 24463  			PodIPs: podIPs,
 24464  		},
 24465  	}
 24466  }
 24467  func TestPodIPsValidation(t *testing.T) {
 24468  	testCases := []struct {
 24469  		pod         core.Pod
 24470  		expectError bool
 24471  	}{{
 24472  		expectError: false,
 24473  		pod:         makePod("nil-ips", "ns", nil),
 24474  	}, {
 24475  		expectError: false,
 24476  		pod:         makePod("empty-podips-list", "ns", []core.PodIP{}),
 24477  	}, {
 24478  		expectError: false,
 24479  		pod:         makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}),
 24480  	}, {
 24481  		expectError: false,
 24482  		pod:         makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}),
 24483  	}, {
 24484  		expectError: false,
 24485  		pod:         makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 24486  	}, {
 24487  		expectError: false,
 24488  		pod:         makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 24489  	},
 24490  		/* failure cases start here */
 24491  		{
 24492  			expectError: true,
 24493  			pod:         makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}),
 24494  		}, {
 24495  			expectError: true,
 24496  			pod:         makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}),
 24497  		}, {
 24498  			expectError: true,
 24499  			pod:         makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 24500  		}, {
 24501  			expectError: true,
 24502  			pod:         makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 24503  		}, {
 24504  			expectError: true,
 24505  			pod:         makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 24506  		},
 24507  
 24508  		{
 24509  			expectError: true,
 24510  			pod:         makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 24511  		}, {
 24512  			expectError: true,
 24513  			pod:         makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 24514  		},
 24515  	}
 24516  
 24517  	for _, testCase := range testCases {
 24518  		t.Run(testCase.pod.Name, func(t *testing.T) {
 24519  			for _, oldTestCase := range testCases {
 24520  				newPod := testCase.pod.DeepCopy()
 24521  				newPod.ResourceVersion = "1"
 24522  
 24523  				oldPod := oldTestCase.pod.DeepCopy()
 24524  				oldPod.ResourceVersion = "1"
 24525  				oldPod.Name = newPod.Name
 24526  
 24527  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 24528  
 24529  				if len(errs) == 0 && testCase.expectError {
 24530  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 24531  				}
 24532  				if len(errs) != 0 && !testCase.expectError {
 24533  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 24534  				}
 24535  			}
 24536  		})
 24537  	}
 24538  }
 24539  
 24540  func makePodWithHostIPs(podName string, podNamespace string, hostIPs []core.HostIP) core.Pod {
 24541  	hostIP := ""
 24542  	if len(hostIPs) > 0 {
 24543  		hostIP = hostIPs[0].IP
 24544  	}
 24545  	return core.Pod{
 24546  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 24547  		Spec: core.PodSpec{
 24548  			Containers: []core.Container{
 24549  				{
 24550  					Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 24551  				},
 24552  			},
 24553  			RestartPolicy: core.RestartPolicyAlways,
 24554  			DNSPolicy:     core.DNSClusterFirst,
 24555  		},
 24556  		Status: core.PodStatus{
 24557  			HostIP:  hostIP,
 24558  			HostIPs: hostIPs,
 24559  		},
 24560  	}
 24561  }
 24562  
 24563  func TestHostIPsValidation(t *testing.T) {
 24564  	testCases := []struct {
 24565  		pod         core.Pod
 24566  		expectError bool
 24567  	}{
 24568  		{
 24569  			expectError: false,
 24570  			pod:         makePodWithHostIPs("nil-ips", "ns", nil),
 24571  		},
 24572  		{
 24573  			expectError: false,
 24574  			pod:         makePodWithHostIPs("empty-HostIPs-list", "ns", []core.HostIP{}),
 24575  		},
 24576  		{
 24577  			expectError: false,
 24578  			pod:         makePodWithHostIPs("single-ip-family-6", "ns", []core.HostIP{{IP: "::1"}}),
 24579  		},
 24580  		{
 24581  			expectError: false,
 24582  			pod:         makePodWithHostIPs("single-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}}),
 24583  		},
 24584  		{
 24585  			expectError: false,
 24586  			pod:         makePodWithHostIPs("dual-stack-4-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 24587  		},
 24588  		{
 24589  			expectError: false,
 24590  			pod:         makePodWithHostIPs("dual-stack-6-4", "ns", []core.HostIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 24591  		},
 24592  		/* failure cases start here */
 24593  		{
 24594  			expectError: true,
 24595  			pod:         makePodWithHostIPs("invalid-pod-ip", "ns", []core.HostIP{{IP: "this-is-not-an-ip"}}),
 24596  		},
 24597  		{
 24598  			expectError: true,
 24599  			pod:         makePodWithHostIPs("dualstack-same-ip-family-6", "ns", []core.HostIP{{IP: "::1"}, {IP: "::2"}}),
 24600  		},
 24601  		{
 24602  			expectError: true,
 24603  			pod:         makePodWithHostIPs("dualstack-same-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 24604  		},
 24605  		{
 24606  			expectError: true,
 24607  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 24608  		},
 24609  		{
 24610  			expectError: true,
 24611  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 24612  		},
 24613  
 24614  		{
 24615  			expectError: true,
 24616  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 24617  		},
 24618  		{
 24619  			expectError: true,
 24620  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 24621  		},
 24622  	}
 24623  
 24624  	for _, testCase := range testCases {
 24625  		t.Run(testCase.pod.Name, func(t *testing.T) {
 24626  			for _, oldTestCase := range testCases {
 24627  				newPod := testCase.pod.DeepCopy()
 24628  				newPod.ResourceVersion = "1"
 24629  
 24630  				oldPod := oldTestCase.pod.DeepCopy()
 24631  				oldPod.ResourceVersion = "1"
 24632  				oldPod.Name = newPod.Name
 24633  
 24634  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 24635  
 24636  				if len(errs) == 0 && testCase.expectError {
 24637  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 24638  				}
 24639  				if len(errs) != 0 && !testCase.expectError {
 24640  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 24641  				}
 24642  			}
 24643  		})
 24644  	}
 24645  }
 24646  
 24647  // makes a node with pod cidr and a name
 24648  func makeNode(nodeName string, podCIDRs []string) core.Node {
 24649  	return core.Node{
 24650  		ObjectMeta: metav1.ObjectMeta{
 24651  			Name: nodeName,
 24652  		},
 24653  		Status: core.NodeStatus{
 24654  			Addresses: []core.NodeAddress{
 24655  				{Type: core.NodeExternalIP, Address: "something"},
 24656  			},
 24657  			Capacity: core.ResourceList{
 24658  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 24659  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 24660  			},
 24661  		},
 24662  		Spec: core.NodeSpec{
 24663  			PodCIDRs: podCIDRs,
 24664  		},
 24665  	}
 24666  }
 24667  func TestValidateNodeCIDRs(t *testing.T) {
 24668  	testCases := []struct {
 24669  		expectError bool
 24670  		node        core.Node
 24671  	}{{
 24672  		expectError: false,
 24673  		node:        makeNode("nil-pod-cidr", nil),
 24674  	}, {
 24675  		expectError: false,
 24676  		node:        makeNode("empty-pod-cidr", []string{}),
 24677  	}, {
 24678  		expectError: false,
 24679  		node:        makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}),
 24680  	}, {
 24681  		expectError: false,
 24682  		node:        makeNode("single-pod-cidr-6", []string{"2000::/10"}),
 24683  	},
 24684  
 24685  		{
 24686  			expectError: false,
 24687  			node:        makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}),
 24688  		}, {
 24689  			expectError: false,
 24690  			node:        makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}),
 24691  		},
 24692  		// error cases starts here
 24693  		{
 24694  			expectError: true,
 24695  			node:        makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}),
 24696  		}, {
 24697  			expectError: true,
 24698  			node:        makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}),
 24699  		}, {
 24700  			expectError: true,
 24701  			node:        makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}),
 24702  		}, {
 24703  			expectError: true,
 24704  			node:        makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}),
 24705  		}, {
 24706  			expectError: true,
 24707  			node:        makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}),
 24708  		}, {
 24709  			expectError: true,
 24710  			node:        makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}),
 24711  		}, {
 24712  			expectError: true,
 24713  			node:        makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}),
 24714  		},
 24715  	}
 24716  	for _, testCase := range testCases {
 24717  		errs := ValidateNode(&testCase.node)
 24718  		if len(errs) == 0 && testCase.expectError {
 24719  			t.Errorf("expected failure for %s, but there were none", testCase.node.Name)
 24720  			return
 24721  		}
 24722  		if len(errs) != 0 && !testCase.expectError {
 24723  			t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs)
 24724  			return
 24725  		}
 24726  	}
 24727  }
 24728  
 24729  func TestValidateSeccompAnnotationAndField(t *testing.T) {
 24730  	const containerName = "container"
 24731  	testProfile := "test"
 24732  
 24733  	for _, test := range []struct {
 24734  		description string
 24735  		pod         *core.Pod
 24736  		validation  func(*testing.T, string, field.ErrorList, *v1.Pod)
 24737  	}{{
 24738  		description: "Field type unconfined and annotation does not match",
 24739  		pod: &core.Pod{
 24740  			ObjectMeta: metav1.ObjectMeta{
 24741  				Annotations: map[string]string{
 24742  					v1.SeccompPodAnnotationKey: "not-matching",
 24743  				},
 24744  			},
 24745  			Spec: core.PodSpec{
 24746  				SecurityContext: &core.PodSecurityContext{
 24747  					SeccompProfile: &core.SeccompProfile{
 24748  						Type: core.SeccompProfileTypeUnconfined,
 24749  					},
 24750  				},
 24751  			},
 24752  		},
 24753  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24754  			require.NotNil(t, allErrs, desc)
 24755  		},
 24756  	}, {
 24757  		description: "Field type default and annotation does not match",
 24758  		pod: &core.Pod{
 24759  			ObjectMeta: metav1.ObjectMeta{
 24760  				Annotations: map[string]string{
 24761  					v1.SeccompPodAnnotationKey: "not-matching",
 24762  				},
 24763  			},
 24764  			Spec: core.PodSpec{
 24765  				SecurityContext: &core.PodSecurityContext{
 24766  					SeccompProfile: &core.SeccompProfile{
 24767  						Type: core.SeccompProfileTypeRuntimeDefault,
 24768  					},
 24769  				},
 24770  			},
 24771  		},
 24772  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24773  			require.NotNil(t, allErrs, desc)
 24774  		},
 24775  	}, {
 24776  		description: "Field type localhost and annotation does not match",
 24777  		pod: &core.Pod{
 24778  			ObjectMeta: metav1.ObjectMeta{
 24779  				Annotations: map[string]string{
 24780  					v1.SeccompPodAnnotationKey: "not-matching",
 24781  				},
 24782  			},
 24783  			Spec: core.PodSpec{
 24784  				SecurityContext: &core.PodSecurityContext{
 24785  					SeccompProfile: &core.SeccompProfile{
 24786  						Type:             core.SeccompProfileTypeLocalhost,
 24787  						LocalhostProfile: &testProfile,
 24788  					},
 24789  				},
 24790  			},
 24791  		},
 24792  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24793  			require.NotNil(t, allErrs, desc)
 24794  		},
 24795  	}, {
 24796  		description: "Field type localhost and localhost/ prefixed annotation does not match",
 24797  		pod: &core.Pod{
 24798  			ObjectMeta: metav1.ObjectMeta{
 24799  				Annotations: map[string]string{
 24800  					v1.SeccompPodAnnotationKey: "localhost/not-matching",
 24801  				},
 24802  			},
 24803  			Spec: core.PodSpec{
 24804  				SecurityContext: &core.PodSecurityContext{
 24805  					SeccompProfile: &core.SeccompProfile{
 24806  						Type:             core.SeccompProfileTypeLocalhost,
 24807  						LocalhostProfile: &testProfile,
 24808  					},
 24809  				},
 24810  			},
 24811  		},
 24812  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24813  			require.NotNil(t, allErrs, desc)
 24814  		},
 24815  	}, {
 24816  		description: "Field type unconfined and annotation does not match (container)",
 24817  		pod: &core.Pod{
 24818  			ObjectMeta: metav1.ObjectMeta{
 24819  				Annotations: map[string]string{
 24820  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24821  				},
 24822  			},
 24823  			Spec: core.PodSpec{
 24824  				Containers: []core.Container{{
 24825  					Name: containerName,
 24826  					SecurityContext: &core.SecurityContext{
 24827  						SeccompProfile: &core.SeccompProfile{
 24828  							Type: core.SeccompProfileTypeUnconfined,
 24829  						},
 24830  					},
 24831  				}},
 24832  			},
 24833  		},
 24834  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24835  			require.NotNil(t, allErrs, desc)
 24836  		},
 24837  	}, {
 24838  		description: "Field type default and annotation does not match (container)",
 24839  		pod: &core.Pod{
 24840  			ObjectMeta: metav1.ObjectMeta{
 24841  				Annotations: map[string]string{
 24842  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24843  				},
 24844  			},
 24845  			Spec: core.PodSpec{
 24846  				Containers: []core.Container{{
 24847  					Name: containerName,
 24848  					SecurityContext: &core.SecurityContext{
 24849  						SeccompProfile: &core.SeccompProfile{
 24850  							Type: core.SeccompProfileTypeRuntimeDefault,
 24851  						},
 24852  					},
 24853  				}},
 24854  			},
 24855  		},
 24856  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24857  			require.NotNil(t, allErrs, desc)
 24858  		},
 24859  	}, {
 24860  		description: "Field type localhost and annotation does not match (container)",
 24861  		pod: &core.Pod{
 24862  			ObjectMeta: metav1.ObjectMeta{
 24863  				Annotations: map[string]string{
 24864  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 24865  				},
 24866  			},
 24867  			Spec: core.PodSpec{
 24868  				Containers: []core.Container{{
 24869  					Name: containerName,
 24870  					SecurityContext: &core.SecurityContext{
 24871  						SeccompProfile: &core.SeccompProfile{
 24872  							Type:             core.SeccompProfileTypeLocalhost,
 24873  							LocalhostProfile: &testProfile,
 24874  						},
 24875  					},
 24876  				}},
 24877  			},
 24878  		},
 24879  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24880  			require.NotNil(t, allErrs, desc)
 24881  		},
 24882  	}, {
 24883  		description: "Field type localhost and localhost/ prefixed annotation does not match (container)",
 24884  		pod: &core.Pod{
 24885  			ObjectMeta: metav1.ObjectMeta{
 24886  				Annotations: map[string]string{
 24887  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 24888  				},
 24889  			},
 24890  			Spec: core.PodSpec{
 24891  				Containers: []core.Container{{
 24892  					Name: containerName,
 24893  					SecurityContext: &core.SecurityContext{
 24894  						SeccompProfile: &core.SeccompProfile{
 24895  							Type:             core.SeccompProfileTypeLocalhost,
 24896  							LocalhostProfile: &testProfile,
 24897  						},
 24898  					},
 24899  				}},
 24900  			},
 24901  		},
 24902  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24903  			require.NotNil(t, allErrs, desc)
 24904  		},
 24905  	}, {
 24906  		description: "Nil errors must not be appended (pod)",
 24907  		pod: &core.Pod{
 24908  			ObjectMeta: metav1.ObjectMeta{
 24909  				Annotations: map[string]string{
 24910  					v1.SeccompPodAnnotationKey: "localhost/anyprofile",
 24911  				},
 24912  			},
 24913  			Spec: core.PodSpec{
 24914  				SecurityContext: &core.PodSecurityContext{
 24915  					SeccompProfile: &core.SeccompProfile{
 24916  						Type: "Abc",
 24917  					},
 24918  				},
 24919  				Containers: []core.Container{{
 24920  					Name: containerName,
 24921  				}},
 24922  			},
 24923  		},
 24924  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24925  			require.Empty(t, allErrs, desc)
 24926  		},
 24927  	}, {
 24928  		description: "Nil errors must not be appended (container)",
 24929  		pod: &core.Pod{
 24930  			ObjectMeta: metav1.ObjectMeta{
 24931  				Annotations: map[string]string{
 24932  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 24933  				},
 24934  			},
 24935  			Spec: core.PodSpec{
 24936  				Containers: []core.Container{{
 24937  					SecurityContext: &core.SecurityContext{
 24938  						SeccompProfile: &core.SeccompProfile{
 24939  							Type: "Abc",
 24940  						},
 24941  					},
 24942  					Name: containerName,
 24943  				}},
 24944  			},
 24945  		},
 24946  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 24947  			require.Empty(t, allErrs, desc)
 24948  		},
 24949  	},
 24950  	} {
 24951  		output := &v1.Pod{
 24952  			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
 24953  		}
 24954  		for i, ctr := range test.pod.Spec.Containers {
 24955  			output.Spec.Containers = append(output.Spec.Containers, v1.Container{})
 24956  			if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
 24957  				output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{
 24958  					SeccompProfile: &v1.SeccompProfile{
 24959  						Type:             v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type),
 24960  						LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile,
 24961  					},
 24962  				}
 24963  			}
 24964  		}
 24965  		errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath(""))
 24966  		test.validation(t, test.description, errList, output)
 24967  	}
 24968  }
 24969  
 24970  func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) {
 24971  	rootFld := field.NewPath("")
 24972  	tests := []struct {
 24973  		description     string
 24974  		annotationValue string
 24975  		seccompField    *core.SeccompProfile
 24976  		fldPath         *field.Path
 24977  		expectedErr     *field.Error
 24978  	}{{
 24979  		description: "seccompField nil should return empty",
 24980  		expectedErr: nil,
 24981  	}, {
 24982  		description:     "unconfined annotation and SeccompProfileTypeUnconfined should return empty",
 24983  		annotationValue: "unconfined",
 24984  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 24985  		expectedErr:     nil,
 24986  	}, {
 24987  		description:     "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24988  		annotationValue: "runtime/default",
 24989  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24990  		expectedErr:     nil,
 24991  	}, {
 24992  		description:     "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24993  		annotationValue: "docker/default",
 24994  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24995  		expectedErr:     nil,
 24996  	}, {
 24997  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty",
 24998  		annotationValue: "localhost/test.json",
 24999  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")},
 25000  		expectedErr:     nil,
 25001  	}, {
 25002  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error",
 25003  		annotationValue: "localhost/test.json",
 25004  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost},
 25005  		fldPath:         rootFld,
 25006  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 25007  	}, {
 25008  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error",
 25009  		annotationValue: "localhost/test.json",
 25010  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")},
 25011  		fldPath:         rootFld,
 25012  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 25013  	}, {
 25014  		description:     "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error",
 25015  		annotationValue: "localhost/test.json",
 25016  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 25017  		fldPath:         rootFld,
 25018  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 25019  	}, {
 25020  		description:     "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error",
 25021  		annotationValue: "localhost/test.json",
 25022  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 25023  		fldPath:         rootFld,
 25024  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 25025  	},
 25026  	}
 25027  
 25028  	for i, test := range tests {
 25029  		err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath)
 25030  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 25031  	}
 25032  }
 25033  
 25034  func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
 25035  	rootFld := field.NewPath("template")
 25036  	tests := []struct {
 25037  		description string
 25038  		spec        *core.PodTemplateSpec
 25039  		fldPath     *field.Path
 25040  		expectedErr field.ErrorList
 25041  	}{{
 25042  		description: "seccomp field and container annotation must match",
 25043  		fldPath:     rootFld,
 25044  		expectedErr: field.ErrorList{
 25045  			field.Forbidden(
 25046  				rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"),
 25047  				"seccomp type in annotation and field must match"),
 25048  		},
 25049  		spec: &core.PodTemplateSpec{
 25050  			ObjectMeta: metav1.ObjectMeta{
 25051  				Annotations: map[string]string{
 25052  					"container.seccomp.security.alpha.kubernetes.io/test2": "unconfined",
 25053  				},
 25054  			},
 25055  			Spec: core.PodSpec{
 25056  				Containers: []core.Container{{
 25057  					Name:                     "test1",
 25058  					Image:                    "alpine",
 25059  					ImagePullPolicy:          core.PullAlways,
 25060  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25061  				}, {
 25062  					SecurityContext: &core.SecurityContext{
 25063  						SeccompProfile: &core.SeccompProfile{
 25064  							Type: core.SeccompProfileTypeRuntimeDefault,
 25065  						},
 25066  					},
 25067  					Name:                     "test2",
 25068  					Image:                    "alpine",
 25069  					ImagePullPolicy:          core.PullAlways,
 25070  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25071  				}},
 25072  				RestartPolicy: core.RestartPolicyAlways,
 25073  				DNSPolicy:     core.DNSDefault,
 25074  			},
 25075  		},
 25076  	}, {
 25077  		description: "seccomp field and pod annotation must match",
 25078  		fldPath:     rootFld,
 25079  		expectedErr: field.ErrorList{
 25080  			field.Forbidden(
 25081  				rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"),
 25082  				"seccomp type in annotation and field must match"),
 25083  		},
 25084  		spec: &core.PodTemplateSpec{
 25085  			ObjectMeta: metav1.ObjectMeta{
 25086  				Annotations: map[string]string{
 25087  					"seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
 25088  				},
 25089  			},
 25090  			Spec: core.PodSpec{
 25091  				SecurityContext: &core.PodSecurityContext{
 25092  					SeccompProfile: &core.SeccompProfile{
 25093  						Type: core.SeccompProfileTypeUnconfined,
 25094  					},
 25095  				},
 25096  				Containers: []core.Container{{
 25097  					Name:                     "test",
 25098  					Image:                    "alpine",
 25099  					ImagePullPolicy:          core.PullAlways,
 25100  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25101  				}},
 25102  				RestartPolicy: core.RestartPolicyAlways,
 25103  				DNSPolicy:     core.DNSDefault,
 25104  			},
 25105  		},
 25106  	}, {
 25107  		description: "init seccomp field and container annotation must match",
 25108  		fldPath:     rootFld,
 25109  		expectedErr: field.ErrorList{
 25110  			field.Forbidden(
 25111  				rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"),
 25112  				"seccomp type in annotation and field must match"),
 25113  		},
 25114  		spec: &core.PodTemplateSpec{
 25115  			ObjectMeta: metav1.ObjectMeta{
 25116  				Annotations: map[string]string{
 25117  					"container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined",
 25118  				},
 25119  			},
 25120  			Spec: core.PodSpec{
 25121  				Containers: []core.Container{{
 25122  					Name:                     "test",
 25123  					Image:                    "alpine",
 25124  					ImagePullPolicy:          core.PullAlways,
 25125  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25126  				}},
 25127  				InitContainers: []core.Container{{
 25128  					Name: "init-test",
 25129  					SecurityContext: &core.SecurityContext{
 25130  						SeccompProfile: &core.SeccompProfile{
 25131  							Type: core.SeccompProfileTypeRuntimeDefault,
 25132  						},
 25133  					},
 25134  					Image:                    "alpine",
 25135  					ImagePullPolicy:          core.PullAlways,
 25136  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 25137  				}},
 25138  				RestartPolicy: core.RestartPolicyAlways,
 25139  				DNSPolicy:     core.DNSDefault,
 25140  			},
 25141  		},
 25142  	},
 25143  	}
 25144  
 25145  	for i, test := range tests {
 25146  		err := ValidatePodTemplateSpec(test.spec, rootFld, PodValidationOptions{})
 25147  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 25148  	}
 25149  }
 25150  
 25151  func TestValidateResourceRequirements(t *testing.T) {
 25152  	path := field.NewPath("resources")
 25153  	tests := []struct {
 25154  		name         string
 25155  		requirements core.ResourceRequirements
 25156  		opts         PodValidationOptions
 25157  	}{{
 25158  		name: "limits and requests of hugepage resource are equal",
 25159  		requirements: core.ResourceRequirements{
 25160  			Limits: core.ResourceList{
 25161  				core.ResourceCPU: resource.MustParse("10"),
 25162  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25163  			},
 25164  			Requests: core.ResourceList{
 25165  				core.ResourceCPU: resource.MustParse("10"),
 25166  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25167  			},
 25168  		},
 25169  		opts: PodValidationOptions{},
 25170  	}, {
 25171  		name: "limits and requests of memory resource are equal",
 25172  		requirements: core.ResourceRequirements{
 25173  			Limits: core.ResourceList{
 25174  				core.ResourceMemory: resource.MustParse("2Mi"),
 25175  			},
 25176  			Requests: core.ResourceList{
 25177  				core.ResourceMemory: resource.MustParse("2Mi"),
 25178  			},
 25179  		},
 25180  		opts: PodValidationOptions{},
 25181  	}, {
 25182  		name: "limits and requests of cpu resource are equal",
 25183  		requirements: core.ResourceRequirements{
 25184  			Limits: core.ResourceList{
 25185  				core.ResourceCPU: resource.MustParse("10"),
 25186  			},
 25187  			Requests: core.ResourceList{
 25188  				core.ResourceCPU: resource.MustParse("10"),
 25189  			},
 25190  		},
 25191  		opts: PodValidationOptions{},
 25192  	},
 25193  	}
 25194  
 25195  	for _, tc := range tests {
 25196  		t.Run(tc.name, func(t *testing.T) {
 25197  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 {
 25198  				t.Errorf("unexpected errors: %v", errs)
 25199  			}
 25200  		})
 25201  	}
 25202  
 25203  	errTests := []struct {
 25204  		name         string
 25205  		requirements core.ResourceRequirements
 25206  		opts         PodValidationOptions
 25207  	}{{
 25208  		name: "hugepage resource without cpu or memory",
 25209  		requirements: core.ResourceRequirements{
 25210  			Limits: core.ResourceList{
 25211  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25212  			},
 25213  			Requests: core.ResourceList{
 25214  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 25215  			},
 25216  		},
 25217  		opts: PodValidationOptions{},
 25218  	},
 25219  	}
 25220  
 25221  	for _, tc := range errTests {
 25222  		t.Run(tc.name, func(t *testing.T) {
 25223  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 {
 25224  				t.Error("expected errors")
 25225  			}
 25226  		})
 25227  	}
 25228  }
 25229  
 25230  func TestValidateNonSpecialIP(t *testing.T) {
 25231  	fp := field.NewPath("ip")
 25232  
 25233  	// Valid values.
 25234  	for _, tc := range []struct {
 25235  		desc string
 25236  		ip   string
 25237  	}{
 25238  		{"ipv4", "10.1.2.3"},
 25239  		{"ipv4 class E", "244.1.2.3"},
 25240  		{"ipv6", "2000::1"},
 25241  	} {
 25242  		t.Run(tc.desc, func(t *testing.T) {
 25243  			errs := ValidateNonSpecialIP(tc.ip, fp)
 25244  			if len(errs) != 0 {
 25245  				t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs)
 25246  			}
 25247  		})
 25248  	}
 25249  	// Invalid cases
 25250  	for _, tc := range []struct {
 25251  		desc string
 25252  		ip   string
 25253  	}{
 25254  		{"ipv4 unspecified", "0.0.0.0"},
 25255  		{"ipv6 unspecified", "::0"},
 25256  		{"ipv4 localhost", "127.0.0.0"},
 25257  		{"ipv4 localhost", "127.255.255.255"},
 25258  		{"ipv6 localhost", "::1"},
 25259  		{"ipv6 link local", "fe80::"},
 25260  		{"ipv6 local multicast", "ff02::"},
 25261  	} {
 25262  		t.Run(tc.desc, func(t *testing.T) {
 25263  			errs := ValidateNonSpecialIP(tc.ip, fp)
 25264  			if len(errs) == 0 {
 25265  				t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip)
 25266  			}
 25267  		})
 25268  	}
 25269  }
 25270  
 25271  func TestValidateHostUsers(t *testing.T) {
 25272  	falseVar := false
 25273  	trueVar := true
 25274  
 25275  	cases := []struct {
 25276  		name    string
 25277  		success bool
 25278  		spec    *core.PodSpec
 25279  	}{{
 25280  		name:    "empty",
 25281  		success: true,
 25282  		spec:    &core.PodSpec{},
 25283  	}, {
 25284  		name:    "hostUsers unset",
 25285  		success: true,
 25286  		spec: &core.PodSpec{
 25287  			SecurityContext: &core.PodSecurityContext{},
 25288  		},
 25289  	}, {
 25290  		name:    "hostUsers=false",
 25291  		success: true,
 25292  		spec: &core.PodSpec{
 25293  			SecurityContext: &core.PodSecurityContext{
 25294  				HostUsers: &falseVar,
 25295  			},
 25296  		},
 25297  	}, {
 25298  		name:    "hostUsers=true",
 25299  		success: true,
 25300  		spec: &core.PodSpec{
 25301  			SecurityContext: &core.PodSecurityContext{
 25302  				HostUsers: &trueVar,
 25303  			},
 25304  		},
 25305  	}, {
 25306  		name:    "hostUsers=false & volumes",
 25307  		success: true,
 25308  		spec: &core.PodSpec{
 25309  			SecurityContext: &core.PodSecurityContext{
 25310  				HostUsers: &falseVar,
 25311  			},
 25312  			Volumes: []core.Volume{{
 25313  				Name: "configmap",
 25314  				VolumeSource: core.VolumeSource{
 25315  					ConfigMap: &core.ConfigMapVolumeSource{
 25316  						LocalObjectReference: core.LocalObjectReference{Name: "configmap"},
 25317  					},
 25318  				},
 25319  			}, {
 25320  				Name: "secret",
 25321  				VolumeSource: core.VolumeSource{
 25322  					Secret: &core.SecretVolumeSource{
 25323  						SecretName: "secret",
 25324  					},
 25325  				},
 25326  			}, {
 25327  				Name: "downward-api",
 25328  				VolumeSource: core.VolumeSource{
 25329  					DownwardAPI: &core.DownwardAPIVolumeSource{},
 25330  				},
 25331  			}, {
 25332  				Name: "proj",
 25333  				VolumeSource: core.VolumeSource{
 25334  					Projected: &core.ProjectedVolumeSource{},
 25335  				},
 25336  			}, {
 25337  				Name: "empty-dir",
 25338  				VolumeSource: core.VolumeSource{
 25339  					EmptyDir: &core.EmptyDirVolumeSource{},
 25340  				},
 25341  			}},
 25342  		},
 25343  	}, {
 25344  		name:    "hostUsers=false - stateful volume",
 25345  		success: true,
 25346  		spec: &core.PodSpec{
 25347  			SecurityContext: &core.PodSecurityContext{
 25348  				HostUsers: &falseVar,
 25349  			},
 25350  			Volumes: []core.Volume{{
 25351  				Name: "host-path",
 25352  				VolumeSource: core.VolumeSource{
 25353  					HostPath: &core.HostPathVolumeSource{},
 25354  				},
 25355  			}},
 25356  		},
 25357  	}, {
 25358  		name:    "hostUsers=true - unsupported volume",
 25359  		success: true,
 25360  		spec: &core.PodSpec{
 25361  			SecurityContext: &core.PodSecurityContext{
 25362  				HostUsers: &trueVar,
 25363  			},
 25364  			Volumes: []core.Volume{{
 25365  				Name: "host-path",
 25366  				VolumeSource: core.VolumeSource{
 25367  					HostPath: &core.HostPathVolumeSource{},
 25368  				},
 25369  			}},
 25370  		},
 25371  	}, {
 25372  		name:    "hostUsers=false & HostNetwork",
 25373  		success: false,
 25374  		spec: &core.PodSpec{
 25375  			SecurityContext: &core.PodSecurityContext{
 25376  				HostUsers:   &falseVar,
 25377  				HostNetwork: true,
 25378  			},
 25379  		},
 25380  	}, {
 25381  		name:    "hostUsers=false & HostPID",
 25382  		success: false,
 25383  		spec: &core.PodSpec{
 25384  			SecurityContext: &core.PodSecurityContext{
 25385  				HostUsers: &falseVar,
 25386  				HostPID:   true,
 25387  			},
 25388  		},
 25389  	}, {
 25390  		name:    "hostUsers=false & HostIPC",
 25391  		success: false,
 25392  		spec: &core.PodSpec{
 25393  			SecurityContext: &core.PodSecurityContext{
 25394  				HostUsers: &falseVar,
 25395  				HostIPC:   true,
 25396  			},
 25397  		},
 25398  	},
 25399  	}
 25400  
 25401  	for _, tc := range cases {
 25402  		t.Run(tc.name, func(t *testing.T) {
 25403  			fPath := field.NewPath("spec")
 25404  
 25405  			allErrs := validateHostUsers(tc.spec, fPath)
 25406  			if !tc.success && len(allErrs) == 0 {
 25407  				t.Errorf("Unexpected success")
 25408  			}
 25409  			if tc.success && len(allErrs) != 0 {
 25410  				t.Errorf("Unexpected error(s): %v", allErrs)
 25411  			}
 25412  		})
 25413  	}
 25414  }
 25415  
 25416  func TestValidateWindowsHostProcessPod(t *testing.T) {
 25417  	const containerName = "container"
 25418  	falseVar := false
 25419  	trueVar := true
 25420  
 25421  	testCases := []struct {
 25422  		name            string
 25423  		expectError     bool
 25424  		allowPrivileged bool
 25425  		podSpec         *core.PodSpec
 25426  	}{{
 25427  		name:            "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate",
 25428  		expectError:     true,
 25429  		allowPrivileged: true,
 25430  		podSpec: &core.PodSpec{
 25431  			SecurityContext: &core.PodSecurityContext{
 25432  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25433  					HostProcess: &trueVar,
 25434  				},
 25435  			},
 25436  			Containers: []core.Container{{
 25437  				Name: containerName,
 25438  			}},
 25439  		},
 25440  	}, {
 25441  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate",
 25442  		expectError:     false,
 25443  		allowPrivileged: true,
 25444  		podSpec: &core.PodSpec{
 25445  			SecurityContext: &core.PodSecurityContext{
 25446  				HostNetwork: true,
 25447  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25448  					HostProcess: &trueVar,
 25449  				},
 25450  			},
 25451  			Containers: []core.Container{{
 25452  				Name: containerName,
 25453  			}},
 25454  		},
 25455  	}, {
 25456  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate",
 25457  		expectError:     false,
 25458  		allowPrivileged: true,
 25459  		podSpec: &core.PodSpec{
 25460  			SecurityContext: &core.PodSecurityContext{
 25461  				HostNetwork: true,
 25462  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25463  					HostProcess: &trueVar,
 25464  				},
 25465  			},
 25466  			Containers: []core.Container{{
 25467  				Name: containerName,
 25468  				SecurityContext: &core.SecurityContext{
 25469  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25470  						HostProcess: &trueVar,
 25471  					},
 25472  				},
 25473  			}},
 25474  			InitContainers: []core.Container{{
 25475  				Name: containerName,
 25476  				SecurityContext: &core.SecurityContext{
 25477  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25478  						HostProcess: &trueVar,
 25479  					},
 25480  				},
 25481  			}},
 25482  		},
 25483  	}, {
 25484  		name:            "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate",
 25485  		expectError:     false,
 25486  		allowPrivileged: true,
 25487  		podSpec: &core.PodSpec{
 25488  			SecurityContext: &core.PodSecurityContext{
 25489  				HostNetwork: true,
 25490  			},
 25491  			Containers: []core.Container{{
 25492  				Name: containerName,
 25493  				SecurityContext: &core.SecurityContext{
 25494  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25495  						HostProcess: &trueVar,
 25496  					},
 25497  				},
 25498  			}},
 25499  			InitContainers: []core.Container{{
 25500  				Name: containerName,
 25501  				SecurityContext: &core.SecurityContext{
 25502  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25503  						HostProcess: &trueVar,
 25504  					},
 25505  				},
 25506  			}},
 25507  		},
 25508  	}, {
 25509  		name:            "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 25510  		expectError:     true,
 25511  		allowPrivileged: true,
 25512  		podSpec: &core.PodSpec{
 25513  			SecurityContext: &core.PodSecurityContext{
 25514  				HostNetwork: true,
 25515  			},
 25516  			Containers: []core.Container{{
 25517  				Name: containerName,
 25518  				SecurityContext: &core.SecurityContext{
 25519  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25520  						HostProcess: &trueVar,
 25521  					},
 25522  				},
 25523  			}},
 25524  			InitContainers: []core.Container{{
 25525  				Name: containerName,
 25526  				SecurityContext: &core.SecurityContext{
 25527  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25528  						HostProcess: &falseVar,
 25529  					},
 25530  				},
 25531  			}},
 25532  		},
 25533  	}, {
 25534  		name:            "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate",
 25535  		expectError:     true,
 25536  		allowPrivileged: true,
 25537  		podSpec: &core.PodSpec{
 25538  			SecurityContext: &core.PodSecurityContext{
 25539  				HostNetwork: true,
 25540  			},
 25541  			Containers: []core.Container{{
 25542  				Name: containerName,
 25543  				SecurityContext: &core.SecurityContext{
 25544  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25545  						HostProcess: &trueVar,
 25546  					},
 25547  				},
 25548  			}},
 25549  			InitContainers: []core.Container{{
 25550  				Name: containerName,
 25551  			}},
 25552  		},
 25553  	}, {
 25554  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate",
 25555  		expectError:     true,
 25556  		allowPrivileged: true,
 25557  		podSpec: &core.PodSpec{
 25558  			SecurityContext: &core.PodSecurityContext{
 25559  				HostNetwork: true,
 25560  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25561  					HostProcess: &trueVar,
 25562  				},
 25563  			},
 25564  			Containers: []core.Container{{
 25565  				Name: containerName,
 25566  				SecurityContext: &core.SecurityContext{
 25567  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25568  						HostProcess: &trueVar,
 25569  					},
 25570  				},
 25571  			}},
 25572  			InitContainers: []core.Container{{
 25573  				Name: containerName,
 25574  				SecurityContext: &core.SecurityContext{
 25575  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25576  						HostProcess: &falseVar,
 25577  					},
 25578  				},
 25579  			}},
 25580  		},
 25581  	}, {
 25582  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 25583  		expectError:     true,
 25584  		allowPrivileged: true,
 25585  		podSpec: &core.PodSpec{
 25586  			SecurityContext: &core.PodSecurityContext{
 25587  				HostNetwork: true,
 25588  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25589  					HostProcess: &trueVar,
 25590  				},
 25591  			},
 25592  			Containers: []core.Container{{
 25593  				Name: containerName,
 25594  				SecurityContext: &core.SecurityContext{
 25595  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25596  						HostProcess: &trueVar,
 25597  					},
 25598  				},
 25599  			}, {
 25600  				Name: containerName,
 25601  				SecurityContext: &core.SecurityContext{
 25602  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25603  						HostProcess: &falseVar,
 25604  					},
 25605  				},
 25606  			}},
 25607  		},
 25608  	}, {
 25609  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate",
 25610  		expectError:     false,
 25611  		allowPrivileged: true,
 25612  		podSpec: &core.PodSpec{
 25613  			SecurityContext: &core.PodSecurityContext{
 25614  				HostNetwork: true,
 25615  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25616  					HostProcess: &trueVar,
 25617  				},
 25618  			},
 25619  			Containers: []core.Container{{
 25620  				Name: containerName,
 25621  				SecurityContext: &core.SecurityContext{
 25622  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25623  						HostProcess: &trueVar,
 25624  					},
 25625  				},
 25626  			}},
 25627  			InitContainers: []core.Container{{
 25628  				Name: containerName,
 25629  			}},
 25630  		},
 25631  	}, {
 25632  		name:            "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate",
 25633  		expectError:     true,
 25634  		allowPrivileged: true,
 25635  		podSpec: &core.PodSpec{
 25636  			SecurityContext: &core.PodSecurityContext{
 25637  				HostNetwork: true,
 25638  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25639  					HostProcess: &falseVar,
 25640  				},
 25641  			},
 25642  			Containers: []core.Container{{
 25643  				Name: containerName,
 25644  				SecurityContext: &core.SecurityContext{
 25645  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25646  						HostProcess: &trueVar,
 25647  					},
 25648  				},
 25649  			}},
 25650  			InitContainers: []core.Container{{
 25651  				Name: containerName,
 25652  			}},
 25653  		},
 25654  	}, {
 25655  		name:            "Pod's HostProcess set to true but all containers override to false should not validate",
 25656  		expectError:     true,
 25657  		allowPrivileged: true,
 25658  		podSpec: &core.PodSpec{
 25659  			SecurityContext: &core.PodSecurityContext{
 25660  				HostNetwork: true,
 25661  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25662  					HostProcess: &trueVar,
 25663  				},
 25664  			},
 25665  			Containers: []core.Container{{
 25666  				Name: containerName,
 25667  				SecurityContext: &core.SecurityContext{
 25668  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25669  						HostProcess: &falseVar,
 25670  					},
 25671  				},
 25672  			}},
 25673  		},
 25674  	}, {
 25675  		name:            "Valid HostProcess pod should spec should not validate if allowPrivileged is not set",
 25676  		expectError:     true,
 25677  		allowPrivileged: false,
 25678  		podSpec: &core.PodSpec{
 25679  			SecurityContext: &core.PodSecurityContext{
 25680  				HostNetwork: true,
 25681  			},
 25682  			Containers: []core.Container{{
 25683  				Name: containerName,
 25684  				SecurityContext: &core.SecurityContext{
 25685  					WindowsOptions: &core.WindowsSecurityContextOptions{
 25686  						HostProcess: &trueVar,
 25687  					},
 25688  				},
 25689  			}},
 25690  		},
 25691  	}, {
 25692  		name:            "Non-HostProcess ephemeral container in HostProcess pod should not validate",
 25693  		expectError:     true,
 25694  		allowPrivileged: true,
 25695  		podSpec: &core.PodSpec{
 25696  			SecurityContext: &core.PodSecurityContext{
 25697  				HostNetwork: true,
 25698  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25699  					HostProcess: &trueVar,
 25700  				},
 25701  			},
 25702  			Containers: []core.Container{{
 25703  				Name: containerName,
 25704  			}},
 25705  			EphemeralContainers: []core.EphemeralContainer{{
 25706  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25707  					SecurityContext: &core.SecurityContext{
 25708  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25709  							HostProcess: &falseVar,
 25710  						},
 25711  					},
 25712  				},
 25713  			}},
 25714  		},
 25715  	}, {
 25716  		name:            "HostProcess ephemeral container in HostProcess pod should validate",
 25717  		expectError:     false,
 25718  		allowPrivileged: true,
 25719  		podSpec: &core.PodSpec{
 25720  			SecurityContext: &core.PodSecurityContext{
 25721  				HostNetwork: true,
 25722  				WindowsOptions: &core.WindowsSecurityContextOptions{
 25723  					HostProcess: &trueVar,
 25724  				},
 25725  			},
 25726  			Containers: []core.Container{{
 25727  				Name: containerName,
 25728  			}},
 25729  			EphemeralContainers: []core.EphemeralContainer{{
 25730  				EphemeralContainerCommon: core.EphemeralContainerCommon{},
 25731  			}},
 25732  		},
 25733  	}, {
 25734  		name:            "Non-HostProcess ephemeral container in Non-HostProcess pod should validate",
 25735  		expectError:     false,
 25736  		allowPrivileged: true,
 25737  		podSpec: &core.PodSpec{
 25738  			Containers: []core.Container{{
 25739  				Name: containerName,
 25740  			}},
 25741  			EphemeralContainers: []core.EphemeralContainer{{
 25742  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25743  					SecurityContext: &core.SecurityContext{
 25744  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25745  							HostProcess: &falseVar,
 25746  						},
 25747  					},
 25748  				},
 25749  			}},
 25750  		},
 25751  	}, {
 25752  		name:            "HostProcess ephemeral container in Non-HostProcess pod should not validate",
 25753  		expectError:     true,
 25754  		allowPrivileged: true,
 25755  		podSpec: &core.PodSpec{
 25756  			Containers: []core.Container{{
 25757  				Name: containerName,
 25758  			}},
 25759  			EphemeralContainers: []core.EphemeralContainer{{
 25760  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 25761  					SecurityContext: &core.SecurityContext{
 25762  						WindowsOptions: &core.WindowsSecurityContextOptions{
 25763  							HostProcess: &trueVar,
 25764  						},
 25765  					},
 25766  				},
 25767  			}},
 25768  		},
 25769  	},
 25770  	}
 25771  
 25772  	for _, testCase := range testCases {
 25773  		t.Run(testCase.name, func(t *testing.T) {
 25774  
 25775  			capabilities.SetForTests(capabilities.Capabilities{
 25776  				AllowPrivileged: testCase.allowPrivileged,
 25777  			})
 25778  
 25779  			errs := validateWindowsHostProcessPod(testCase.podSpec, field.NewPath("spec"))
 25780  			if testCase.expectError && len(errs) == 0 {
 25781  				t.Errorf("Unexpected success")
 25782  			}
 25783  			if !testCase.expectError && len(errs) != 0 {
 25784  				t.Errorf("Unexpected error(s): %v", errs)
 25785  			}
 25786  		})
 25787  	}
 25788  }
 25789  
 25790  func TestValidateOS(t *testing.T) {
 25791  	testCases := []struct {
 25792  		name        string
 25793  		expectError bool
 25794  		podSpec     *core.PodSpec
 25795  	}{{
 25796  		name:        "no OS field, featuregate",
 25797  		expectError: false,
 25798  		podSpec:     &core.PodSpec{OS: nil},
 25799  	}, {
 25800  		name:        "empty OS field, featuregate",
 25801  		expectError: true,
 25802  		podSpec:     &core.PodSpec{OS: &core.PodOS{}},
 25803  	}, {
 25804  		name:        "OS field, featuregate, valid OS",
 25805  		expectError: false,
 25806  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Linux}},
 25807  	}, {
 25808  		name:        "OS field, featuregate, valid OS",
 25809  		expectError: false,
 25810  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Windows}},
 25811  	}, {
 25812  		name:        "OS field, featuregate, empty OS",
 25813  		expectError: true,
 25814  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: ""}},
 25815  	}, {
 25816  		name:        "OS field, featuregate, invalid OS",
 25817  		expectError: true,
 25818  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}},
 25819  	},
 25820  	}
 25821  	for _, testCase := range testCases {
 25822  		t.Run(testCase.name, func(t *testing.T) {
 25823  			errs := validateOS(testCase.podSpec, field.NewPath("spec"), PodValidationOptions{})
 25824  			if testCase.expectError && len(errs) == 0 {
 25825  				t.Errorf("Unexpected success")
 25826  			}
 25827  			if !testCase.expectError && len(errs) != 0 {
 25828  				t.Errorf("Unexpected error(s): %v", errs)
 25829  			}
 25830  		})
 25831  	}
 25832  }
 25833  
 25834  func TestValidateAppArmorProfileFormat(t *testing.T) {
 25835  	tests := []struct {
 25836  		profile     string
 25837  		expectValid bool
 25838  	}{
 25839  		{"", true},
 25840  		{v1.DeprecatedAppArmorBetaProfileRuntimeDefault, true},
 25841  		{v1.DeprecatedAppArmorBetaProfileNameUnconfined, true},
 25842  		{"baz", false}, // Missing local prefix.
 25843  		{v1.DeprecatedAppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
 25844  		{v1.DeprecatedAppArmorBetaProfileNamePrefix + "foo-bar", true},
 25845  	}
 25846  
 25847  	for _, test := range tests {
 25848  		err := ValidateAppArmorProfileFormat(test.profile)
 25849  		if test.expectValid {
 25850  			assert.NoError(t, err, "Profile %s should be valid", test.profile)
 25851  		} else {
 25852  			assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
 25853  		}
 25854  	}
 25855  }
 25856  
 25857  func TestValidateDownwardAPIHostIPs(t *testing.T) {
 25858  	testCases := []struct {
 25859  		name           string
 25860  		expectError    bool
 25861  		featureEnabled bool
 25862  		fieldSel       *core.ObjectFieldSelector
 25863  	}{
 25864  		{
 25865  			name:           "has no hostIPs field, featuregate enabled",
 25866  			expectError:    false,
 25867  			featureEnabled: true,
 25868  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
 25869  		},
 25870  		{
 25871  			name:           "has hostIPs field, featuregate enabled",
 25872  			expectError:    false,
 25873  			featureEnabled: true,
 25874  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
 25875  		},
 25876  	}
 25877  	for _, testCase := range testCases {
 25878  		t.Run(testCase.name, func(t *testing.T) {
 25879  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)()
 25880  
 25881  			errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled})
 25882  			if testCase.expectError && len(errs) == 0 {
 25883  				t.Errorf("Unexpected success")
 25884  			}
 25885  			if !testCase.expectError && len(errs) != 0 {
 25886  				t.Errorf("Unexpected error(s): %v", errs)
 25887  			}
 25888  		})
 25889  	}
 25890  }
 25891  
 25892  func TestValidatePVSecretReference(t *testing.T) {
 25893  	rootFld := field.NewPath("name")
 25894  	type args struct {
 25895  		secretRef *core.SecretReference
 25896  		fldPath   *field.Path
 25897  	}
 25898  	tests := []struct {
 25899  		name          string
 25900  		args          args
 25901  		expectError   bool
 25902  		expectedError string
 25903  	}{{
 25904  		name:          "invalid secret ref name",
 25905  		args:          args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld},
 25906  		expectError:   true,
 25907  		expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
 25908  	}, {
 25909  		name:          "invalid secret ref namespace",
 25910  		args:          args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld},
 25911  		expectError:   true,
 25912  		expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg,
 25913  	}, {
 25914  		name:          "invalid secret: missing namespace",
 25915  		args:          args{&core.SecretReference{Name: "valid"}, rootFld},
 25916  		expectError:   true,
 25917  		expectedError: "name.namespace: Required value",
 25918  	}, {
 25919  		name:          "invalid secret : missing name",
 25920  		args:          args{&core.SecretReference{Namespace: "default"}, rootFld},
 25921  		expectError:   true,
 25922  		expectedError: "name.name: Required value",
 25923  	}, {
 25924  		name:          "valid secret",
 25925  		args:          args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld},
 25926  		expectError:   false,
 25927  		expectedError: "",
 25928  	},
 25929  	}
 25930  	for _, tt := range tests {
 25931  		t.Run(tt.name, func(t *testing.T) {
 25932  			errs := validatePVSecretReference(tt.args.secretRef, tt.args.fldPath)
 25933  			if tt.expectError && len(errs) == 0 {
 25934  				t.Errorf("Unexpected success")
 25935  			}
 25936  			if tt.expectError && len(errs) != 0 {
 25937  				str := errs[0].Error()
 25938  				if str != "" && !strings.Contains(str, tt.expectedError) {
 25939  					t.Errorf("%s: expected error detail either empty or %q, got %q", tt.name, tt.expectedError, str)
 25940  				}
 25941  			}
 25942  			if !tt.expectError && len(errs) != 0 {
 25943  				t.Errorf("Unexpected error(s): %v", errs)
 25944  			}
 25945  		})
 25946  	}
 25947  }
 25948  
 25949  func TestValidateDynamicResourceAllocation(t *testing.T) {
 25950  	externalClaimName := "some-claim"
 25951  	externalClaimTemplateName := "some-claim-template"
 25952  	goodClaimSource := core.ClaimSource{
 25953  		ResourceClaimName: &externalClaimName,
 25954  	}
 25955  	shortPodName := &metav1.ObjectMeta{
 25956  		Name: "some-pod",
 25957  	}
 25958  	brokenPodName := &metav1.ObjectMeta{
 25959  		Name: ".dot.com",
 25960  	}
 25961  	goodClaimTemplate := core.PodSpec{
 25962  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}},
 25963  		RestartPolicy: core.RestartPolicyAlways,
 25964  		DNSPolicy:     core.DNSClusterFirst,
 25965  		ResourceClaims: []core.PodResourceClaim{{
 25966  			Name: "my-claim-template",
 25967  			Source: core.ClaimSource{
 25968  				ResourceClaimTemplateName: &externalClaimTemplateName,
 25969  			},
 25970  		}},
 25971  	}
 25972  	goodClaimReference := core.PodSpec{
 25973  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}},
 25974  		RestartPolicy: core.RestartPolicyAlways,
 25975  		DNSPolicy:     core.DNSClusterFirst,
 25976  		ResourceClaims: []core.PodResourceClaim{{
 25977  			Name: "my-claim-reference",
 25978  			Source: core.ClaimSource{
 25979  				ResourceClaimName: &externalClaimName,
 25980  			},
 25981  		}},
 25982  	}
 25983  
 25984  	successCases := map[string]core.PodSpec{
 25985  		"resource claim reference": goodClaimTemplate,
 25986  		"resource claim template":  goodClaimTemplate,
 25987  		"multiple claims": {
 25988  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}},
 25989  			RestartPolicy: core.RestartPolicyAlways,
 25990  			DNSPolicy:     core.DNSClusterFirst,
 25991  			ResourceClaims: []core.PodResourceClaim{{
 25992  				Name:   "my-claim",
 25993  				Source: goodClaimSource,
 25994  			}, {
 25995  				Name:   "another-claim",
 25996  				Source: goodClaimSource,
 25997  			}},
 25998  		},
 25999  		"init container": {
 26000  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26001  			InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26002  			RestartPolicy:  core.RestartPolicyAlways,
 26003  			DNSPolicy:      core.DNSClusterFirst,
 26004  			ResourceClaims: []core.PodResourceClaim{{
 26005  				Name:   "my-claim",
 26006  				Source: goodClaimSource,
 26007  			}},
 26008  		},
 26009  	}
 26010  	for k, v := range successCases {
 26011  		t.Run(k, func(t *testing.T) {
 26012  			if errs := ValidatePodSpec(&v, shortPodName, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
 26013  				t.Errorf("expected success: %v", errs)
 26014  			}
 26015  		})
 26016  	}
 26017  
 26018  	failureCases := map[string]core.PodSpec{
 26019  		"pod claim name with prefix": {
 26020  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 26021  			RestartPolicy: core.RestartPolicyAlways,
 26022  			DNSPolicy:     core.DNSClusterFirst,
 26023  			ResourceClaims: []core.PodResourceClaim{{
 26024  				Name:   "../my-claim",
 26025  				Source: goodClaimSource,
 26026  			}},
 26027  		},
 26028  		"pod claim name with path": {
 26029  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 26030  			RestartPolicy: core.RestartPolicyAlways,
 26031  			DNSPolicy:     core.DNSClusterFirst,
 26032  			ResourceClaims: []core.PodResourceClaim{{
 26033  				Name:   "my/claim",
 26034  				Source: goodClaimSource,
 26035  			}},
 26036  		},
 26037  		"pod claim name empty": {
 26038  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 26039  			RestartPolicy: core.RestartPolicyAlways,
 26040  			DNSPolicy:     core.DNSClusterFirst,
 26041  			ResourceClaims: []core.PodResourceClaim{{
 26042  				Name:   "",
 26043  				Source: goodClaimSource,
 26044  			}},
 26045  		},
 26046  		"duplicate pod claim entries": {
 26047  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 26048  			RestartPolicy: core.RestartPolicyAlways,
 26049  			DNSPolicy:     core.DNSClusterFirst,
 26050  			ResourceClaims: []core.PodResourceClaim{{
 26051  				Name:   "my-claim",
 26052  				Source: goodClaimSource,
 26053  			}, {
 26054  				Name:   "my-claim",
 26055  				Source: goodClaimSource,
 26056  			}},
 26057  		},
 26058  		"resource claim source empty": {
 26059  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26060  			RestartPolicy: core.RestartPolicyAlways,
 26061  			DNSPolicy:     core.DNSClusterFirst,
 26062  			ResourceClaims: []core.PodResourceClaim{{
 26063  				Name:   "my-claim",
 26064  				Source: core.ClaimSource{},
 26065  			}},
 26066  		},
 26067  		"resource claim reference and template": {
 26068  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26069  			RestartPolicy: core.RestartPolicyAlways,
 26070  			DNSPolicy:     core.DNSClusterFirst,
 26071  			ResourceClaims: []core.PodResourceClaim{{
 26072  				Name: "my-claim",
 26073  				Source: core.ClaimSource{
 26074  					ResourceClaimName:         &externalClaimName,
 26075  					ResourceClaimTemplateName: &externalClaimTemplateName,
 26076  				},
 26077  			}},
 26078  		},
 26079  		"claim not found": {
 26080  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}},
 26081  			RestartPolicy: core.RestartPolicyAlways,
 26082  			DNSPolicy:     core.DNSClusterFirst,
 26083  			ResourceClaims: []core.PodResourceClaim{{
 26084  				Name:   "my-claim",
 26085  				Source: goodClaimSource,
 26086  			}},
 26087  		},
 26088  		"claim name empty": {
 26089  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}},
 26090  			RestartPolicy: core.RestartPolicyAlways,
 26091  			DNSPolicy:     core.DNSClusterFirst,
 26092  			ResourceClaims: []core.PodResourceClaim{{
 26093  				Name:   "my-claim",
 26094  				Source: goodClaimSource,
 26095  			}},
 26096  		},
 26097  		"pod claim name duplicates": {
 26098  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}},
 26099  			RestartPolicy: core.RestartPolicyAlways,
 26100  			DNSPolicy:     core.DNSClusterFirst,
 26101  			ResourceClaims: []core.PodResourceClaim{{
 26102  				Name:   "my-claim",
 26103  				Source: goodClaimSource,
 26104  			}},
 26105  		},
 26106  		"no claims defined": {
 26107  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26108  			RestartPolicy: core.RestartPolicyAlways,
 26109  			DNSPolicy:     core.DNSClusterFirst,
 26110  		},
 26111  		"duplicate pod claim name": {
 26112  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26113  			RestartPolicy: core.RestartPolicyAlways,
 26114  			DNSPolicy:     core.DNSClusterFirst,
 26115  			ResourceClaims: []core.PodResourceClaim{{
 26116  				Name:   "my-claim",
 26117  				Source: goodClaimSource,
 26118  			}, {
 26119  				Name:   "my-claim",
 26120  				Source: goodClaimSource,
 26121  			}},
 26122  		},
 26123  		"ephemeral container don't support resource requirements": {
 26124  			Containers:          []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 26125  			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"}},
 26126  			RestartPolicy:       core.RestartPolicyAlways,
 26127  			DNSPolicy:           core.DNSClusterFirst,
 26128  			ResourceClaims: []core.PodResourceClaim{{
 26129  				Name:   "my-claim",
 26130  				Source: goodClaimSource,
 26131  			}},
 26132  		},
 26133  		"invalid claim template name": func() core.PodSpec {
 26134  			spec := goodClaimTemplate.DeepCopy()
 26135  			notLabel := ".foo_bar"
 26136  			spec.ResourceClaims[0].Source.ResourceClaimTemplateName = &notLabel
 26137  			return *spec
 26138  		}(),
 26139  		"invalid claim reference name": func() core.PodSpec {
 26140  			spec := goodClaimReference.DeepCopy()
 26141  			notLabel := ".foo_bar"
 26142  			spec.ResourceClaims[0].Source.ResourceClaimName = &notLabel
 26143  			return *spec
 26144  		}(),
 26145  	}
 26146  	for k, v := range failureCases {
 26147  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
 26148  			t.Errorf("expected failure for %q", k)
 26149  		}
 26150  	}
 26151  
 26152  	t.Run("generated-claim-name", func(t *testing.T) {
 26153  		for _, spec := range []*core.PodSpec{&goodClaimTemplate, &goodClaimReference} {
 26154  			claimName := spec.ResourceClaims[0].Name
 26155  			t.Run(claimName, func(t *testing.T) {
 26156  				for _, podMeta := range []*metav1.ObjectMeta{shortPodName, brokenPodName} {
 26157  					t.Run(podMeta.Name, func(t *testing.T) {
 26158  						errs := ValidatePodSpec(spec, podMeta, field.NewPath("field"), PodValidationOptions{})
 26159  						// Only one out of the four combinations fails.
 26160  						expectError := spec == &goodClaimTemplate && podMeta == brokenPodName
 26161  						if expectError && len(errs) == 0 {
 26162  							t.Error("did not get the expected failure")
 26163  						}
 26164  						if !expectError && len(errs) > 0 {
 26165  							t.Errorf("unexpected failures: %+v", errs)
 26166  						}
 26167  					})
 26168  				}
 26169  			})
 26170  		}
 26171  	})
 26172  }
 26173  
 26174  func TestValidateLoadBalancerStatus(t *testing.T) {
 26175  	ipModeVIP := core.LoadBalancerIPModeVIP
 26176  	ipModeProxy := core.LoadBalancerIPModeProxy
 26177  	ipModeDummy := core.LoadBalancerIPMode("dummy")
 26178  
 26179  	testCases := []struct {
 26180  		name          string
 26181  		ipModeEnabled bool
 26182  		nonLBAllowed  bool
 26183  		tweakLBStatus func(s *core.LoadBalancerStatus)
 26184  		tweakSvcSpec  func(s *core.ServiceSpec)
 26185  		numErrs       int
 26186  	}{
 26187  		{
 26188  			name:         "type is not LB",
 26189  			nonLBAllowed: false,
 26190  			tweakSvcSpec: func(s *core.ServiceSpec) {
 26191  				s.Type = core.ServiceTypeClusterIP
 26192  			},
 26193  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26194  				s.Ingress = []core.LoadBalancerIngress{{
 26195  					IP: "1.2.3.4",
 26196  				}}
 26197  			},
 26198  			numErrs: 1,
 26199  		}, {
 26200  			name:         "type is not LB. back-compat",
 26201  			nonLBAllowed: true,
 26202  			tweakSvcSpec: func(s *core.ServiceSpec) {
 26203  				s.Type = core.ServiceTypeClusterIP
 26204  			},
 26205  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26206  				s.Ingress = []core.LoadBalancerIngress{{
 26207  					IP: "1.2.3.4",
 26208  				}}
 26209  			},
 26210  			numErrs: 0,
 26211  		}, {
 26212  			name:          "valid vip ipMode",
 26213  			ipModeEnabled: true,
 26214  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26215  				s.Ingress = []core.LoadBalancerIngress{{
 26216  					IP:     "1.2.3.4",
 26217  					IPMode: &ipModeVIP,
 26218  				}}
 26219  			},
 26220  			numErrs: 0,
 26221  		}, {
 26222  			name:          "valid proxy ipMode",
 26223  			ipModeEnabled: true,
 26224  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26225  				s.Ingress = []core.LoadBalancerIngress{{
 26226  					IP:     "1.2.3.4",
 26227  					IPMode: &ipModeProxy,
 26228  				}}
 26229  			},
 26230  			numErrs: 0,
 26231  		}, {
 26232  			name:          "invalid ipMode",
 26233  			ipModeEnabled: true,
 26234  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26235  				s.Ingress = []core.LoadBalancerIngress{{
 26236  					IP:     "1.2.3.4",
 26237  					IPMode: &ipModeDummy,
 26238  				}}
 26239  			},
 26240  			numErrs: 1,
 26241  		}, {
 26242  			name:          "missing ipMode with LoadbalancerIPMode enabled",
 26243  			ipModeEnabled: true,
 26244  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26245  				s.Ingress = []core.LoadBalancerIngress{{
 26246  					IP: "1.2.3.4",
 26247  				}}
 26248  			},
 26249  			numErrs: 1,
 26250  		}, {
 26251  			name:          "missing ipMode with LoadbalancerIPMode disabled",
 26252  			ipModeEnabled: false,
 26253  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26254  				s.Ingress = []core.LoadBalancerIngress{{
 26255  					IP: "1.2.3.4",
 26256  				}}
 26257  			},
 26258  			numErrs: 0,
 26259  		}, {
 26260  			name:          "missing ip with ipMode present",
 26261  			ipModeEnabled: true,
 26262  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 26263  				s.Ingress = []core.LoadBalancerIngress{{
 26264  					IPMode: &ipModeProxy,
 26265  				}}
 26266  			},
 26267  			numErrs: 1,
 26268  		},
 26269  	}
 26270  	for _, tc := range testCases {
 26271  		t.Run(tc.name, func(t *testing.T) {
 26272  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)()
 26273  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowServiceLBStatusOnNonLB, tc.nonLBAllowed)()
 26274  			status := core.LoadBalancerStatus{}
 26275  			tc.tweakLBStatus(&status)
 26276  			spec := core.ServiceSpec{Type: core.ServiceTypeLoadBalancer}
 26277  			if tc.tweakSvcSpec != nil {
 26278  				tc.tweakSvcSpec(&spec)
 26279  			}
 26280  			errs := ValidateLoadBalancerStatus(&status, field.NewPath("status"), &spec)
 26281  			if len(errs) != tc.numErrs {
 26282  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 26283  			}
 26284  		})
 26285  	}
 26286  }
 26287  
 26288  func TestValidateSleepAction(t *testing.T) {
 26289  	fldPath := field.NewPath("root")
 26290  	getInvalidStr := func(gracePeriod int64) string {
 26291  		return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
 26292  	}
 26293  
 26294  	testCases := []struct {
 26295  		name        string
 26296  		action      *core.SleepAction
 26297  		gracePeriod int64
 26298  		expectErr   field.ErrorList
 26299  	}{
 26300  		{
 26301  			name: "valid setting",
 26302  			action: &core.SleepAction{
 26303  				Seconds: 5,
 26304  			},
 26305  			gracePeriod: 30,
 26306  		},
 26307  		{
 26308  			name: "negative seconds",
 26309  			action: &core.SleepAction{
 26310  				Seconds: -1,
 26311  			},
 26312  			gracePeriod: 30,
 26313  			expectErr:   field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
 26314  		},
 26315  		{
 26316  			name: "longer than gracePeriod",
 26317  			action: &core.SleepAction{
 26318  				Seconds: 5,
 26319  			},
 26320  			gracePeriod: 3,
 26321  			expectErr:   field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
 26322  		},
 26323  	}
 26324  
 26325  	for _, tc := range testCases {
 26326  		t.Run(tc.name, func(t *testing.T) {
 26327  			errs := validateSleepAction(tc.action, tc.gracePeriod, fldPath)
 26328  
 26329  			if len(tc.expectErr) > 0 && len(errs) == 0 {
 26330  				t.Errorf("Unexpected success")
 26331  			} else if len(tc.expectErr) == 0 && len(errs) != 0 {
 26332  				t.Errorf("Unexpected error(s): %v", errs)
 26333  			} else if len(tc.expectErr) > 0 {
 26334  				if tc.expectErr[0].Error() != errs[0].Error() {
 26335  					t.Errorf("Unexpected error(s): %v", errs)
 26336  				}
 26337  			}
 26338  		})
 26339  	}
 26340  }
 26341  

View as plain text