k8s.io/kubernetes@v1.29.3/pkg/apis/core/validation/validation_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"math"
    23  	"reflect"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"google.golang.org/protobuf/proto"
    34  	v1 "k8s.io/api/core/v1"
    35  	"k8s.io/apimachinery/pkg/api/resource"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/util/intstr"
    38  	"k8s.io/apimachinery/pkg/util/sets"
    39  	"k8s.io/apimachinery/pkg/util/validation"
    40  	"k8s.io/apimachinery/pkg/util/validation/field"
    41  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    42  	"k8s.io/component-base/featuregate"
    43  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    44  	kubeletapis "k8s.io/kubelet/pkg/apis"
    45  	"k8s.io/kubernetes/pkg/apis/core"
    46  	"k8s.io/kubernetes/pkg/capabilities"
    47  	"k8s.io/kubernetes/pkg/features"
    48  	utilpointer "k8s.io/utils/pointer"
    49  	"k8s.io/utils/ptr"
    50  )
    51  
    52  const (
    53  	dnsLabelErrMsg          = "a lowercase RFC 1123 label must consist of"
    54  	dnsSubdomainLabelErrMsg = "a lowercase RFC 1123 subdomain"
    55  	envVarNameErrMsg        = "a valid environment variable name must consist of"
    56  	defaultGracePeriod      = int64(30)
    57  )
    58  
    59  var (
    60  	containerRestartPolicyAlways    = core.ContainerRestartPolicyAlways
    61  	containerRestartPolicyOnFailure = core.ContainerRestartPolicy("OnFailure")
    62  	containerRestartPolicyNever     = core.ContainerRestartPolicy("Never")
    63  	containerRestartPolicyInvalid   = core.ContainerRestartPolicy("invalid")
    64  	containerRestartPolicyEmpty     = core.ContainerRestartPolicy("")
    65  )
    66  
    67  type topologyPair struct {
    68  	key   string
    69  	value string
    70  }
    71  
    72  func line() string {
    73  	_, _, line, ok := runtime.Caller(1)
    74  	var s string
    75  	if ok {
    76  		s = fmt.Sprintf("%d", line)
    77  	} else {
    78  		s = "<??>"
    79  	}
    80  	return s
    81  }
    82  
    83  func prettyErrorList(errs field.ErrorList) string {
    84  	var s string
    85  	for _, e := range errs {
    86  		s += fmt.Sprintf("\t%s\n", e)
    87  	}
    88  	return s
    89  }
    90  
    91  func newHostPathType(pathType string) *core.HostPathType {
    92  	hostPathType := new(core.HostPathType)
    93  	*hostPathType = core.HostPathType(pathType)
    94  	return hostPathType
    95  }
    96  
    97  func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume {
    98  	objMeta := metav1.ObjectMeta{Name: name}
    99  	if namespace != "" {
   100  		objMeta.Namespace = namespace
   101  	}
   102  
   103  	return &core.PersistentVolume{
   104  		ObjectMeta: objMeta,
   105  		Spec:       spec,
   106  	}
   107  }
   108  
   109  func TestValidatePersistentVolumes(t *testing.T) {
   110  	validMode := core.PersistentVolumeFilesystem
   111  	invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
   112  	scenarios := map[string]struct {
   113  		isExpectedFailure           bool
   114  		enableVolumeAttributesClass bool
   115  		volume                      *core.PersistentVolume
   116  	}{
   117  		"good-volume": {
   118  			isExpectedFailure: false,
   119  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   120  				Capacity: core.ResourceList{
   121  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   122  				},
   123  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   124  				PersistentVolumeSource: core.PersistentVolumeSource{
   125  					HostPath: &core.HostPathVolumeSource{
   126  						Path: "/foo",
   127  						Type: newHostPathType(string(core.HostPathDirectory)),
   128  					},
   129  				},
   130  			}),
   131  		},
   132  		"good-volume-with-capacity-unit": {
   133  			isExpectedFailure: false,
   134  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   135  				Capacity: core.ResourceList{
   136  					core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"),
   137  				},
   138  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   139  				PersistentVolumeSource: core.PersistentVolumeSource{
   140  					HostPath: &core.HostPathVolumeSource{
   141  						Path: "/foo",
   142  						Type: newHostPathType(string(core.HostPathDirectory)),
   143  					},
   144  				},
   145  			}),
   146  		},
   147  		"good-volume-without-capacity-unit": {
   148  			isExpectedFailure: false,
   149  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   150  				Capacity: core.ResourceList{
   151  					core.ResourceName(core.ResourceStorage): resource.MustParse("10"),
   152  				},
   153  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   154  				PersistentVolumeSource: core.PersistentVolumeSource{
   155  					HostPath: &core.HostPathVolumeSource{
   156  						Path: "/foo",
   157  						Type: newHostPathType(string(core.HostPathDirectory)),
   158  					},
   159  				},
   160  			}),
   161  		},
   162  		"good-volume-with-storage-class": {
   163  			isExpectedFailure: false,
   164  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   165  				Capacity: core.ResourceList{
   166  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   167  				},
   168  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   169  				PersistentVolumeSource: core.PersistentVolumeSource{
   170  					HostPath: &core.HostPathVolumeSource{
   171  						Path: "/foo",
   172  						Type: newHostPathType(string(core.HostPathDirectory)),
   173  					},
   174  				},
   175  				StorageClassName: "valid",
   176  			}),
   177  		},
   178  		"good-volume-with-retain-policy": {
   179  			isExpectedFailure: false,
   180  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   181  				Capacity: core.ResourceList{
   182  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   183  				},
   184  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   185  				PersistentVolumeSource: core.PersistentVolumeSource{
   186  					HostPath: &core.HostPathVolumeSource{
   187  						Path: "/foo",
   188  						Type: newHostPathType(string(core.HostPathDirectory)),
   189  					},
   190  				},
   191  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain,
   192  			}),
   193  		},
   194  		"good-volume-with-volume-mode": {
   195  			isExpectedFailure: false,
   196  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   197  				Capacity: core.ResourceList{
   198  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   199  				},
   200  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   201  				PersistentVolumeSource: core.PersistentVolumeSource{
   202  					HostPath: &core.HostPathVolumeSource{
   203  						Path: "/foo",
   204  						Type: newHostPathType(string(core.HostPathDirectory)),
   205  					},
   206  				},
   207  				VolumeMode: &validMode,
   208  			}),
   209  		},
   210  		"invalid-accessmode": {
   211  			isExpectedFailure: true,
   212  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   213  				Capacity: core.ResourceList{
   214  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   215  				},
   216  				AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
   217  				PersistentVolumeSource: core.PersistentVolumeSource{
   218  					HostPath: &core.HostPathVolumeSource{
   219  						Path: "/foo",
   220  						Type: newHostPathType(string(core.HostPathDirectory)),
   221  					},
   222  				},
   223  			}),
   224  		},
   225  		"invalid-reclaimpolicy": {
   226  			isExpectedFailure: true,
   227  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   228  				Capacity: core.ResourceList{
   229  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   230  				},
   231  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   232  				PersistentVolumeSource: core.PersistentVolumeSource{
   233  					HostPath: &core.HostPathVolumeSource{
   234  						Path: "/foo",
   235  						Type: newHostPathType(string(core.HostPathDirectory)),
   236  					},
   237  				},
   238  				PersistentVolumeReclaimPolicy: "fakeReclaimPolicy",
   239  			}),
   240  		},
   241  		"invalid-volume-mode": {
   242  			isExpectedFailure: true,
   243  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   244  				Capacity: core.ResourceList{
   245  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   246  				},
   247  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   248  				PersistentVolumeSource: core.PersistentVolumeSource{
   249  					HostPath: &core.HostPathVolumeSource{
   250  						Path: "/foo",
   251  						Type: newHostPathType(string(core.HostPathDirectory)),
   252  					},
   253  				},
   254  				VolumeMode: &invalidMode,
   255  			}),
   256  		},
   257  		"with-read-write-once-pod": {
   258  			isExpectedFailure: false,
   259  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   260  				Capacity: core.ResourceList{
   261  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   262  				},
   263  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
   264  				PersistentVolumeSource: core.PersistentVolumeSource{
   265  					HostPath: &core.HostPathVolumeSource{
   266  						Path: "/foo",
   267  						Type: newHostPathType(string(core.HostPathDirectory)),
   268  					},
   269  				},
   270  			}),
   271  		},
   272  		"with-read-write-once-pod-and-others": {
   273  			isExpectedFailure: true,
   274  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   275  				Capacity: core.ResourceList{
   276  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   277  				},
   278  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
   279  				PersistentVolumeSource: core.PersistentVolumeSource{
   280  					HostPath: &core.HostPathVolumeSource{
   281  						Path: "/foo",
   282  						Type: newHostPathType(string(core.HostPathDirectory)),
   283  					},
   284  				},
   285  			}),
   286  		},
   287  		"unexpected-namespace": {
   288  			isExpectedFailure: true,
   289  			volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
   290  				Capacity: core.ResourceList{
   291  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   292  				},
   293  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   294  				PersistentVolumeSource: core.PersistentVolumeSource{
   295  					HostPath: &core.HostPathVolumeSource{
   296  						Path: "/foo",
   297  						Type: newHostPathType(string(core.HostPathDirectory)),
   298  					},
   299  				},
   300  			}),
   301  		},
   302  		"missing-volume-source": {
   303  			isExpectedFailure: true,
   304  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   305  				Capacity: core.ResourceList{
   306  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   307  				},
   308  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   309  			}),
   310  		},
   311  		"bad-name": {
   312  			isExpectedFailure: true,
   313  			volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{
   314  				Capacity: core.ResourceList{
   315  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   316  				},
   317  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   318  				PersistentVolumeSource: core.PersistentVolumeSource{
   319  					HostPath: &core.HostPathVolumeSource{
   320  						Path: "/foo",
   321  						Type: newHostPathType(string(core.HostPathDirectory)),
   322  					},
   323  				},
   324  			}),
   325  		},
   326  		"missing-name": {
   327  			isExpectedFailure: true,
   328  			volume: testVolume("", "", core.PersistentVolumeSpec{
   329  				Capacity: core.ResourceList{
   330  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   331  				},
   332  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   333  				PersistentVolumeSource: core.PersistentVolumeSource{
   334  					HostPath: &core.HostPathVolumeSource{
   335  						Path: "/foo",
   336  						Type: newHostPathType(string(core.HostPathDirectory)),
   337  					},
   338  				},
   339  			}),
   340  		},
   341  		"missing-capacity": {
   342  			isExpectedFailure: true,
   343  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   344  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   345  				PersistentVolumeSource: core.PersistentVolumeSource{
   346  					HostPath: &core.HostPathVolumeSource{
   347  						Path: "/foo",
   348  						Type: newHostPathType(string(core.HostPathDirectory)),
   349  					},
   350  				},
   351  			}),
   352  		},
   353  		"bad-volume-zero-capacity": {
   354  			isExpectedFailure: true,
   355  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   356  				Capacity: core.ResourceList{
   357  					core.ResourceName(core.ResourceStorage): resource.MustParse("0"),
   358  				},
   359  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   360  				PersistentVolumeSource: core.PersistentVolumeSource{
   361  					HostPath: &core.HostPathVolumeSource{
   362  						Path: "/foo",
   363  						Type: newHostPathType(string(core.HostPathDirectory)),
   364  					},
   365  				},
   366  			}),
   367  		},
   368  		"missing-accessmodes": {
   369  			isExpectedFailure: true,
   370  			volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{
   371  				Capacity: core.ResourceList{
   372  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   373  				},
   374  				PersistentVolumeSource: core.PersistentVolumeSource{
   375  					HostPath: &core.HostPathVolumeSource{
   376  						Path: "/foo",
   377  						Type: newHostPathType(string(core.HostPathDirectory)),
   378  					},
   379  				},
   380  			}),
   381  		},
   382  		"too-many-sources": {
   383  			isExpectedFailure: true,
   384  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   385  				Capacity: core.ResourceList{
   386  					core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
   387  				},
   388  				PersistentVolumeSource: core.PersistentVolumeSource{
   389  					HostPath: &core.HostPathVolumeSource{
   390  						Path: "/foo",
   391  						Type: newHostPathType(string(core.HostPathDirectory)),
   392  					},
   393  					GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"},
   394  				},
   395  			}),
   396  		},
   397  		"host mount of / with recycle reclaim policy": {
   398  			isExpectedFailure: true,
   399  			volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
   400  				Capacity: core.ResourceList{
   401  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   402  				},
   403  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   404  				PersistentVolumeSource: core.PersistentVolumeSource{
   405  					HostPath: &core.HostPathVolumeSource{
   406  						Path: "/",
   407  						Type: newHostPathType(string(core.HostPathDirectory)),
   408  					},
   409  				},
   410  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   411  			}),
   412  		},
   413  		"host mount of / with recycle reclaim policy 2": {
   414  			isExpectedFailure: true,
   415  			volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
   416  				Capacity: core.ResourceList{
   417  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   418  				},
   419  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   420  				PersistentVolumeSource: core.PersistentVolumeSource{
   421  					HostPath: &core.HostPathVolumeSource{
   422  						Path: "/a/..",
   423  						Type: newHostPathType(string(core.HostPathDirectory)),
   424  					},
   425  				},
   426  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   427  			}),
   428  		},
   429  		"invalid-storage-class-name": {
   430  			isExpectedFailure: true,
   431  			volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{
   432  				Capacity: core.ResourceList{
   433  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   434  				},
   435  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   436  				PersistentVolumeSource: core.PersistentVolumeSource{
   437  					HostPath: &core.HostPathVolumeSource{
   438  						Path: "/foo",
   439  						Type: newHostPathType(string(core.HostPathDirectory)),
   440  					},
   441  				},
   442  				StorageClassName: "-invalid-",
   443  			}),
   444  		},
   445  		"bad-hostpath-volume-backsteps": {
   446  			isExpectedFailure: true,
   447  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   448  				Capacity: core.ResourceList{
   449  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   450  				},
   451  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   452  				PersistentVolumeSource: core.PersistentVolumeSource{
   453  					HostPath: &core.HostPathVolumeSource{
   454  						Path: "/foo/..",
   455  						Type: newHostPathType(string(core.HostPathDirectory)),
   456  					},
   457  				},
   458  				StorageClassName: "backstep-hostpath",
   459  			}),
   460  		},
   461  		"volume-node-affinity": {
   462  			isExpectedFailure: false,
   463  			volume:            testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
   464  		},
   465  		"volume-empty-node-affinity": {
   466  			isExpectedFailure: true,
   467  			volume:            testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}),
   468  		},
   469  		"volume-bad-node-affinity": {
   470  			isExpectedFailure: true,
   471  			volume: testVolumeWithNodeAffinity(
   472  				&core.VolumeNodeAffinity{
   473  					Required: &core.NodeSelector{
   474  						NodeSelectorTerms: []core.NodeSelectorTerm{{
   475  							MatchExpressions: []core.NodeSelectorRequirement{{
   476  								Operator: core.NodeSelectorOpIn,
   477  								Values:   []string{"test-label-value"},
   478  							}},
   479  						}},
   480  					},
   481  				}),
   482  		},
   483  		"invalid-volume-attributes-class-name": {
   484  			isExpectedFailure:           true,
   485  			enableVolumeAttributesClass: true,
   486  			volume: testVolume("invalid-volume-attributes-class-name", "", core.PersistentVolumeSpec{
   487  				Capacity: core.ResourceList{
   488  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   489  				},
   490  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   491  				PersistentVolumeSource: core.PersistentVolumeSource{
   492  					HostPath: &core.HostPathVolumeSource{
   493  						Path: "/foo",
   494  						Type: newHostPathType(string(core.HostPathDirectory)),
   495  					},
   496  				},
   497  				StorageClassName:          "invalid",
   498  				VolumeAttributesClassName: ptr.To("-invalid-"),
   499  			}),
   500  		},
   501  		"invalid-empty-volume-attributes-class-name": {
   502  			isExpectedFailure:           true,
   503  			enableVolumeAttributesClass: true,
   504  			volume: testVolume("invalid-empty-volume-attributes-class-name", "", core.PersistentVolumeSpec{
   505  				Capacity: core.ResourceList{
   506  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   507  				},
   508  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   509  				PersistentVolumeSource: core.PersistentVolumeSource{
   510  					HostPath: &core.HostPathVolumeSource{
   511  						Path: "/foo",
   512  						Type: newHostPathType(string(core.HostPathDirectory)),
   513  					},
   514  				},
   515  				StorageClassName:          "invalid",
   516  				VolumeAttributesClassName: ptr.To(""),
   517  			}),
   518  		},
   519  		"volume-with-good-volume-attributes-class-and-matched-volume-resource-when-feature-gate-is-on": {
   520  			isExpectedFailure:           false,
   521  			enableVolumeAttributesClass: true,
   522  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   523  				Capacity: core.ResourceList{
   524  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   525  				},
   526  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   527  				PersistentVolumeSource: core.PersistentVolumeSource{
   528  					CSI: &core.CSIPersistentVolumeSource{
   529  						Driver:       "test-driver",
   530  						VolumeHandle: "test-123",
   531  					},
   532  				},
   533  				StorageClassName:          "valid",
   534  				VolumeAttributesClassName: ptr.To("valid"),
   535  			}),
   536  		},
   537  		"volume-with-good-volume-attributes-class-and-mismatched-volume-resource-when-feature-gate-is-on": {
   538  			isExpectedFailure:           true,
   539  			enableVolumeAttributesClass: true,
   540  			volume: testVolume("foo", "", core.PersistentVolumeSpec{
   541  				Capacity: core.ResourceList{
   542  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   543  				},
   544  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   545  				PersistentVolumeSource: core.PersistentVolumeSource{
   546  					HostPath: &core.HostPathVolumeSource{
   547  						Path: "/foo",
   548  						Type: newHostPathType(string(core.HostPathDirectory)),
   549  					},
   550  				},
   551  				StorageClassName:          "valid",
   552  				VolumeAttributesClassName: ptr.To("valid"),
   553  			}),
   554  		},
   555  	}
   556  
   557  	for name, scenario := range scenarios {
   558  		t.Run(name, func(t *testing.T) {
   559  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
   560  
   561  			opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
   562  			errs := ValidatePersistentVolume(scenario.volume, opts)
   563  			if len(errs) == 0 && scenario.isExpectedFailure {
   564  				t.Errorf("Unexpected success for scenario: %s", name)
   565  			}
   566  			if len(errs) > 0 && !scenario.isExpectedFailure {
   567  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   568  			}
   569  		})
   570  	}
   571  
   572  }
   573  
   574  func TestValidatePersistentVolumeSpec(t *testing.T) {
   575  	fsmode := core.PersistentVolumeFilesystem
   576  	blockmode := core.PersistentVolumeBlock
   577  	scenarios := map[string]struct {
   578  		isExpectedFailure bool
   579  		isInlineSpec      bool
   580  		pvSpec            *core.PersistentVolumeSpec
   581  	}{
   582  		"pv-pvspec-valid": {
   583  			isExpectedFailure: false,
   584  			isInlineSpec:      false,
   585  			pvSpec: &core.PersistentVolumeSpec{
   586  				Capacity: core.ResourceList{
   587  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   588  				},
   589  				StorageClassName:              "testclass",
   590  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   591  				AccessModes:                   []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   592  				PersistentVolumeSource: core.PersistentVolumeSource{
   593  					HostPath: &core.HostPathVolumeSource{
   594  						Path: "/foo",
   595  						Type: newHostPathType(string(core.HostPathDirectory)),
   596  					},
   597  				},
   598  				VolumeMode:   &fsmode,
   599  				NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
   600  			},
   601  		},
   602  		"inline-pvspec-with-capacity": {
   603  			isExpectedFailure: true,
   604  			isInlineSpec:      true,
   605  			pvSpec: &core.PersistentVolumeSpec{
   606  				Capacity: core.ResourceList{
   607  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
   608  				},
   609  				PersistentVolumeSource: core.PersistentVolumeSource{
   610  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   611  				},
   612  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   613  			},
   614  		},
   615  		"inline-pvspec-with-podSec": {
   616  			isExpectedFailure: true,
   617  			isInlineSpec:      true,
   618  			pvSpec: &core.PersistentVolumeSpec{
   619  				PersistentVolumeSource: core.PersistentVolumeSource{
   620  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   621  				},
   622  				AccessModes:      []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   623  				StorageClassName: "testclass",
   624  			},
   625  		},
   626  		"inline-pvspec-with-non-fs-volume-mode": {
   627  			isExpectedFailure: true,
   628  			isInlineSpec:      true,
   629  			pvSpec: &core.PersistentVolumeSpec{
   630  				PersistentVolumeSource: core.PersistentVolumeSource{
   631  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   632  				},
   633  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   634  				VolumeMode:  &blockmode,
   635  			},
   636  		},
   637  		"inline-pvspec-with-non-retain-reclaim-policy": {
   638  			isExpectedFailure: true,
   639  			isInlineSpec:      true,
   640  			pvSpec: &core.PersistentVolumeSpec{
   641  				PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
   642  				PersistentVolumeSource: core.PersistentVolumeSource{
   643  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   644  				},
   645  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   646  			},
   647  		},
   648  		"inline-pvspec-with-node-affinity": {
   649  			isExpectedFailure: true,
   650  			isInlineSpec:      true,
   651  			pvSpec: &core.PersistentVolumeSpec{
   652  				PersistentVolumeSource: core.PersistentVolumeSource{
   653  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   654  				},
   655  				AccessModes:  []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   656  				NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
   657  			},
   658  		},
   659  		"inline-pvspec-with-non-csi-source": {
   660  			isExpectedFailure: true,
   661  			isInlineSpec:      true,
   662  			pvSpec: &core.PersistentVolumeSpec{
   663  				PersistentVolumeSource: core.PersistentVolumeSource{
   664  					HostPath: &core.HostPathVolumeSource{
   665  						Path: "/foo",
   666  						Type: newHostPathType(string(core.HostPathDirectory)),
   667  					},
   668  				},
   669  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   670  			},
   671  		},
   672  		"inline-pvspec-valid-with-access-modes-and-mount-options": {
   673  			isExpectedFailure: false,
   674  			isInlineSpec:      true,
   675  			pvSpec: &core.PersistentVolumeSpec{
   676  				PersistentVolumeSource: core.PersistentVolumeSource{
   677  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   678  				},
   679  				AccessModes:  []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   680  				MountOptions: []string{"soft", "read-write"},
   681  			},
   682  		},
   683  		"inline-pvspec-valid-with-access-modes": {
   684  			isExpectedFailure: false,
   685  			isInlineSpec:      true,
   686  			pvSpec: &core.PersistentVolumeSpec{
   687  				PersistentVolumeSource: core.PersistentVolumeSource{
   688  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   689  				},
   690  				AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   691  			},
   692  		},
   693  		"inline-pvspec-with-missing-acess-modes": {
   694  			isExpectedFailure: true,
   695  			isInlineSpec:      true,
   696  			pvSpec: &core.PersistentVolumeSpec{
   697  				PersistentVolumeSource: core.PersistentVolumeSource{
   698  					CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
   699  				},
   700  				MountOptions: []string{"soft", "read-write"},
   701  			},
   702  		},
   703  	}
   704  	for name, scenario := range scenarios {
   705  		opts := PersistentVolumeSpecValidationOptions{}
   706  		errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"), opts)
   707  		if len(errs) == 0 && scenario.isExpectedFailure {
   708  			t.Errorf("Unexpected success for scenario: %s", name)
   709  		}
   710  		if len(errs) > 0 && !scenario.isExpectedFailure {
   711  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   712  		}
   713  	}
   714  }
   715  
   716  func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
   717  	validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
   718  		Capacity: core.ResourceList{
   719  			core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
   720  		},
   721  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   722  		PersistentVolumeSource: core.PersistentVolumeSource{
   723  			HostPath: &core.HostPathVolumeSource{
   724  				Path: "/foo",
   725  				Type: newHostPathType(string(core.HostPathDirectory)),
   726  			},
   727  		},
   728  		StorageClassName: "valid",
   729  	})
   730  	validPvSourceNoUpdate := validVolume.DeepCopy()
   731  	invalidPvSourceUpdateType := validVolume.DeepCopy()
   732  	invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
   733  		FlexVolume: &core.FlexPersistentVolumeSource{
   734  			Driver: "kubernetes.io/blue",
   735  			FSType: "ext4",
   736  		},
   737  	}
   738  	invalidPvSourceUpdateDeep := validVolume.DeepCopy()
   739  	invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
   740  		HostPath: &core.HostPathVolumeSource{
   741  			Path: "/updated",
   742  			Type: newHostPathType(string(core.HostPathDirectory)),
   743  		},
   744  	}
   745  
   746  	validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{
   747  		Capacity: core.ResourceList{
   748  			core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
   749  		},
   750  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
   751  		PersistentVolumeSource: core.PersistentVolumeSource{
   752  			CSI: &core.CSIPersistentVolumeSource{
   753  				Driver:       "come.google.gcepd",
   754  				VolumeHandle: "foobar",
   755  			},
   756  		},
   757  		StorageClassName: "gp2",
   758  	})
   759  
   760  	expandSecretRef := &core.SecretReference{
   761  		Name:      "expansion-secret",
   762  		Namespace: "default",
   763  	}
   764  
   765  	// shortSecretRef refers to the secretRefs which are validated with IsDNS1035Label
   766  	shortSecretName := "key-name"
   767  	shortSecretRef := &core.SecretReference{
   768  		Name:      shortSecretName,
   769  		Namespace: "default",
   770  	}
   771  
   772  	// longSecretRef refers to the secretRefs which are validated with IsDNS1123Subdomain
   773  	longSecretName := "key-name.example.com"
   774  	longSecretRef := &core.SecretReference{
   775  		Name:      longSecretName,
   776  		Namespace: "default",
   777  	}
   778  
   779  	// invalidSecrets missing name, namespace and both
   780  	inValidSecretRef := &core.SecretReference{
   781  		Name:      "",
   782  		Namespace: "",
   783  	}
   784  	invalidSecretRefmissingName := &core.SecretReference{
   785  		Name:      "",
   786  		Namespace: "default",
   787  	}
   788  	invalidSecretRefmissingNamespace := &core.SecretReference{
   789  		Name:      "invalidnamespace",
   790  		Namespace: "",
   791  	}
   792  
   793  	scenarios := map[string]struct {
   794  		isExpectedFailure bool
   795  		oldVolume         *core.PersistentVolume
   796  		newVolume         *core.PersistentVolume
   797  	}{
   798  		"condition-no-update": {
   799  			isExpectedFailure: false,
   800  			oldVolume:         validVolume,
   801  			newVolume:         validPvSourceNoUpdate,
   802  		},
   803  		"condition-update-source-type": {
   804  			isExpectedFailure: true,
   805  			oldVolume:         validVolume,
   806  			newVolume:         invalidPvSourceUpdateType,
   807  		},
   808  		"condition-update-source-deep": {
   809  			isExpectedFailure: true,
   810  			oldVolume:         validVolume,
   811  			newVolume:         invalidPvSourceUpdateDeep,
   812  		},
   813  		"csi-expansion-enabled-with-pv-secret": {
   814  			isExpectedFailure: false,
   815  			oldVolume:         validCSIVolume,
   816  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
   817  		},
   818  		"csi-expansion-enabled-with-old-pv-secret": {
   819  			isExpectedFailure: true,
   820  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, expandSecretRef, "controllerExpand"),
   821  			newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{
   822  				Name:      "foo-secret",
   823  				Namespace: "default",
   824  			}, "controllerExpand"),
   825  		},
   826  		"csi-expansion-enabled-with-shortSecretRef": {
   827  			isExpectedFailure: false,
   828  			oldVolume:         validCSIVolume,
   829  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   830  		},
   831  		"csi-expansion-enabled-with-longSecretRef": {
   832  			isExpectedFailure: false, // updating controllerExpandSecretRef is allowed only from nil
   833  			oldVolume:         validCSIVolume,
   834  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   835  		},
   836  		"csi-expansion-enabled-from-shortSecretRef-to-shortSecretRef": {
   837  			isExpectedFailure: false,
   838  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   839  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   840  		},
   841  		"csi-expansion-enabled-from-shortSecretRef-to-longSecretRef": {
   842  			isExpectedFailure: true, // updating controllerExpandSecretRef is allowed only from nil
   843  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerExpand"),
   844  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   845  		},
   846  		"csi-expansion-enabled-from-longSecretRef-to-longSecretRef": {
   847  			isExpectedFailure: false,
   848  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   849  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerExpand"),
   850  		},
   851  		"csi-cntrlpublish-enabled-with-shortSecretRef": {
   852  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   853  			oldVolume:         validCSIVolume,
   854  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   855  		},
   856  		"csi-cntrlpublish-enabled-with-longSecretRef": {
   857  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   858  			oldVolume:         validCSIVolume,
   859  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   860  		},
   861  		"csi-cntrlpublish-enabled-from-shortSecretRef-to-shortSecretRef": {
   862  			isExpectedFailure: false,
   863  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   864  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   865  		},
   866  		"csi-cntrlpublish-enabled-from-shortSecretRef-to-longSecretRef": {
   867  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   868  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "controllerPublish"),
   869  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   870  		},
   871  		"csi-cntrlpublish-enabled-from-longSecretRef-to-longSecretRef": {
   872  			isExpectedFailure: false,
   873  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   874  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "controllerPublish"),
   875  		},
   876  		"csi-nodepublish-enabled-with-shortSecretRef": {
   877  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   878  			oldVolume:         validCSIVolume,
   879  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   880  		},
   881  		"csi-nodepublish-enabled-with-longSecretRef": {
   882  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   883  			oldVolume:         validCSIVolume,
   884  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   885  		},
   886  		"csi-nodepublish-enabled-from-shortSecretRef-to-shortSecretRef": {
   887  			isExpectedFailure: false,
   888  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   889  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   890  		},
   891  		"csi-nodepublish-enabled-from-shortSecretRef-to-longSecretRef": {
   892  			isExpectedFailure: true,
   893  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodePublish"),
   894  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   895  		},
   896  		"csi-nodepublish-enabled-from-longSecretRef-to-longSecretRef": {
   897  			isExpectedFailure: false,
   898  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   899  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodePublish"),
   900  		},
   901  		"csi-nodestage-enabled-with-shortSecretRef": {
   902  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   903  			oldVolume:         validCSIVolume,
   904  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   905  		},
   906  		"csi-nodestage-enabled-with-longSecretRef": {
   907  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   908  			oldVolume:         validCSIVolume,
   909  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   910  		},
   911  		"csi-nodestage-enabled-from-shortSecretRef-to-longSecretRef": {
   912  			isExpectedFailure: true, // updating secretRef will fail as the object is immutable eventhough the secretRef is valid
   913  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   914  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   915  		},
   916  
   917  		// At present, there is no validation exist for nodeStage secretRef in
   918  		// ValidatePersistentVolumeSpec->validateCSIPersistentVolumeSource, due to that, below
   919  		// checks/validations pass!
   920  
   921  		"csi-nodestage-enabled-from-invalidSecretRef-to-invalidSecretRef": {
   922  			isExpectedFailure: false,
   923  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
   924  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, inValidSecretRef, "nodeStage"),
   925  		},
   926  		"csi-nodestage-enabled-from-invalidSecretRefmissingname-to-invalidSecretRefmissingname": {
   927  			isExpectedFailure: false,
   928  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
   929  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingName, "nodeStage"),
   930  		},
   931  		"csi-nodestage-enabled-from-invalidSecretRefmissingnamespace-to-invalidSecretRefmissingnamespace": {
   932  			isExpectedFailure: false,
   933  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
   934  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, invalidSecretRefmissingNamespace, "nodeStage"),
   935  		},
   936  		"csi-nodestage-enabled-from-shortSecretRef-to-shortSecretRef": {
   937  			isExpectedFailure: false,
   938  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   939  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, shortSecretRef, "nodeStage"),
   940  		},
   941  		"csi-nodestage-enabled-from-longSecretRef-to-longSecretRef": {
   942  			isExpectedFailure: false,
   943  			oldVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   944  			newVolume:         getCSIVolumeWithSecret(validCSIVolume, longSecretRef, "nodeStage"),
   945  		},
   946  	}
   947  	for name, scenario := range scenarios {
   948  		opts := ValidationOptionsForPersistentVolume(scenario.newVolume, scenario.oldVolume)
   949  		errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume, opts)
   950  		if len(errs) == 0 && scenario.isExpectedFailure {
   951  			t.Errorf("Unexpected success for scenario: %s", name)
   952  		}
   953  		if len(errs) > 0 && !scenario.isExpectedFailure {
   954  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
   955  		}
   956  	}
   957  }
   958  
   959  func TestValidationOptionsForPersistentVolume(t *testing.T) {
   960  	tests := map[string]struct {
   961  		oldPv                       *core.PersistentVolume
   962  		enableVolumeAttributesClass bool
   963  		expectValidationOpts        PersistentVolumeSpecValidationOptions
   964  	}{
   965  		"nil old pv": {
   966  			oldPv:                nil,
   967  			expectValidationOpts: PersistentVolumeSpecValidationOptions{},
   968  		},
   969  		"nil old pv and feature-gate VolumeAttrributesClass is on": {
   970  			oldPv:                       nil,
   971  			enableVolumeAttributesClass: true,
   972  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   973  		},
   974  		"nil old pv and feature-gate VolumeAttrributesClass is off": {
   975  			oldPv:                       nil,
   976  			enableVolumeAttributesClass: false,
   977  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: false},
   978  		},
   979  		"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is on": {
   980  			oldPv: &core.PersistentVolume{
   981  				Spec: core.PersistentVolumeSpec{
   982  					VolumeAttributesClassName: ptr.To("foo"),
   983  				},
   984  			},
   985  			enableVolumeAttributesClass: true,
   986  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   987  		},
   988  		"old pv has volumeAttributesClass and feature-gate VolumeAttrributesClass is off": {
   989  			oldPv: &core.PersistentVolume{
   990  				Spec: core.PersistentVolumeSpec{
   991  					VolumeAttributesClassName: ptr.To("foo"),
   992  				},
   993  			},
   994  			enableVolumeAttributesClass: false,
   995  			expectValidationOpts:        PersistentVolumeSpecValidationOptions{EnableVolumeAttributesClass: true},
   996  		},
   997  	}
   998  
   999  	for name, tc := range tests {
  1000  		t.Run(name, func(t *testing.T) {
  1001  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
  1002  
  1003  			opts := ValidationOptionsForPersistentVolume(nil, tc.oldPv)
  1004  			if opts != tc.expectValidationOpts {
  1005  				t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
  1006  			}
  1007  		})
  1008  	}
  1009  }
  1010  
  1011  func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference, secretfield string) *core.PersistentVolume {
  1012  	pvCopy := pv.DeepCopy()
  1013  	switch secretfield {
  1014  	case "controllerExpand":
  1015  		pvCopy.Spec.CSI.ControllerExpandSecretRef = secret
  1016  	case "controllerPublish":
  1017  		pvCopy.Spec.CSI.ControllerPublishSecretRef = secret
  1018  	case "nodePublish":
  1019  		pvCopy.Spec.CSI.NodePublishSecretRef = secret
  1020  	case "nodeStage":
  1021  		pvCopy.Spec.CSI.NodeStageSecretRef = secret
  1022  	default:
  1023  		panic("unknown string")
  1024  	}
  1025  
  1026  	return pvCopy
  1027  }
  1028  
  1029  func pvcWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaim {
  1030  	return &core.PersistentVolumeClaim{
  1031  		Spec: core.PersistentVolumeClaimSpec{
  1032  			VolumeAttributesClassName: vacName,
  1033  		},
  1034  	}
  1035  }
  1036  
  1037  func pvcWithDataSource(dataSource *core.TypedLocalObjectReference) *core.PersistentVolumeClaim {
  1038  	return &core.PersistentVolumeClaim{
  1039  		Spec: core.PersistentVolumeClaimSpec{
  1040  			DataSource: dataSource,
  1041  		},
  1042  	}
  1043  }
  1044  func pvcWithDataSourceRef(ref *core.TypedObjectReference) *core.PersistentVolumeClaim {
  1045  	return &core.PersistentVolumeClaim{
  1046  		Spec: core.PersistentVolumeClaimSpec{
  1047  			DataSourceRef: ref,
  1048  		},
  1049  	}
  1050  }
  1051  
  1052  func pvcTemplateWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimTemplate {
  1053  	return &core.PersistentVolumeClaimTemplate{
  1054  		Spec: core.PersistentVolumeClaimSpec{
  1055  			VolumeAttributesClassName: vacName,
  1056  		},
  1057  	}
  1058  }
  1059  
  1060  func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
  1061  	return core.PersistentVolumeSpec{
  1062  		Capacity: core.ResourceList{
  1063  			core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1064  		},
  1065  		AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1066  		PersistentVolumeSource: core.PersistentVolumeSource{
  1067  			Local: &core.LocalVolumeSource{
  1068  				Path: path,
  1069  			},
  1070  		},
  1071  		NodeAffinity:     affinity,
  1072  		StorageClassName: "test-storage-class",
  1073  	}
  1074  }
  1075  
  1076  func TestValidateLocalVolumes(t *testing.T) {
  1077  	scenarios := map[string]struct {
  1078  		isExpectedFailure bool
  1079  		volume            *core.PersistentVolume
  1080  	}{
  1081  		"alpha invalid local volume nil annotations": {
  1082  			isExpectedFailure: true,
  1083  			volume: testVolume(
  1084  				"invalid-local-volume-nil-annotations",
  1085  				"",
  1086  				testLocalVolume("/foo", nil)),
  1087  		},
  1088  		"valid local volume": {
  1089  			isExpectedFailure: false,
  1090  			volume: testVolume("valid-local-volume", "",
  1091  				testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
  1092  		},
  1093  		"invalid local volume no node affinity": {
  1094  			isExpectedFailure: true,
  1095  			volume: testVolume("invalid-local-volume-no-node-affinity", "",
  1096  				testLocalVolume("/foo", nil)),
  1097  		},
  1098  		"invalid local volume empty path": {
  1099  			isExpectedFailure: true,
  1100  			volume: testVolume("invalid-local-volume-empty-path", "",
  1101  				testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))),
  1102  		},
  1103  		"invalid-local-volume-backsteps": {
  1104  			isExpectedFailure: true,
  1105  			volume: testVolume("foo", "",
  1106  				testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))),
  1107  		},
  1108  		"valid-local-volume-relative-path": {
  1109  			isExpectedFailure: false,
  1110  			volume: testVolume("foo", "",
  1111  				testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))),
  1112  		},
  1113  	}
  1114  
  1115  	for name, scenario := range scenarios {
  1116  		opts := ValidationOptionsForPersistentVolume(scenario.volume, nil)
  1117  		errs := ValidatePersistentVolume(scenario.volume, opts)
  1118  		if len(errs) == 0 && scenario.isExpectedFailure {
  1119  			t.Errorf("Unexpected success for scenario: %s", name)
  1120  		}
  1121  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1122  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1123  		}
  1124  	}
  1125  }
  1126  
  1127  func testVolumeWithVolumeAttributesClass(vacName *string) *core.PersistentVolume {
  1128  	return testVolume("test-volume-with-volume-attributes-class", "",
  1129  		core.PersistentVolumeSpec{
  1130  			Capacity: core.ResourceList{
  1131  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1132  			},
  1133  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1134  			PersistentVolumeSource: core.PersistentVolumeSource{
  1135  				CSI: &core.CSIPersistentVolumeSource{
  1136  					Driver:       "test-driver",
  1137  					VolumeHandle: "test-123",
  1138  				},
  1139  			},
  1140  			StorageClassName:          "test-storage-class",
  1141  			VolumeAttributesClassName: vacName,
  1142  		})
  1143  }
  1144  
  1145  func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
  1146  	return testVolume("test-affinity-volume", "",
  1147  		core.PersistentVolumeSpec{
  1148  			Capacity: core.ResourceList{
  1149  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1150  			},
  1151  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  1152  			PersistentVolumeSource: core.PersistentVolumeSource{
  1153  				GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
  1154  					PDName: "foo",
  1155  				},
  1156  			},
  1157  			StorageClassName: "test-storage-class",
  1158  			NodeAffinity:     affinity,
  1159  		})
  1160  }
  1161  
  1162  func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity {
  1163  	return &core.VolumeNodeAffinity{
  1164  		Required: &core.NodeSelector{
  1165  			NodeSelectorTerms: []core.NodeSelectorTerm{{
  1166  				MatchExpressions: []core.NodeSelectorRequirement{{
  1167  					Key:      key,
  1168  					Operator: core.NodeSelectorOpIn,
  1169  					Values:   []string{value},
  1170  				}},
  1171  			}},
  1172  		},
  1173  	}
  1174  }
  1175  
  1176  func multipleVolumeNodeAffinity(terms [][]topologyPair) *core.VolumeNodeAffinity {
  1177  	nodeSelectorTerms := []core.NodeSelectorTerm{}
  1178  	for _, term := range terms {
  1179  		matchExpressions := []core.NodeSelectorRequirement{}
  1180  		for _, topology := range term {
  1181  			matchExpressions = append(matchExpressions, core.NodeSelectorRequirement{
  1182  				Key:      topology.key,
  1183  				Operator: core.NodeSelectorOpIn,
  1184  				Values:   []string{topology.value},
  1185  			})
  1186  		}
  1187  		nodeSelectorTerms = append(nodeSelectorTerms, core.NodeSelectorTerm{
  1188  			MatchExpressions: matchExpressions,
  1189  		})
  1190  	}
  1191  
  1192  	return &core.VolumeNodeAffinity{
  1193  		Required: &core.NodeSelector{
  1194  			NodeSelectorTerms: nodeSelectorTerms,
  1195  		},
  1196  	}
  1197  }
  1198  
  1199  func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
  1200  	scenarios := map[string]struct {
  1201  		isExpectedFailure bool
  1202  		oldPV             *core.PersistentVolume
  1203  		newPV             *core.PersistentVolume
  1204  	}{
  1205  		"nil-nothing-changed": {
  1206  			isExpectedFailure: false,
  1207  			oldPV:             testVolumeWithNodeAffinity(nil),
  1208  			newPV:             testVolumeWithNodeAffinity(nil),
  1209  		},
  1210  		"affinity-nothing-changed": {
  1211  			isExpectedFailure: false,
  1212  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1213  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1214  		},
  1215  		"affinity-changed": {
  1216  			isExpectedFailure: true,
  1217  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1218  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")),
  1219  		},
  1220  		"affinity-non-beta-label-changed": {
  1221  			isExpectedFailure: true,
  1222  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1223  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo2", "bar")),
  1224  		},
  1225  		"affinity-zone-beta-unchanged": {
  1226  			isExpectedFailure: false,
  1227  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1228  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1229  		},
  1230  		"affinity-zone-beta-label-to-GA": {
  1231  			isExpectedFailure: false,
  1232  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1233  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
  1234  		},
  1235  		"affinity-zone-beta-label-to-non-GA": {
  1236  			isExpectedFailure: true,
  1237  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1238  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1239  		},
  1240  		"affinity-zone-GA-label-changed": {
  1241  			isExpectedFailure: true,
  1242  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyZone, "bar")),
  1243  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaZone, "bar")),
  1244  		},
  1245  		"affinity-region-beta-unchanged": {
  1246  			isExpectedFailure: false,
  1247  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1248  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1249  		},
  1250  		"affinity-region-beta-label-to-GA": {
  1251  			isExpectedFailure: false,
  1252  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1253  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
  1254  		},
  1255  		"affinity-region-beta-label-to-non-GA": {
  1256  			isExpectedFailure: true,
  1257  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1258  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1259  		},
  1260  		"affinity-region-GA-label-changed": {
  1261  			isExpectedFailure: true,
  1262  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelTopologyRegion, "bar")),
  1263  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelFailureDomainBetaRegion, "bar")),
  1264  		},
  1265  		"affinity-os-beta-label-unchanged": {
  1266  			isExpectedFailure: false,
  1267  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1268  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1269  		},
  1270  		"affinity-os-beta-label-to-GA": {
  1271  			isExpectedFailure: false,
  1272  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1273  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
  1274  		},
  1275  		"affinity-os-beta-label-to-non-GA": {
  1276  			isExpectedFailure: true,
  1277  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1278  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1279  		},
  1280  		"affinity-os-GA-label-changed": {
  1281  			isExpectedFailure: true,
  1282  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelOSStable, "bar")),
  1283  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelOS, "bar")),
  1284  		},
  1285  		"affinity-arch-beta-label-unchanged": {
  1286  			isExpectedFailure: false,
  1287  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1288  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1289  		},
  1290  		"affinity-arch-beta-label-to-GA": {
  1291  			isExpectedFailure: false,
  1292  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1293  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
  1294  		},
  1295  		"affinity-arch-beta-label-to-non-GA": {
  1296  			isExpectedFailure: true,
  1297  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1298  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1299  		},
  1300  		"affinity-arch-GA-label-changed": {
  1301  			isExpectedFailure: true,
  1302  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelArchStable, "bar")),
  1303  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(kubeletapis.LabelArch, "bar")),
  1304  		},
  1305  		"affinity-instanceType-beta-label-unchanged": {
  1306  			isExpectedFailure: false,
  1307  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1308  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1309  		},
  1310  		"affinity-instanceType-beta-label-to-GA": {
  1311  			isExpectedFailure: false,
  1312  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1313  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
  1314  		},
  1315  		"affinity-instanceType-beta-label-to-non-GA": {
  1316  			isExpectedFailure: true,
  1317  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1318  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1319  		},
  1320  		"affinity-instanceType-GA-label-changed": {
  1321  			isExpectedFailure: true,
  1322  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceTypeStable, "bar")),
  1323  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity(v1.LabelInstanceType, "bar")),
  1324  		},
  1325  		"affinity-same-terms-expressions-length-beta-to-GA-partially-changed": {
  1326  			isExpectedFailure: false,
  1327  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1328  				topologyPair{"foo", "bar"},
  1329  			}, {
  1330  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1331  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1332  			}, {
  1333  				topologyPair{kubeletapis.LabelOS, "bar"},
  1334  				topologyPair{kubeletapis.LabelArch, "bar"},
  1335  				topologyPair{v1.LabelInstanceType, "bar"},
  1336  			},
  1337  			})),
  1338  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1339  				topologyPair{"foo", "bar"},
  1340  			}, {
  1341  				topologyPair{v1.LabelTopologyZone, "bar"},
  1342  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1343  			}, {
  1344  				topologyPair{kubeletapis.LabelOS, "bar"},
  1345  				topologyPair{v1.LabelArchStable, "bar"},
  1346  				topologyPair{v1.LabelInstanceTypeStable, "bar"},
  1347  			},
  1348  			})),
  1349  		},
  1350  		"affinity-same-terms-expressions-length-beta-to-non-GA-partially-changed": {
  1351  			isExpectedFailure: true,
  1352  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1353  				topologyPair{"foo", "bar"},
  1354  			}, {
  1355  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1356  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1357  			},
  1358  			})),
  1359  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1360  				topologyPair{"foo", "bar"},
  1361  			}, {
  1362  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1363  				topologyPair{"foo", "bar"},
  1364  			},
  1365  			})),
  1366  		},
  1367  		"affinity-same-terms-expressions-length-GA-partially-changed": {
  1368  			isExpectedFailure: true,
  1369  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1370  				topologyPair{"foo", "bar"},
  1371  			}, {
  1372  				topologyPair{v1.LabelTopologyZone, "bar"},
  1373  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1374  				topologyPair{v1.LabelOSStable, "bar"},
  1375  			},
  1376  			})),
  1377  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1378  				topologyPair{"foo", "bar"},
  1379  			}, {
  1380  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1381  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1382  				topologyPair{v1.LabelOSStable, "bar"},
  1383  			},
  1384  			})),
  1385  		},
  1386  		"affinity-same-terms-expressions-length-beta-fully-changed": {
  1387  			isExpectedFailure: false,
  1388  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1389  				topologyPair{"foo", "bar"},
  1390  			}, {
  1391  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1392  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1393  			}, {
  1394  				topologyPair{kubeletapis.LabelOS, "bar"},
  1395  				topologyPair{kubeletapis.LabelArch, "bar"},
  1396  				topologyPair{v1.LabelInstanceType, "bar"},
  1397  			},
  1398  			})),
  1399  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1400  				topologyPair{"foo", "bar"},
  1401  			}, {
  1402  				topologyPair{v1.LabelTopologyZone, "bar"},
  1403  				topologyPair{v1.LabelTopologyRegion, "bar"},
  1404  			}, {
  1405  				topologyPair{v1.LabelOSStable, "bar"},
  1406  				topologyPair{v1.LabelArchStable, "bar"},
  1407  				topologyPair{v1.LabelInstanceTypeStable, "bar"},
  1408  			},
  1409  			})),
  1410  		},
  1411  		"affinity-same-terms-expressions-length-beta-GA-mixed-fully-changed": {
  1412  			isExpectedFailure: true,
  1413  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1414  				topologyPair{"foo", "bar"},
  1415  			}, {
  1416  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1417  				topologyPair{v1.LabelTopologyZone, "bar"},
  1418  			},
  1419  			})),
  1420  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1421  				topologyPair{"foo", "bar"},
  1422  			}, {
  1423  				topologyPair{v1.LabelTopologyZone, "bar"},
  1424  				topologyPair{v1.LabelFailureDomainBetaZone, "bar2"},
  1425  			},
  1426  			})),
  1427  		},
  1428  		"affinity-same-terms-length-different-expressions-length-beta-changed": {
  1429  			isExpectedFailure: true,
  1430  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1431  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1432  			},
  1433  			})),
  1434  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1435  				topologyPair{v1.LabelTopologyZone, "bar"},
  1436  				topologyPair{v1.LabelFailureDomainBetaRegion, "bar"},
  1437  			},
  1438  			})),
  1439  		},
  1440  		"affinity-different-terms-expressions-length-beta-changed": {
  1441  			isExpectedFailure: true,
  1442  			oldPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1443  				topologyPair{v1.LabelFailureDomainBetaZone, "bar"},
  1444  			},
  1445  			})),
  1446  			newPV: testVolumeWithNodeAffinity(multipleVolumeNodeAffinity([][]topologyPair{{
  1447  				topologyPair{v1.LabelTopologyZone, "bar"},
  1448  			}, {
  1449  				topologyPair{v1.LabelArchStable, "bar"},
  1450  			},
  1451  			})),
  1452  		},
  1453  		"nil-to-obj": {
  1454  			isExpectedFailure: false,
  1455  			oldPV:             testVolumeWithNodeAffinity(nil),
  1456  			newPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1457  		},
  1458  		"obj-to-nil": {
  1459  			isExpectedFailure: true,
  1460  			oldPV:             testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
  1461  			newPV:             testVolumeWithNodeAffinity(nil),
  1462  		},
  1463  	}
  1464  
  1465  	for name, scenario := range scenarios {
  1466  		originalNewPV := scenario.newPV.DeepCopy()
  1467  		originalOldPV := scenario.oldPV.DeepCopy()
  1468  		opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  1469  		errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  1470  		if len(errs) == 0 && scenario.isExpectedFailure {
  1471  			t.Errorf("Unexpected success for scenario: %s", name)
  1472  		}
  1473  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1474  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1475  		}
  1476  		if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
  1477  			t.Errorf("newPV was modified: %s", diff)
  1478  		}
  1479  		if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
  1480  			t.Errorf("oldPV was modified: %s", diff)
  1481  		}
  1482  	}
  1483  }
  1484  
  1485  func TestValidatePeristentVolumeAttributesClassUpdate(t *testing.T) {
  1486  	scenarios := map[string]struct {
  1487  		isExpectedFailure           bool
  1488  		enableVolumeAttributesClass bool
  1489  		oldPV                       *core.PersistentVolume
  1490  		newPV                       *core.PersistentVolume
  1491  	}{
  1492  		"nil-nothing-changed": {
  1493  			isExpectedFailure:           false,
  1494  			enableVolumeAttributesClass: true,
  1495  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1496  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1497  		},
  1498  		"vac-nothing-changed": {
  1499  			isExpectedFailure:           false,
  1500  			enableVolumeAttributesClass: true,
  1501  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1502  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1503  		},
  1504  		"vac-changed": {
  1505  			isExpectedFailure:           false,
  1506  			enableVolumeAttributesClass: true,
  1507  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1508  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("bar")),
  1509  		},
  1510  		"nil-to-string": {
  1511  			isExpectedFailure:           false,
  1512  			enableVolumeAttributesClass: true,
  1513  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1514  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1515  		},
  1516  		"nil-to-empty-string": {
  1517  			isExpectedFailure:           true,
  1518  			enableVolumeAttributesClass: true,
  1519  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1520  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1521  		},
  1522  		"string-to-nil": {
  1523  			isExpectedFailure:           true,
  1524  			enableVolumeAttributesClass: true,
  1525  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1526  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1527  		},
  1528  		"string-to-empty-string": {
  1529  			isExpectedFailure:           true,
  1530  			enableVolumeAttributesClass: true,
  1531  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1532  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1533  		},
  1534  		"vac-nothing-changed-when-feature-gate-is-off": {
  1535  			isExpectedFailure:           false,
  1536  			enableVolumeAttributesClass: false,
  1537  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1538  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1539  		},
  1540  		"vac-changed-when-feature-gate-is-off": {
  1541  			isExpectedFailure:           true,
  1542  			enableVolumeAttributesClass: false,
  1543  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1544  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("bar")),
  1545  		},
  1546  		"nil-to-string-when-feature-gate-is-off": {
  1547  			isExpectedFailure:           true,
  1548  			enableVolumeAttributesClass: false,
  1549  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1550  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1551  		},
  1552  		"nil-to-empty-string-when-feature-gate-is-off": {
  1553  			isExpectedFailure:           true,
  1554  			enableVolumeAttributesClass: false,
  1555  			oldPV:                       testVolumeWithVolumeAttributesClass(nil),
  1556  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1557  		},
  1558  		"string-to-nil-when-feature-gate-is-off": {
  1559  			isExpectedFailure:           true,
  1560  			enableVolumeAttributesClass: false,
  1561  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1562  			newPV:                       testVolumeWithVolumeAttributesClass(nil),
  1563  		},
  1564  		"string-to-empty-string-when-feature-gate-is-off": {
  1565  			isExpectedFailure:           true,
  1566  			enableVolumeAttributesClass: false,
  1567  			oldPV:                       testVolumeWithVolumeAttributesClass(ptr.To("foo")),
  1568  			newPV:                       testVolumeWithVolumeAttributesClass(ptr.To("")),
  1569  		},
  1570  	}
  1571  
  1572  	for name, scenario := range scenarios {
  1573  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
  1574  
  1575  		originalNewPV := scenario.newPV.DeepCopy()
  1576  		originalOldPV := scenario.oldPV.DeepCopy()
  1577  		opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  1578  		errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  1579  		if len(errs) == 0 && scenario.isExpectedFailure {
  1580  			t.Errorf("Unexpected success for scenario: %s", name)
  1581  		}
  1582  		if len(errs) > 0 && !scenario.isExpectedFailure {
  1583  			t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  1584  		}
  1585  		if diff := cmp.Diff(originalNewPV, scenario.newPV); len(diff) > 0 {
  1586  			t.Errorf("newPV was modified: %s", diff)
  1587  		}
  1588  		if diff := cmp.Diff(originalOldPV, scenario.oldPV); len(diff) > 0 {
  1589  			t.Errorf("oldPV was modified: %s", diff)
  1590  		}
  1591  	}
  1592  }
  1593  
  1594  func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1595  	return &core.PersistentVolumeClaim{
  1596  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
  1597  		Spec:       spec,
  1598  	}
  1599  }
  1600  
  1601  func testVolumeClaimWithStatus(
  1602  	name, namespace string,
  1603  	spec core.PersistentVolumeClaimSpec,
  1604  	status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim {
  1605  	return &core.PersistentVolumeClaim{
  1606  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
  1607  		Spec:       spec,
  1608  		Status:     status,
  1609  	}
  1610  }
  1611  
  1612  func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1613  	annotations := map[string]string{
  1614  		v1.BetaStorageClassAnnotation: annval,
  1615  	}
  1616  
  1617  	return &core.PersistentVolumeClaim{
  1618  		ObjectMeta: metav1.ObjectMeta{
  1619  			Name:        name,
  1620  			Namespace:   namespace,
  1621  			Annotations: annotations,
  1622  		},
  1623  		Spec: spec,
  1624  	}
  1625  }
  1626  
  1627  func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1628  	annotations := map[string]string{
  1629  		ann: annval,
  1630  	}
  1631  
  1632  	return &core.PersistentVolumeClaim{
  1633  		ObjectMeta: metav1.ObjectMeta{
  1634  			Name:        name,
  1635  			Namespace:   namespace,
  1636  			Annotations: annotations,
  1637  		},
  1638  		Spec: spec,
  1639  	}
  1640  }
  1641  
  1642  func testVolumeClaimStorageClassInSpec(name, namespace, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1643  	spec.StorageClassName = &scName
  1644  	return &core.PersistentVolumeClaim{
  1645  		ObjectMeta: metav1.ObjectMeta{
  1646  			Name:      name,
  1647  			Namespace: namespace,
  1648  		},
  1649  		Spec: spec,
  1650  	}
  1651  }
  1652  
  1653  func testVolumeClaimStorageClassNilInSpec(name, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1654  	spec.StorageClassName = nil
  1655  	return &core.PersistentVolumeClaim{
  1656  		ObjectMeta: metav1.ObjectMeta{
  1657  			Name:      name,
  1658  			Namespace: namespace,
  1659  		},
  1660  		Spec: spec,
  1661  	}
  1662  }
  1663  
  1664  func testVolumeSnapshotDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
  1665  	scName := "csi-plugin"
  1666  	dataSourceInSpec := core.PersistentVolumeClaimSpec{
  1667  		AccessModes: []core.PersistentVolumeAccessMode{
  1668  			core.ReadOnlyMany,
  1669  		},
  1670  		Resources: core.VolumeResourceRequirements{
  1671  			Requests: core.ResourceList{
  1672  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1673  			},
  1674  		},
  1675  		StorageClassName: &scName,
  1676  		DataSource: &core.TypedLocalObjectReference{
  1677  			APIGroup: &apiGroup,
  1678  			Kind:     kind,
  1679  			Name:     name,
  1680  		},
  1681  	}
  1682  
  1683  	return &dataSourceInSpec
  1684  }
  1685  
  1686  func TestAlphaVolumeSnapshotDataSource(t *testing.T) {
  1687  	successTestCases := []core.PersistentVolumeClaimSpec{
  1688  		*testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
  1689  	}
  1690  	failedTestCases := []core.PersistentVolumeClaimSpec{
  1691  		*testVolumeSnapshotDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
  1692  		*testVolumeSnapshotDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
  1693  	}
  1694  
  1695  	for _, tc := range successTestCases {
  1696  		opts := PersistentVolumeClaimSpecValidationOptions{}
  1697  		if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) != 0 {
  1698  			t.Errorf("expected success: %v", errs)
  1699  		}
  1700  	}
  1701  	for _, tc := range failedTestCases {
  1702  		opts := PersistentVolumeClaimSpecValidationOptions{}
  1703  		if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec"), opts); len(errs) == 0 {
  1704  			t.Errorf("expected failure: %v", errs)
  1705  		}
  1706  	}
  1707  }
  1708  
  1709  func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1710  	spec.StorageClassName = &scName
  1711  	return &core.PersistentVolumeClaim{
  1712  		ObjectMeta: metav1.ObjectMeta{
  1713  			Name:        name,
  1714  			Namespace:   namespace,
  1715  			Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
  1716  		},
  1717  		Spec: spec,
  1718  	}
  1719  }
  1720  
  1721  func testVolumeClaimStorageClassInAnnotationAndNilInSpec(name, namespace, scNameInAnn string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
  1722  	spec.StorageClassName = nil
  1723  	return &core.PersistentVolumeClaim{
  1724  		ObjectMeta: metav1.ObjectMeta{
  1725  			Name:        name,
  1726  			Namespace:   namespace,
  1727  			Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
  1728  		},
  1729  		Spec: spec,
  1730  	}
  1731  }
  1732  
  1733  func testValidatePVC(t *testing.T, ephemeral bool) {
  1734  	invalidClassName := "-invalid-"
  1735  	validClassName := "valid"
  1736  	invalidAPIGroup := "^invalid"
  1737  	invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
  1738  	validMode := core.PersistentVolumeFilesystem
  1739  	goodName := "foo"
  1740  	goodNS := "ns"
  1741  	if ephemeral {
  1742  		// Must be empty for ephemeral inline volumes.
  1743  		goodName = ""
  1744  		goodNS = ""
  1745  	}
  1746  	goodClaimSpec := core.PersistentVolumeClaimSpec{
  1747  		Selector: &metav1.LabelSelector{
  1748  			MatchExpressions: []metav1.LabelSelectorRequirement{{
  1749  				Key:      "key2",
  1750  				Operator: "Exists",
  1751  			}},
  1752  		},
  1753  		AccessModes: []core.PersistentVolumeAccessMode{
  1754  			core.ReadWriteOnce,
  1755  			core.ReadOnlyMany,
  1756  		},
  1757  		Resources: core.VolumeResourceRequirements{
  1758  			Requests: core.ResourceList{
  1759  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1760  			},
  1761  		},
  1762  		StorageClassName: &validClassName,
  1763  		VolumeMode:       &validMode,
  1764  	}
  1765  	now := metav1.Now()
  1766  	ten := int64(10)
  1767  
  1768  	scenarios := map[string]struct {
  1769  		isExpectedFailure           bool
  1770  		enableVolumeAttributesClass bool
  1771  		claim                       *core.PersistentVolumeClaim
  1772  	}{
  1773  		"good-claim": {
  1774  			isExpectedFailure: false,
  1775  			claim:             testVolumeClaim(goodName, goodNS, goodClaimSpec),
  1776  		},
  1777  		"missing-name": {
  1778  			isExpectedFailure: !ephemeral,
  1779  			claim:             testVolumeClaim("", goodNS, goodClaimSpec),
  1780  		},
  1781  		"missing-namespace": {
  1782  			isExpectedFailure: !ephemeral,
  1783  			claim:             testVolumeClaim(goodName, "", goodClaimSpec),
  1784  		},
  1785  		"with-generate-name": {
  1786  			isExpectedFailure: ephemeral,
  1787  			claim: func() *core.PersistentVolumeClaim {
  1788  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1789  				claim.GenerateName = "pvc-"
  1790  				return claim
  1791  			}(),
  1792  		},
  1793  		"with-uid": {
  1794  			isExpectedFailure: ephemeral,
  1795  			claim: func() *core.PersistentVolumeClaim {
  1796  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1797  				claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
  1798  				return claim
  1799  			}(),
  1800  		},
  1801  		"with-resource-version": {
  1802  			isExpectedFailure: ephemeral,
  1803  			claim: func() *core.PersistentVolumeClaim {
  1804  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1805  				claim.ResourceVersion = "1"
  1806  				return claim
  1807  			}(),
  1808  		},
  1809  		"with-generation": {
  1810  			isExpectedFailure: ephemeral,
  1811  			claim: func() *core.PersistentVolumeClaim {
  1812  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1813  				claim.Generation = 100
  1814  				return claim
  1815  			}(),
  1816  		},
  1817  		"with-creation-timestamp": {
  1818  			isExpectedFailure: ephemeral,
  1819  			claim: func() *core.PersistentVolumeClaim {
  1820  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1821  				claim.CreationTimestamp = now
  1822  				return claim
  1823  			}(),
  1824  		},
  1825  		"with-deletion-grace-period-seconds": {
  1826  			isExpectedFailure: ephemeral,
  1827  			claim: func() *core.PersistentVolumeClaim {
  1828  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1829  				claim.DeletionGracePeriodSeconds = &ten
  1830  				return claim
  1831  			}(),
  1832  		},
  1833  		"with-owner-references": {
  1834  			isExpectedFailure: ephemeral,
  1835  			claim: func() *core.PersistentVolumeClaim {
  1836  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1837  				claim.OwnerReferences = []metav1.OwnerReference{{
  1838  					APIVersion: "v1",
  1839  					Kind:       "pod",
  1840  					Name:       "foo",
  1841  					UID:        "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
  1842  				},
  1843  				}
  1844  				return claim
  1845  			}(),
  1846  		},
  1847  		"with-finalizers": {
  1848  			isExpectedFailure: ephemeral,
  1849  			claim: func() *core.PersistentVolumeClaim {
  1850  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1851  				claim.Finalizers = []string{
  1852  					"example.com/foo",
  1853  				}
  1854  				return claim
  1855  			}(),
  1856  		},
  1857  		"with-managed-fields": {
  1858  			isExpectedFailure: ephemeral,
  1859  			claim: func() *core.PersistentVolumeClaim {
  1860  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1861  				claim.ManagedFields = []metav1.ManagedFieldsEntry{{
  1862  					FieldsType: "FieldsV1",
  1863  					Operation:  "Apply",
  1864  					APIVersion: "apps/v1",
  1865  					Manager:    "foo",
  1866  				},
  1867  				}
  1868  				return claim
  1869  			}(),
  1870  		},
  1871  		"with-good-labels": {
  1872  			claim: func() *core.PersistentVolumeClaim {
  1873  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1874  				claim.Labels = map[string]string{
  1875  					"apps.kubernetes.io/name": "test",
  1876  				}
  1877  				return claim
  1878  			}(),
  1879  		},
  1880  		"with-bad-labels": {
  1881  			isExpectedFailure: true,
  1882  			claim: func() *core.PersistentVolumeClaim {
  1883  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1884  				claim.Labels = map[string]string{
  1885  					"hello-world": "hyphen not allowed",
  1886  				}
  1887  				return claim
  1888  			}(),
  1889  		},
  1890  		"with-good-annotations": {
  1891  			claim: func() *core.PersistentVolumeClaim {
  1892  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1893  				claim.Labels = map[string]string{
  1894  					"foo": "bar",
  1895  				}
  1896  				return claim
  1897  			}(),
  1898  		},
  1899  		"with-bad-annotations": {
  1900  			isExpectedFailure: true,
  1901  			claim: func() *core.PersistentVolumeClaim {
  1902  				claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
  1903  				claim.Labels = map[string]string{
  1904  					"hello-world": "hyphen not allowed",
  1905  				}
  1906  				return claim
  1907  			}(),
  1908  		},
  1909  		"with-read-write-once-pod": {
  1910  			isExpectedFailure: false,
  1911  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1912  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod"},
  1913  				Resources: core.VolumeResourceRequirements{
  1914  					Requests: core.ResourceList{
  1915  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1916  					},
  1917  				},
  1918  			}),
  1919  		},
  1920  		"with-read-write-once-pod-and-others": {
  1921  			isExpectedFailure: true,
  1922  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1923  				AccessModes: []core.PersistentVolumeAccessMode{"ReadWriteOncePod", "ReadWriteMany"},
  1924  				Resources: core.VolumeResourceRequirements{
  1925  					Requests: core.ResourceList{
  1926  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1927  					},
  1928  				},
  1929  			}),
  1930  		},
  1931  		"invalid-claim-zero-capacity": {
  1932  			isExpectedFailure: true,
  1933  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1934  				Selector: &metav1.LabelSelector{
  1935  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  1936  						Key:      "key2",
  1937  						Operator: "Exists",
  1938  					}},
  1939  				},
  1940  				AccessModes: []core.PersistentVolumeAccessMode{
  1941  					core.ReadWriteOnce,
  1942  					core.ReadOnlyMany,
  1943  				},
  1944  				Resources: core.VolumeResourceRequirements{
  1945  					Requests: core.ResourceList{
  1946  						core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
  1947  					},
  1948  				},
  1949  				StorageClassName: &validClassName,
  1950  			}),
  1951  		},
  1952  		"invalid-label-selector": {
  1953  			isExpectedFailure: true,
  1954  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1955  				Selector: &metav1.LabelSelector{
  1956  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  1957  						Key:      "key2",
  1958  						Operator: "InvalidOp",
  1959  						Values:   []string{"value1", "value2"},
  1960  					}},
  1961  				},
  1962  				AccessModes: []core.PersistentVolumeAccessMode{
  1963  					core.ReadWriteOnce,
  1964  					core.ReadOnlyMany,
  1965  				},
  1966  				Resources: core.VolumeResourceRequirements{
  1967  					Requests: core.ResourceList{
  1968  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1969  					},
  1970  				},
  1971  			}),
  1972  		},
  1973  		"invalid-accessmode": {
  1974  			isExpectedFailure: true,
  1975  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1976  				AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
  1977  				Resources: core.VolumeResourceRequirements{
  1978  					Requests: core.ResourceList{
  1979  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1980  					},
  1981  				},
  1982  			}),
  1983  		},
  1984  		"no-access-modes": {
  1985  			isExpectedFailure: true,
  1986  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1987  				Resources: core.VolumeResourceRequirements{
  1988  					Requests: core.ResourceList{
  1989  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  1990  					},
  1991  				},
  1992  			}),
  1993  		},
  1994  		"no-resource-requests": {
  1995  			isExpectedFailure: true,
  1996  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  1997  				AccessModes: []core.PersistentVolumeAccessMode{
  1998  					core.ReadWriteOnce,
  1999  				},
  2000  			}),
  2001  		},
  2002  		"invalid-resource-requests": {
  2003  			isExpectedFailure: true,
  2004  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2005  				AccessModes: []core.PersistentVolumeAccessMode{
  2006  					core.ReadWriteOnce,
  2007  				},
  2008  				Resources: core.VolumeResourceRequirements{
  2009  					Requests: core.ResourceList{
  2010  						core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  2011  					},
  2012  				},
  2013  			}),
  2014  		},
  2015  		"negative-storage-request": {
  2016  			isExpectedFailure: true,
  2017  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2018  				Selector: &metav1.LabelSelector{
  2019  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2020  						Key:      "key2",
  2021  						Operator: "Exists",
  2022  					}},
  2023  				},
  2024  				AccessModes: []core.PersistentVolumeAccessMode{
  2025  					core.ReadWriteOnce,
  2026  					core.ReadOnlyMany,
  2027  				},
  2028  				Resources: core.VolumeResourceRequirements{
  2029  					Requests: core.ResourceList{
  2030  						core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
  2031  					},
  2032  				},
  2033  			}),
  2034  		},
  2035  		"zero-storage-request": {
  2036  			isExpectedFailure: true,
  2037  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2038  				Selector: &metav1.LabelSelector{
  2039  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2040  						Key:      "key2",
  2041  						Operator: "Exists",
  2042  					}},
  2043  				},
  2044  				AccessModes: []core.PersistentVolumeAccessMode{
  2045  					core.ReadWriteOnce,
  2046  					core.ReadOnlyMany,
  2047  				},
  2048  				Resources: core.VolumeResourceRequirements{
  2049  					Requests: core.ResourceList{
  2050  						core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
  2051  					},
  2052  				},
  2053  			}),
  2054  		},
  2055  		"invalid-storage-class-name": {
  2056  			isExpectedFailure: true,
  2057  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2058  				Selector: &metav1.LabelSelector{
  2059  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2060  						Key:      "key2",
  2061  						Operator: "Exists",
  2062  					}},
  2063  				},
  2064  				AccessModes: []core.PersistentVolumeAccessMode{
  2065  					core.ReadWriteOnce,
  2066  					core.ReadOnlyMany,
  2067  				},
  2068  				Resources: core.VolumeResourceRequirements{
  2069  					Requests: core.ResourceList{
  2070  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2071  					},
  2072  				},
  2073  				StorageClassName: &invalidClassName,
  2074  			}),
  2075  		},
  2076  		"invalid-volume-mode": {
  2077  			isExpectedFailure: true,
  2078  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2079  				AccessModes: []core.PersistentVolumeAccessMode{
  2080  					core.ReadWriteOnce,
  2081  					core.ReadOnlyMany,
  2082  				},
  2083  				Resources: core.VolumeResourceRequirements{
  2084  					Requests: core.ResourceList{
  2085  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2086  					},
  2087  				},
  2088  				VolumeMode: &invalidMode,
  2089  			}),
  2090  		},
  2091  		"mismatch-data-source-and-ref": {
  2092  			isExpectedFailure: true,
  2093  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2094  				AccessModes: []core.PersistentVolumeAccessMode{
  2095  					core.ReadWriteOnce,
  2096  				},
  2097  				Resources: core.VolumeResourceRequirements{
  2098  					Requests: core.ResourceList{
  2099  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2100  					},
  2101  				},
  2102  				DataSource: &core.TypedLocalObjectReference{
  2103  					Kind: "PersistentVolumeClaim",
  2104  					Name: "pvc1",
  2105  				},
  2106  				DataSourceRef: &core.TypedObjectReference{
  2107  					Kind: "PersistentVolumeClaim",
  2108  					Name: "pvc2",
  2109  				},
  2110  			}),
  2111  		},
  2112  		"invaild-apigroup-in-data-source": {
  2113  			isExpectedFailure: true,
  2114  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2115  				AccessModes: []core.PersistentVolumeAccessMode{
  2116  					core.ReadWriteOnce,
  2117  				},
  2118  				Resources: core.VolumeResourceRequirements{
  2119  					Requests: core.ResourceList{
  2120  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2121  					},
  2122  				},
  2123  				DataSource: &core.TypedLocalObjectReference{
  2124  					APIGroup: &invalidAPIGroup,
  2125  					Kind:     "Foo",
  2126  					Name:     "foo1",
  2127  				},
  2128  			}),
  2129  		},
  2130  		"invaild-apigroup-in-data-source-ref": {
  2131  			isExpectedFailure: true,
  2132  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2133  				AccessModes: []core.PersistentVolumeAccessMode{
  2134  					core.ReadWriteOnce,
  2135  				},
  2136  				Resources: core.VolumeResourceRequirements{
  2137  					Requests: core.ResourceList{
  2138  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2139  					},
  2140  				},
  2141  				DataSourceRef: &core.TypedObjectReference{
  2142  					APIGroup: &invalidAPIGroup,
  2143  					Kind:     "Foo",
  2144  					Name:     "foo1",
  2145  				},
  2146  			}),
  2147  		},
  2148  		"invalid-volume-attributes-class-name": {
  2149  			isExpectedFailure:           true,
  2150  			enableVolumeAttributesClass: true,
  2151  			claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
  2152  				Selector: &metav1.LabelSelector{
  2153  					MatchExpressions: []metav1.LabelSelectorRequirement{{
  2154  						Key:      "key2",
  2155  						Operator: "Exists",
  2156  					}},
  2157  				},
  2158  				AccessModes: []core.PersistentVolumeAccessMode{
  2159  					core.ReadWriteOnce,
  2160  					core.ReadOnlyMany,
  2161  				},
  2162  				Resources: core.VolumeResourceRequirements{
  2163  					Requests: core.ResourceList{
  2164  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2165  					},
  2166  				},
  2167  				VolumeAttributesClassName: &invalidClassName,
  2168  			}),
  2169  		},
  2170  	}
  2171  
  2172  	for name, scenario := range scenarios {
  2173  		t.Run(name, func(t *testing.T) {
  2174  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
  2175  
  2176  			var errs field.ErrorList
  2177  			if ephemeral {
  2178  				volumes := []core.Volume{{
  2179  					Name: "foo",
  2180  					VolumeSource: core.VolumeSource{
  2181  						Ephemeral: &core.EphemeralVolumeSource{
  2182  							VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  2183  								ObjectMeta: scenario.claim.ObjectMeta,
  2184  								Spec:       scenario.claim.Spec,
  2185  							},
  2186  						},
  2187  					},
  2188  				},
  2189  				}
  2190  				opts := PodValidationOptions{}
  2191  				_, errs = ValidateVolumes(volumes, nil, field.NewPath(""), opts)
  2192  			} else {
  2193  				opts := ValidationOptionsForPersistentVolumeClaim(scenario.claim, nil)
  2194  				errs = ValidatePersistentVolumeClaim(scenario.claim, opts)
  2195  			}
  2196  			if len(errs) == 0 && scenario.isExpectedFailure {
  2197  				t.Error("Unexpected success for scenario")
  2198  			}
  2199  			if len(errs) > 0 && !scenario.isExpectedFailure {
  2200  				t.Errorf("Unexpected failure: %+v", errs)
  2201  			}
  2202  		})
  2203  	}
  2204  }
  2205  
  2206  func TestValidatePersistentVolumeClaim(t *testing.T) {
  2207  	testValidatePVC(t, false)
  2208  }
  2209  
  2210  func TestValidateEphemeralVolume(t *testing.T) {
  2211  	testValidatePVC(t, true)
  2212  }
  2213  
  2214  func TestAlphaPVVolumeModeUpdate(t *testing.T) {
  2215  	block := core.PersistentVolumeBlock
  2216  	file := core.PersistentVolumeFilesystem
  2217  
  2218  	scenarios := map[string]struct {
  2219  		isExpectedFailure bool
  2220  		oldPV             *core.PersistentVolume
  2221  		newPV             *core.PersistentVolume
  2222  	}{
  2223  		"valid-update-volume-mode-block-to-block": {
  2224  			isExpectedFailure: false,
  2225  			oldPV:             createTestVolModePV(&block),
  2226  			newPV:             createTestVolModePV(&block),
  2227  		},
  2228  		"valid-update-volume-mode-file-to-file": {
  2229  			isExpectedFailure: false,
  2230  			oldPV:             createTestVolModePV(&file),
  2231  			newPV:             createTestVolModePV(&file),
  2232  		},
  2233  		"invalid-update-volume-mode-to-block": {
  2234  			isExpectedFailure: true,
  2235  			oldPV:             createTestVolModePV(&file),
  2236  			newPV:             createTestVolModePV(&block),
  2237  		},
  2238  		"invalid-update-volume-mode-to-file": {
  2239  			isExpectedFailure: true,
  2240  			oldPV:             createTestVolModePV(&block),
  2241  			newPV:             createTestVolModePV(&file),
  2242  		},
  2243  		"invalid-update-volume-mode-nil-to-file": {
  2244  			isExpectedFailure: true,
  2245  			oldPV:             createTestVolModePV(nil),
  2246  			newPV:             createTestVolModePV(&file),
  2247  		},
  2248  		"invalid-update-volume-mode-nil-to-block": {
  2249  			isExpectedFailure: true,
  2250  			oldPV:             createTestVolModePV(nil),
  2251  			newPV:             createTestVolModePV(&block),
  2252  		},
  2253  		"invalid-update-volume-mode-file-to-nil": {
  2254  			isExpectedFailure: true,
  2255  			oldPV:             createTestVolModePV(&file),
  2256  			newPV:             createTestVolModePV(nil),
  2257  		},
  2258  		"invalid-update-volume-mode-block-to-nil": {
  2259  			isExpectedFailure: true,
  2260  			oldPV:             createTestVolModePV(&block),
  2261  			newPV:             createTestVolModePV(nil),
  2262  		},
  2263  		"invalid-update-volume-mode-nil-to-nil": {
  2264  			isExpectedFailure: false,
  2265  			oldPV:             createTestVolModePV(nil),
  2266  			newPV:             createTestVolModePV(nil),
  2267  		},
  2268  		"invalid-update-volume-mode-empty-to-mode": {
  2269  			isExpectedFailure: true,
  2270  			oldPV:             createTestPV(),
  2271  			newPV:             createTestVolModePV(&block),
  2272  		},
  2273  	}
  2274  
  2275  	for name, scenario := range scenarios {
  2276  		t.Run(name, func(t *testing.T) {
  2277  			opts := ValidationOptionsForPersistentVolume(scenario.newPV, scenario.oldPV)
  2278  			// ensure we have a resource version specified for updates
  2279  			errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV, opts)
  2280  			if len(errs) == 0 && scenario.isExpectedFailure {
  2281  				t.Errorf("Unexpected success for scenario: %s", name)
  2282  			}
  2283  			if len(errs) > 0 && !scenario.isExpectedFailure {
  2284  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  2285  			}
  2286  		})
  2287  	}
  2288  }
  2289  
  2290  func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
  2291  	block := core.PersistentVolumeBlock
  2292  	file := core.PersistentVolumeFilesystem
  2293  	invaildAPIGroup := "^invalid"
  2294  
  2295  	validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2296  		AccessModes: []core.PersistentVolumeAccessMode{
  2297  			core.ReadWriteOnce,
  2298  			core.ReadOnlyMany,
  2299  		},
  2300  		Resources: core.VolumeResourceRequirements{
  2301  			Requests: core.ResourceList{
  2302  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2303  			},
  2304  		},
  2305  	}, core.PersistentVolumeClaimStatus{
  2306  		Phase: core.ClaimBound,
  2307  	})
  2308  
  2309  	validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2310  		AccessModes: []core.PersistentVolumeAccessMode{
  2311  			core.ReadOnlyMany,
  2312  		},
  2313  		Resources: core.VolumeResourceRequirements{
  2314  			Requests: core.ResourceList{
  2315  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2316  			},
  2317  		},
  2318  	})
  2319  	validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{
  2320  		AccessModes: []core.PersistentVolumeAccessMode{
  2321  			core.ReadOnlyMany,
  2322  		},
  2323  		Resources: core.VolumeResourceRequirements{
  2324  			Requests: core.ResourceList{
  2325  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2326  			},
  2327  		},
  2328  	})
  2329  	validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2330  		AccessModes: []core.PersistentVolumeAccessMode{
  2331  			core.ReadWriteOnce,
  2332  			core.ReadOnlyMany,
  2333  		},
  2334  		Resources: core.VolumeResourceRequirements{
  2335  			Requests: core.ResourceList{
  2336  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2337  			},
  2338  		},
  2339  		VolumeName: "volume",
  2340  	})
  2341  	invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2342  		AccessModes: []core.PersistentVolumeAccessMode{
  2343  			core.ReadWriteOnce,
  2344  			core.ReadOnlyMany,
  2345  		},
  2346  		Resources: core.VolumeResourceRequirements{
  2347  			Requests: core.ResourceList{
  2348  				core.ResourceName(core.ResourceStorage): resource.MustParse("20G"),
  2349  			},
  2350  		},
  2351  		VolumeName: "volume",
  2352  	})
  2353  	invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2354  		AccessModes: []core.PersistentVolumeAccessMode{
  2355  			core.ReadWriteOnce,
  2356  		},
  2357  		Resources: core.VolumeResourceRequirements{
  2358  			Requests: core.ResourceList{
  2359  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2360  			},
  2361  		},
  2362  		VolumeName: "volume",
  2363  	})
  2364  	validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2365  		AccessModes: []core.PersistentVolumeAccessMode{
  2366  			core.ReadWriteOnce,
  2367  		},
  2368  		VolumeMode: &file,
  2369  		Resources: core.VolumeResourceRequirements{
  2370  			Requests: core.ResourceList{
  2371  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2372  			},
  2373  		},
  2374  		VolumeName: "volume",
  2375  	})
  2376  	validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2377  		AccessModes: []core.PersistentVolumeAccessMode{
  2378  			core.ReadWriteOnce,
  2379  		},
  2380  		VolumeMode: &block,
  2381  		Resources: core.VolumeResourceRequirements{
  2382  			Requests: core.ResourceList{
  2383  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2384  			},
  2385  		},
  2386  		VolumeName: "volume",
  2387  	})
  2388  	invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2389  		AccessModes: []core.PersistentVolumeAccessMode{
  2390  			core.ReadWriteOnce,
  2391  		},
  2392  		VolumeMode: nil,
  2393  		Resources: core.VolumeResourceRequirements{
  2394  			Requests: core.ResourceList{
  2395  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2396  			},
  2397  		},
  2398  		VolumeName: "volume",
  2399  	})
  2400  	invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2401  		AccessModes: []core.PersistentVolumeAccessMode{
  2402  			core.ReadOnlyMany,
  2403  		},
  2404  		Resources: core.VolumeResourceRequirements{
  2405  			Requests: core.ResourceList{
  2406  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2407  			},
  2408  		},
  2409  		VolumeName: "volume",
  2410  	})
  2411  	validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2412  		AccessModes: []core.PersistentVolumeAccessMode{
  2413  			core.ReadOnlyMany,
  2414  		},
  2415  		Resources: core.VolumeResourceRequirements{
  2416  			Requests: core.ResourceList{
  2417  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2418  			},
  2419  		},
  2420  		VolumeName: "volume",
  2421  	})
  2422  	validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2423  		AccessModes: []core.PersistentVolumeAccessMode{
  2424  			core.ReadWriteOnce,
  2425  			core.ReadOnlyMany,
  2426  		},
  2427  		Resources: core.VolumeResourceRequirements{
  2428  			Requests: core.ResourceList{
  2429  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2430  			},
  2431  		},
  2432  		VolumeName: "volume",
  2433  	})
  2434  	validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2435  		AccessModes: []core.PersistentVolumeAccessMode{
  2436  			core.ReadWriteOnce,
  2437  			core.ReadOnlyMany,
  2438  		},
  2439  		Resources: core.VolumeResourceRequirements{
  2440  			Requests: core.ResourceList{
  2441  				core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
  2442  			},
  2443  		},
  2444  	}, core.PersistentVolumeClaimStatus{
  2445  		Phase: core.ClaimBound,
  2446  	})
  2447  
  2448  	invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2449  		AccessModes: []core.PersistentVolumeAccessMode{
  2450  			core.ReadWriteOnce,
  2451  			core.ReadOnlyMany,
  2452  		},
  2453  		Resources: core.VolumeResourceRequirements{
  2454  			Requests: core.ResourceList{
  2455  				core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
  2456  			},
  2457  		},
  2458  	}, core.PersistentVolumeClaimStatus{
  2459  		Phase: core.ClaimBound,
  2460  	})
  2461  
  2462  	unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2463  		AccessModes: []core.PersistentVolumeAccessMode{
  2464  			core.ReadWriteOnce,
  2465  			core.ReadOnlyMany,
  2466  		},
  2467  		Resources: core.VolumeResourceRequirements{
  2468  			Requests: core.ResourceList{
  2469  				core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
  2470  			},
  2471  		},
  2472  	}, core.PersistentVolumeClaimStatus{
  2473  		Phase: core.ClaimPending,
  2474  	})
  2475  
  2476  	validClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2477  		AccessModes: []core.PersistentVolumeAccessMode{
  2478  			core.ReadOnlyMany,
  2479  		},
  2480  		Resources: core.VolumeResourceRequirements{
  2481  			Requests: core.ResourceList{
  2482  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2483  			},
  2484  		},
  2485  	})
  2486  
  2487  	validClaimStorageClassInSpecChanged := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2488  		AccessModes: []core.PersistentVolumeAccessMode{
  2489  			core.ReadOnlyMany,
  2490  		},
  2491  		Resources: core.VolumeResourceRequirements{
  2492  			Requests: core.ResourceList{
  2493  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2494  			},
  2495  		},
  2496  	})
  2497  
  2498  	validClaimStorageClassNil := testVolumeClaimStorageClassNilInSpec("foo", "ns", core.PersistentVolumeClaimSpec{
  2499  		AccessModes: []core.PersistentVolumeAccessMode{
  2500  			core.ReadOnlyMany,
  2501  		},
  2502  		Resources: core.VolumeResourceRequirements{
  2503  			Requests: core.ResourceList{
  2504  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2505  			},
  2506  		},
  2507  	})
  2508  
  2509  	invalidClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
  2510  		AccessModes: []core.PersistentVolumeAccessMode{
  2511  			core.ReadOnlyMany,
  2512  		},
  2513  		Resources: core.VolumeResourceRequirements{
  2514  			Requests: core.ResourceList{
  2515  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2516  			},
  2517  		},
  2518  	})
  2519  
  2520  	validClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
  2521  		"foo", "ns", "fast", "fast", core.PersistentVolumeClaimSpec{
  2522  			AccessModes: []core.PersistentVolumeAccessMode{
  2523  				core.ReadOnlyMany,
  2524  			},
  2525  			Resources: core.VolumeResourceRequirements{
  2526  				Requests: core.ResourceList{
  2527  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2528  				},
  2529  			},
  2530  		})
  2531  
  2532  	validClaimStorageClassInAnnotationAndNilInSpec := testVolumeClaimStorageClassInAnnotationAndNilInSpec(
  2533  		"foo", "ns", "fast", core.PersistentVolumeClaimSpec{
  2534  			AccessModes: []core.PersistentVolumeAccessMode{
  2535  				core.ReadOnlyMany,
  2536  			},
  2537  			Resources: core.VolumeResourceRequirements{
  2538  				Requests: core.ResourceList{
  2539  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2540  				},
  2541  			},
  2542  		})
  2543  
  2544  	invalidClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
  2545  		"foo", "ns", "fast2", "fast", core.PersistentVolumeClaimSpec{
  2546  			AccessModes: []core.PersistentVolumeAccessMode{
  2547  				core.ReadOnlyMany,
  2548  			},
  2549  			Resources: core.VolumeResourceRequirements{
  2550  				Requests: core.ResourceList{
  2551  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2552  				},
  2553  			},
  2554  		})
  2555  
  2556  	validClaimRWOPAccessMode := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2557  		AccessModes: []core.PersistentVolumeAccessMode{
  2558  			core.ReadWriteOncePod,
  2559  		},
  2560  		Resources: core.VolumeResourceRequirements{
  2561  			Requests: core.ResourceList{
  2562  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2563  			},
  2564  		},
  2565  		VolumeName: "volume",
  2566  	})
  2567  
  2568  	validClaimRWOPAccessModeAddAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
  2569  		AccessModes: []core.PersistentVolumeAccessMode{
  2570  			core.ReadWriteOncePod,
  2571  		},
  2572  		Resources: core.VolumeResourceRequirements{
  2573  			Requests: core.ResourceList{
  2574  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2575  			},
  2576  		},
  2577  		VolumeName: "volume",
  2578  	})
  2579  	validClaimShrinkInitial := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2580  		AccessModes: []core.PersistentVolumeAccessMode{
  2581  			core.ReadWriteOnce,
  2582  			core.ReadOnlyMany,
  2583  		},
  2584  		Resources: core.VolumeResourceRequirements{
  2585  			Requests: core.ResourceList{
  2586  				core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
  2587  			},
  2588  		},
  2589  	}, core.PersistentVolumeClaimStatus{
  2590  		Phase: core.ClaimBound,
  2591  		Capacity: core.ResourceList{
  2592  			core.ResourceStorage: resource.MustParse("10G"),
  2593  		},
  2594  	})
  2595  
  2596  	unboundShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2597  		AccessModes: []core.PersistentVolumeAccessMode{
  2598  			core.ReadWriteOnce,
  2599  			core.ReadOnlyMany,
  2600  		},
  2601  		Resources: core.VolumeResourceRequirements{
  2602  			Requests: core.ResourceList{
  2603  				core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
  2604  			},
  2605  		},
  2606  	}, core.PersistentVolumeClaimStatus{
  2607  		Phase: core.ClaimPending,
  2608  		Capacity: core.ResourceList{
  2609  			core.ResourceStorage: resource.MustParse("10G"),
  2610  		},
  2611  	})
  2612  
  2613  	validClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2614  		AccessModes: []core.PersistentVolumeAccessMode{
  2615  			core.ReadWriteOnce,
  2616  			core.ReadOnlyMany,
  2617  		},
  2618  		Resources: core.VolumeResourceRequirements{
  2619  			Requests: core.ResourceList{
  2620  				core.ResourceStorage: resource.MustParse("13G"),
  2621  			},
  2622  		},
  2623  	}, core.PersistentVolumeClaimStatus{
  2624  		Phase: core.ClaimBound,
  2625  		Capacity: core.ResourceList{
  2626  			core.ResourceStorage: resource.MustParse("10G"),
  2627  		},
  2628  	})
  2629  
  2630  	invalidShrinkToStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2631  		AccessModes: []core.PersistentVolumeAccessMode{
  2632  			core.ReadWriteOnce,
  2633  			core.ReadOnlyMany,
  2634  		},
  2635  		Resources: core.VolumeResourceRequirements{
  2636  			Requests: core.ResourceList{
  2637  				core.ResourceStorage: resource.MustParse("10G"),
  2638  			},
  2639  		},
  2640  	}, core.PersistentVolumeClaimStatus{
  2641  		Phase: core.ClaimBound,
  2642  		Capacity: core.ResourceList{
  2643  			core.ResourceStorage: resource.MustParse("10G"),
  2644  		},
  2645  	})
  2646  
  2647  	invalidClaimShrink := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2648  		AccessModes: []core.PersistentVolumeAccessMode{
  2649  			core.ReadWriteOnce,
  2650  			core.ReadOnlyMany,
  2651  		},
  2652  		Resources: core.VolumeResourceRequirements{
  2653  			Requests: core.ResourceList{
  2654  				core.ResourceStorage: resource.MustParse("3G"),
  2655  			},
  2656  		},
  2657  	}, core.PersistentVolumeClaimStatus{
  2658  		Phase: core.ClaimBound,
  2659  		Capacity: core.ResourceList{
  2660  			core.ResourceStorage: resource.MustParse("10G"),
  2661  		},
  2662  	})
  2663  
  2664  	invalidClaimDataSourceAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2665  		AccessModes: []core.PersistentVolumeAccessMode{
  2666  			core.ReadWriteOnce,
  2667  		},
  2668  		VolumeMode: &file,
  2669  		Resources: core.VolumeResourceRequirements{
  2670  			Requests: core.ResourceList{
  2671  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2672  			},
  2673  		},
  2674  		VolumeName: "volume",
  2675  		DataSource: &core.TypedLocalObjectReference{
  2676  			APIGroup: &invaildAPIGroup,
  2677  			Kind:     "Foo",
  2678  			Name:     "foo",
  2679  		},
  2680  	})
  2681  
  2682  	invalidClaimDataSourceRefAPIGroup := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
  2683  		AccessModes: []core.PersistentVolumeAccessMode{
  2684  			core.ReadWriteOnce,
  2685  		},
  2686  		VolumeMode: &file,
  2687  		Resources: core.VolumeResourceRequirements{
  2688  			Requests: core.ResourceList{
  2689  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2690  			},
  2691  		},
  2692  		VolumeName: "volume",
  2693  		DataSourceRef: &core.TypedObjectReference{
  2694  			APIGroup: &invaildAPIGroup,
  2695  			Kind:     "Foo",
  2696  			Name:     "foo",
  2697  		},
  2698  	})
  2699  
  2700  	validClaimNilVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2701  		AccessModes: []core.PersistentVolumeAccessMode{
  2702  			core.ReadWriteOnce,
  2703  			core.ReadOnlyMany,
  2704  		},
  2705  		Resources: core.VolumeResourceRequirements{
  2706  			Requests: core.ResourceList{
  2707  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2708  			},
  2709  		},
  2710  	}, core.PersistentVolumeClaimStatus{
  2711  		Phase: core.ClaimBound,
  2712  	})
  2713  	validClaimEmptyVolumeAttributesClass := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2714  		VolumeAttributesClassName: utilpointer.String(""),
  2715  		AccessModes: []core.PersistentVolumeAccessMode{
  2716  			core.ReadWriteOnce,
  2717  			core.ReadOnlyMany,
  2718  		},
  2719  		Resources: core.VolumeResourceRequirements{
  2720  			Requests: core.ResourceList{
  2721  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2722  			},
  2723  		},
  2724  	}, core.PersistentVolumeClaimStatus{
  2725  		Phase: core.ClaimBound,
  2726  	})
  2727  	validClaimVolumeAttributesClass1 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2728  		VolumeAttributesClassName: utilpointer.String("vac1"),
  2729  		AccessModes: []core.PersistentVolumeAccessMode{
  2730  			core.ReadWriteOnce,
  2731  			core.ReadOnlyMany,
  2732  		},
  2733  		Resources: core.VolumeResourceRequirements{
  2734  			Requests: core.ResourceList{
  2735  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2736  			},
  2737  		},
  2738  	}, core.PersistentVolumeClaimStatus{
  2739  		Phase: core.ClaimBound,
  2740  	})
  2741  	validClaimVolumeAttributesClass2 := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
  2742  		VolumeAttributesClassName: utilpointer.String("vac2"),
  2743  		AccessModes: []core.PersistentVolumeAccessMode{
  2744  			core.ReadWriteOnce,
  2745  			core.ReadOnlyMany,
  2746  		},
  2747  		Resources: core.VolumeResourceRequirements{
  2748  			Requests: core.ResourceList{
  2749  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  2750  			},
  2751  		},
  2752  	}, core.PersistentVolumeClaimStatus{
  2753  		Phase: core.ClaimBound,
  2754  	})
  2755  
  2756  	scenarios := map[string]struct {
  2757  		isExpectedFailure           bool
  2758  		oldClaim                    *core.PersistentVolumeClaim
  2759  		newClaim                    *core.PersistentVolumeClaim
  2760  		enableRecoverFromExpansion  bool
  2761  		enableVolumeAttributesClass bool
  2762  	}{
  2763  		"valid-update-volumeName-only": {
  2764  			isExpectedFailure: false,
  2765  			oldClaim:          validClaim,
  2766  			newClaim:          validUpdateClaim,
  2767  		},
  2768  		"valid-no-op-update": {
  2769  			isExpectedFailure: false,
  2770  			oldClaim:          validUpdateClaim,
  2771  			newClaim:          validUpdateClaim,
  2772  		},
  2773  		"invalid-update-change-resources-on-bound-claim": {
  2774  			isExpectedFailure: true,
  2775  			oldClaim:          validUpdateClaim,
  2776  			newClaim:          invalidUpdateClaimResources,
  2777  		},
  2778  		"invalid-update-change-access-modes-on-bound-claim": {
  2779  			isExpectedFailure: true,
  2780  			oldClaim:          validUpdateClaim,
  2781  			newClaim:          invalidUpdateClaimAccessModes,
  2782  		},
  2783  		"valid-update-volume-mode-block-to-block": {
  2784  			isExpectedFailure: false,
  2785  			oldClaim:          validClaimVolumeModeBlock,
  2786  			newClaim:          validClaimVolumeModeBlock,
  2787  		},
  2788  		"valid-update-volume-mode-file-to-file": {
  2789  			isExpectedFailure: false,
  2790  			oldClaim:          validClaimVolumeModeFile,
  2791  			newClaim:          validClaimVolumeModeFile,
  2792  		},
  2793  		"invalid-update-volume-mode-to-block": {
  2794  			isExpectedFailure: true,
  2795  			oldClaim:          validClaimVolumeModeFile,
  2796  			newClaim:          validClaimVolumeModeBlock,
  2797  		},
  2798  		"invalid-update-volume-mode-to-file": {
  2799  			isExpectedFailure: true,
  2800  			oldClaim:          validClaimVolumeModeBlock,
  2801  			newClaim:          validClaimVolumeModeFile,
  2802  		},
  2803  		"invalid-update-volume-mode-nil-to-file": {
  2804  			isExpectedFailure: true,
  2805  			oldClaim:          invalidClaimVolumeModeNil,
  2806  			newClaim:          validClaimVolumeModeFile,
  2807  		},
  2808  		"invalid-update-volume-mode-nil-to-block": {
  2809  			isExpectedFailure: true,
  2810  			oldClaim:          invalidClaimVolumeModeNil,
  2811  			newClaim:          validClaimVolumeModeBlock,
  2812  		},
  2813  		"invalid-update-volume-mode-block-to-nil": {
  2814  			isExpectedFailure: true,
  2815  			oldClaim:          validClaimVolumeModeBlock,
  2816  			newClaim:          invalidClaimVolumeModeNil,
  2817  		},
  2818  		"invalid-update-volume-mode-file-to-nil": {
  2819  			isExpectedFailure: true,
  2820  			oldClaim:          validClaimVolumeModeFile,
  2821  			newClaim:          invalidClaimVolumeModeNil,
  2822  		},
  2823  		"invalid-update-volume-mode-empty-to-mode": {
  2824  			isExpectedFailure: true,
  2825  			oldClaim:          validClaim,
  2826  			newClaim:          validClaimVolumeModeBlock,
  2827  		},
  2828  		"invalid-update-volume-mode-mode-to-empty": {
  2829  			isExpectedFailure: true,
  2830  			oldClaim:          validClaimVolumeModeBlock,
  2831  			newClaim:          validClaim,
  2832  		},
  2833  		"invalid-update-change-storage-class-annotation-after-creation": {
  2834  			isExpectedFailure: true,
  2835  			oldClaim:          validClaimStorageClass,
  2836  			newClaim:          invalidUpdateClaimStorageClass,
  2837  		},
  2838  		"valid-update-mutable-annotation": {
  2839  			isExpectedFailure: false,
  2840  			oldClaim:          validClaimAnnotation,
  2841  			newClaim:          validUpdateClaimMutableAnnotation,
  2842  		},
  2843  		"valid-update-add-annotation": {
  2844  			isExpectedFailure: false,
  2845  			oldClaim:          validClaim,
  2846  			newClaim:          validAddClaimAnnotation,
  2847  		},
  2848  		"valid-size-update-resize-disabled": {
  2849  			oldClaim: validClaim,
  2850  			newClaim: validSizeUpdate,
  2851  		},
  2852  		"valid-size-update-resize-enabled": {
  2853  			isExpectedFailure: false,
  2854  			oldClaim:          validClaim,
  2855  			newClaim:          validSizeUpdate,
  2856  		},
  2857  		"invalid-size-update-resize-enabled": {
  2858  			isExpectedFailure: true,
  2859  			oldClaim:          validClaim,
  2860  			newClaim:          invalidSizeUpdate,
  2861  		},
  2862  		"unbound-size-update-resize-enabled": {
  2863  			isExpectedFailure: true,
  2864  			oldClaim:          validClaim,
  2865  			newClaim:          unboundSizeUpdate,
  2866  		},
  2867  		"valid-upgrade-storage-class-annotation-to-spec": {
  2868  			isExpectedFailure: false,
  2869  			oldClaim:          validClaimStorageClass,
  2870  			newClaim:          validClaimStorageClassInSpec,
  2871  		},
  2872  		"valid-upgrade-nil-storage-class-spec-to-spec": {
  2873  			isExpectedFailure: false,
  2874  			oldClaim:          validClaimStorageClassNil,
  2875  			newClaim:          validClaimStorageClassInSpec,
  2876  		},
  2877  		"invalid-upgrade-not-nil-storage-class-spec-to-spec": {
  2878  			isExpectedFailure: true,
  2879  			oldClaim:          validClaimStorageClassInSpec,
  2880  			newClaim:          validClaimStorageClassInSpecChanged,
  2881  		},
  2882  		"invalid-upgrade-to-nil-storage-class-spec-to-spec": {
  2883  			isExpectedFailure: true,
  2884  			oldClaim:          validClaimStorageClassInSpec,
  2885  			newClaim:          validClaimStorageClassNil,
  2886  		},
  2887  		"valid-upgrade-storage-class-annotation-and-nil-spec-to-spec-retro": {
  2888  			isExpectedFailure: false,
  2889  			oldClaim:          validClaimStorageClassInAnnotationAndNilInSpec,
  2890  			newClaim:          validClaimStorageClassInAnnotationAndSpec,
  2891  		},
  2892  		"invalid-upgrade-storage-class-annotation-and-spec-to-spec-retro": {
  2893  			isExpectedFailure: true,
  2894  			oldClaim:          validClaimStorageClassInAnnotationAndSpec,
  2895  			newClaim:          validClaimStorageClassInSpecChanged,
  2896  		},
  2897  		"invalid-upgrade-storage-class-annotation-and-no-spec": {
  2898  			isExpectedFailure: true,
  2899  			oldClaim:          validClaimStorageClassInAnnotationAndNilInSpec,
  2900  			newClaim:          validClaimStorageClassInSpecChanged,
  2901  		},
  2902  		"invalid-upgrade-storage-class-annotation-to-spec": {
  2903  			isExpectedFailure: true,
  2904  			oldClaim:          validClaimStorageClass,
  2905  			newClaim:          invalidClaimStorageClassInSpec,
  2906  		},
  2907  		"valid-upgrade-storage-class-annotation-to-annotation-and-spec": {
  2908  			isExpectedFailure: false,
  2909  			oldClaim:          validClaimStorageClass,
  2910  			newClaim:          validClaimStorageClassInAnnotationAndSpec,
  2911  		},
  2912  		"invalid-upgrade-storage-class-annotation-to-annotation-and-spec": {
  2913  			isExpectedFailure: true,
  2914  			oldClaim:          validClaimStorageClass,
  2915  			newClaim:          invalidClaimStorageClassInAnnotationAndSpec,
  2916  		},
  2917  		"invalid-upgrade-storage-class-in-spec": {
  2918  			isExpectedFailure: true,
  2919  			oldClaim:          validClaimStorageClassInSpec,
  2920  			newClaim:          invalidClaimStorageClassInSpec,
  2921  		},
  2922  		"invalid-downgrade-storage-class-spec-to-annotation": {
  2923  			isExpectedFailure: true,
  2924  			oldClaim:          validClaimStorageClassInSpec,
  2925  			newClaim:          validClaimStorageClass,
  2926  		},
  2927  		"valid-update-rwop-used-and-rwop-feature-disabled": {
  2928  			isExpectedFailure: false,
  2929  			oldClaim:          validClaimRWOPAccessMode,
  2930  			newClaim:          validClaimRWOPAccessModeAddAnnotation,
  2931  		},
  2932  		"valid-expand-shrink-resize-enabled": {
  2933  			oldClaim:                   validClaimShrinkInitial,
  2934  			newClaim:                   validClaimShrink,
  2935  			enableRecoverFromExpansion: true,
  2936  		},
  2937  		"invalid-expand-shrink-resize-enabled": {
  2938  			oldClaim:                   validClaimShrinkInitial,
  2939  			newClaim:                   invalidClaimShrink,
  2940  			enableRecoverFromExpansion: true,
  2941  			isExpectedFailure:          true,
  2942  		},
  2943  		"invalid-expand-shrink-to-status-resize-enabled": {
  2944  			oldClaim:                   validClaimShrinkInitial,
  2945  			newClaim:                   invalidShrinkToStatus,
  2946  			enableRecoverFromExpansion: true,
  2947  			isExpectedFailure:          true,
  2948  		},
  2949  		"invalid-expand-shrink-recover-disabled": {
  2950  			oldClaim:                   validClaimShrinkInitial,
  2951  			newClaim:                   validClaimShrink,
  2952  			enableRecoverFromExpansion: false,
  2953  			isExpectedFailure:          true,
  2954  		},
  2955  		"unbound-size-shrink-resize-enabled": {
  2956  			oldClaim:                   validClaimShrinkInitial,
  2957  			newClaim:                   unboundShrink,
  2958  			enableRecoverFromExpansion: true,
  2959  			isExpectedFailure:          true,
  2960  		},
  2961  		"allow-update-pvc-when-data-source-used": {
  2962  			oldClaim:          invalidClaimDataSourceAPIGroup,
  2963  			newClaim:          invalidClaimDataSourceAPIGroup,
  2964  			isExpectedFailure: false,
  2965  		},
  2966  		"allow-update-pvc-when-data-source-ref-used": {
  2967  			oldClaim:          invalidClaimDataSourceRefAPIGroup,
  2968  			newClaim:          invalidClaimDataSourceRefAPIGroup,
  2969  			isExpectedFailure: false,
  2970  		},
  2971  		"valid-update-volume-attributes-class-from-nil": {
  2972  			oldClaim:                    validClaimNilVolumeAttributesClass,
  2973  			newClaim:                    validClaimVolumeAttributesClass1,
  2974  			enableVolumeAttributesClass: true,
  2975  			isExpectedFailure:           false,
  2976  		},
  2977  		"valid-update-volume-attributes-class-from-empty": {
  2978  			oldClaim:                    validClaimEmptyVolumeAttributesClass,
  2979  			newClaim:                    validClaimVolumeAttributesClass1,
  2980  			enableVolumeAttributesClass: true,
  2981  			isExpectedFailure:           false,
  2982  		},
  2983  		"valid-update-volume-attributes-class": {
  2984  			oldClaim:                    validClaimVolumeAttributesClass1,
  2985  			newClaim:                    validClaimVolumeAttributesClass2,
  2986  			enableVolumeAttributesClass: true,
  2987  			isExpectedFailure:           false,
  2988  		},
  2989  		"invalid-update-volume-attributes-class": {
  2990  			oldClaim:                    validClaimVolumeAttributesClass1,
  2991  			newClaim:                    validClaimNilVolumeAttributesClass,
  2992  			enableVolumeAttributesClass: true,
  2993  			isExpectedFailure:           true,
  2994  		},
  2995  		"invalid-update-volume-attributes-class-to-nil": {
  2996  			oldClaim:                    validClaimVolumeAttributesClass1,
  2997  			newClaim:                    validClaimNilVolumeAttributesClass,
  2998  			enableVolumeAttributesClass: true,
  2999  			isExpectedFailure:           true,
  3000  		},
  3001  		"invalid-update-volume-attributes-class-to-empty": {
  3002  			oldClaim:                    validClaimVolumeAttributesClass1,
  3003  			newClaim:                    validClaimEmptyVolumeAttributesClass,
  3004  			enableVolumeAttributesClass: true,
  3005  			isExpectedFailure:           true,
  3006  		},
  3007  		"invalid-update-volume-attributes-class-to-nil-without-featuregate-enabled": {
  3008  			oldClaim:                    validClaimVolumeAttributesClass1,
  3009  			newClaim:                    validClaimNilVolumeAttributesClass,
  3010  			enableVolumeAttributesClass: false,
  3011  			isExpectedFailure:           true,
  3012  		},
  3013  		"invalid-update-volume-attributes-class-without-featuregate-enabled": {
  3014  			oldClaim:                    validClaimVolumeAttributesClass1,
  3015  			newClaim:                    validClaimVolumeAttributesClass2,
  3016  			enableVolumeAttributesClass: false,
  3017  			isExpectedFailure:           true,
  3018  		},
  3019  	}
  3020  
  3021  	for name, scenario := range scenarios {
  3022  		t.Run(name, func(t *testing.T) {
  3023  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
  3024  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, scenario.enableVolumeAttributesClass)()
  3025  
  3026  			scenario.oldClaim.ResourceVersion = "1"
  3027  			scenario.newClaim.ResourceVersion = "1"
  3028  			opts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
  3029  			errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim, opts)
  3030  			if len(errs) == 0 && scenario.isExpectedFailure {
  3031  				t.Errorf("Unexpected success for scenario: %s", name)
  3032  			}
  3033  			if len(errs) > 0 && !scenario.isExpectedFailure {
  3034  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
  3035  			}
  3036  		})
  3037  	}
  3038  }
  3039  
  3040  func TestValidationOptionsForPersistentVolumeClaim(t *testing.T) {
  3041  	invaildAPIGroup := "^invalid"
  3042  
  3043  	tests := map[string]struct {
  3044  		oldPvc                      *core.PersistentVolumeClaim
  3045  		enableVolumeAttributesClass bool
  3046  		expectValidationOpts        PersistentVolumeClaimSpecValidationOptions
  3047  	}{
  3048  		"nil pv": {
  3049  			oldPvc: nil,
  3050  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3051  				EnableRecoverFromExpansionFailure: false,
  3052  				EnableVolumeAttributesClass:       false,
  3053  			},
  3054  		},
  3055  		"invaild apiGroup in dataSource allowed because the old pvc is used": {
  3056  			oldPvc: pvcWithDataSource(&core.TypedLocalObjectReference{APIGroup: &invaildAPIGroup}),
  3057  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3058  				AllowInvalidAPIGroupInDataSourceOrRef: true,
  3059  			},
  3060  		},
  3061  		"invaild apiGroup in dataSourceRef allowed because the old pvc is used": {
  3062  			oldPvc: pvcWithDataSourceRef(&core.TypedObjectReference{APIGroup: &invaildAPIGroup}),
  3063  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3064  				AllowInvalidAPIGroupInDataSourceOrRef: true,
  3065  			},
  3066  		},
  3067  		"volume attributes class allowed because feature enable": {
  3068  			oldPvc:                      pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
  3069  			enableVolumeAttributesClass: true,
  3070  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3071  				EnableRecoverFromExpansionFailure: false,
  3072  				EnableVolumeAttributesClass:       true,
  3073  			},
  3074  		},
  3075  		"volume attributes class validated because used and feature disabled": {
  3076  			oldPvc:                      pvcWithVolumeAttributesClassName(utilpointer.String("foo")),
  3077  			enableVolumeAttributesClass: false,
  3078  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3079  				EnableRecoverFromExpansionFailure: false,
  3080  				EnableVolumeAttributesClass:       true,
  3081  			},
  3082  		},
  3083  	}
  3084  
  3085  	for name, tc := range tests {
  3086  		t.Run(name, func(t *testing.T) {
  3087  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
  3088  
  3089  			opts := ValidationOptionsForPersistentVolumeClaim(nil, tc.oldPvc)
  3090  			if opts != tc.expectValidationOpts {
  3091  				t.Errorf("Expected opts: %+v, received: %+v", tc.expectValidationOpts, opts)
  3092  			}
  3093  		})
  3094  	}
  3095  }
  3096  
  3097  func TestValidationOptionsForPersistentVolumeClaimTemplate(t *testing.T) {
  3098  	tests := map[string]struct {
  3099  		oldPvcTemplate              *core.PersistentVolumeClaimTemplate
  3100  		enableVolumeAttributesClass bool
  3101  		expectValidationOpts        PersistentVolumeClaimSpecValidationOptions
  3102  	}{
  3103  		"nil pv": {
  3104  			oldPvcTemplate:       nil,
  3105  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{},
  3106  		},
  3107  		"volume attributes class allowed because feature enable": {
  3108  			oldPvcTemplate:              pvcTemplateWithVolumeAttributesClassName(utilpointer.String("foo")),
  3109  			enableVolumeAttributesClass: true,
  3110  			expectValidationOpts: PersistentVolumeClaimSpecValidationOptions{
  3111  				EnableVolumeAttributesClass: true,
  3112  			},
  3113  		},
  3114  	}
  3115  
  3116  	for name, tc := range tests {
  3117  		t.Run(name, func(t *testing.T) {
  3118  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.enableVolumeAttributesClass)()
  3119  
  3120  			opts := ValidationOptionsForPersistentVolumeClaimTemplate(nil, tc.oldPvcTemplate)
  3121  			if opts != tc.expectValidationOpts {
  3122  				t.Errorf("Expected opts: %+v, received: %+v", opts, tc.expectValidationOpts)
  3123  			}
  3124  		})
  3125  	}
  3126  }
  3127  
  3128  func TestValidateKeyToPath(t *testing.T) {
  3129  	testCases := []struct {
  3130  		kp      core.KeyToPath
  3131  		ok      bool
  3132  		errtype field.ErrorType
  3133  	}{{
  3134  		kp: core.KeyToPath{Key: "k", Path: "p"},
  3135  		ok: true,
  3136  	}, {
  3137  		kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"},
  3138  		ok: true,
  3139  	}, {
  3140  		kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"},
  3141  		ok: true,
  3142  	}, {
  3143  		kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(0644)},
  3144  		ok: true,
  3145  	}, {
  3146  		kp:      core.KeyToPath{Key: "", Path: "p"},
  3147  		ok:      false,
  3148  		errtype: field.ErrorTypeRequired,
  3149  	}, {
  3150  		kp:      core.KeyToPath{Key: "k", Path: ""},
  3151  		ok:      false,
  3152  		errtype: field.ErrorTypeRequired,
  3153  	}, {
  3154  		kp:      core.KeyToPath{Key: "k", Path: "..p"},
  3155  		ok:      false,
  3156  		errtype: field.ErrorTypeInvalid,
  3157  	}, {
  3158  		kp:      core.KeyToPath{Key: "k", Path: "../p"},
  3159  		ok:      false,
  3160  		errtype: field.ErrorTypeInvalid,
  3161  	}, {
  3162  		kp:      core.KeyToPath{Key: "k", Path: "p/../p"},
  3163  		ok:      false,
  3164  		errtype: field.ErrorTypeInvalid,
  3165  	}, {
  3166  		kp:      core.KeyToPath{Key: "k", Path: "p/.."},
  3167  		ok:      false,
  3168  		errtype: field.ErrorTypeInvalid,
  3169  	}, {
  3170  		kp:      core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(01000)},
  3171  		ok:      false,
  3172  		errtype: field.ErrorTypeInvalid,
  3173  	}, {
  3174  		kp:      core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32(-1)},
  3175  		ok:      false,
  3176  		errtype: field.ErrorTypeInvalid,
  3177  	},
  3178  	}
  3179  
  3180  	for i, tc := range testCases {
  3181  		errs := validateKeyToPath(&tc.kp, field.NewPath("field"))
  3182  		if tc.ok && len(errs) > 0 {
  3183  			t.Errorf("[%d] unexpected errors: %v", i, errs)
  3184  		} else if !tc.ok && len(errs) == 0 {
  3185  			t.Errorf("[%d] expected error type %v", i, tc.errtype)
  3186  		} else if len(errs) > 1 {
  3187  			t.Errorf("[%d] expected only one error, got %d", i, len(errs))
  3188  		} else if !tc.ok {
  3189  			if errs[0].Type != tc.errtype {
  3190  				t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type)
  3191  			}
  3192  		}
  3193  	}
  3194  }
  3195  
  3196  func TestValidateNFSVolumeSource(t *testing.T) {
  3197  	testCases := []struct {
  3198  		name      string
  3199  		nfs       *core.NFSVolumeSource
  3200  		errtype   field.ErrorType
  3201  		errfield  string
  3202  		errdetail string
  3203  	}{{
  3204  		name:     "missing server",
  3205  		nfs:      &core.NFSVolumeSource{Server: "", Path: "/tmp"},
  3206  		errtype:  field.ErrorTypeRequired,
  3207  		errfield: "server",
  3208  	}, {
  3209  		name:     "missing path",
  3210  		nfs:      &core.NFSVolumeSource{Server: "my-server", Path: ""},
  3211  		errtype:  field.ErrorTypeRequired,
  3212  		errfield: "path",
  3213  	}, {
  3214  		name:      "abs path",
  3215  		nfs:       &core.NFSVolumeSource{Server: "my-server", Path: "tmp"},
  3216  		errtype:   field.ErrorTypeInvalid,
  3217  		errfield:  "path",
  3218  		errdetail: "must be an absolute path",
  3219  	},
  3220  	}
  3221  
  3222  	for i, tc := range testCases {
  3223  		errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field"))
  3224  
  3225  		if len(errs) > 0 && tc.errtype == "" {
  3226  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3227  		} else if len(errs) == 0 && tc.errtype != "" {
  3228  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3229  		} else if len(errs) >= 1 {
  3230  			if errs[0].Type != tc.errtype {
  3231  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3232  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3233  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3234  			} else if !strings.Contains(errs[0].Detail, tc.errdetail) {
  3235  				t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
  3236  			}
  3237  		}
  3238  	}
  3239  }
  3240  
  3241  func TestValidateGlusterfs(t *testing.T) {
  3242  	testCases := []struct {
  3243  		name     string
  3244  		gfs      *core.GlusterfsVolumeSource
  3245  		errtype  field.ErrorType
  3246  		errfield string
  3247  	}{{
  3248  		name:     "missing endpointname",
  3249  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"},
  3250  		errtype:  field.ErrorTypeRequired,
  3251  		errfield: "endpoints",
  3252  	}, {
  3253  		name:     "missing path",
  3254  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""},
  3255  		errtype:  field.ErrorTypeRequired,
  3256  		errfield: "path",
  3257  	}, {
  3258  		name:     "missing endpointname and path",
  3259  		gfs:      &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""},
  3260  		errtype:  field.ErrorTypeRequired,
  3261  		errfield: "endpoints",
  3262  	},
  3263  	}
  3264  
  3265  	for i, tc := range testCases {
  3266  		errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field"))
  3267  
  3268  		if len(errs) > 0 && tc.errtype == "" {
  3269  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3270  		} else if len(errs) == 0 && tc.errtype != "" {
  3271  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3272  		} else if len(errs) >= 1 {
  3273  			if errs[0].Type != tc.errtype {
  3274  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3275  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3276  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3277  			}
  3278  		}
  3279  	}
  3280  }
  3281  
  3282  func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) {
  3283  	var epNs *string
  3284  	namespace := ""
  3285  	epNs = &namespace
  3286  
  3287  	testCases := []struct {
  3288  		name     string
  3289  		gfs      *core.GlusterfsPersistentVolumeSource
  3290  		errtype  field.ErrorType
  3291  		errfield string
  3292  	}{{
  3293  		name:     "missing endpointname",
  3294  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"},
  3295  		errtype:  field.ErrorTypeRequired,
  3296  		errfield: "endpoints",
  3297  	}, {
  3298  		name:     "missing path",
  3299  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""},
  3300  		errtype:  field.ErrorTypeRequired,
  3301  		errfield: "path",
  3302  	}, {
  3303  		name:     "non null endpointnamespace with empty string",
  3304  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs},
  3305  		errtype:  field.ErrorTypeInvalid,
  3306  		errfield: "endpointsNamespace",
  3307  	}, {
  3308  		name:     "missing endpointname and path",
  3309  		gfs:      &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""},
  3310  		errtype:  field.ErrorTypeRequired,
  3311  		errfield: "endpoints",
  3312  	},
  3313  	}
  3314  
  3315  	for i, tc := range testCases {
  3316  		errs := validateGlusterfsPersistentVolumeSource(tc.gfs, field.NewPath("field"))
  3317  
  3318  		if len(errs) > 0 && tc.errtype == "" {
  3319  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3320  		} else if len(errs) == 0 && tc.errtype != "" {
  3321  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3322  		} else if len(errs) >= 1 {
  3323  			if errs[0].Type != tc.errtype {
  3324  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3325  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3326  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3327  			}
  3328  		}
  3329  	}
  3330  }
  3331  
  3332  func TestValidateCSIVolumeSource(t *testing.T) {
  3333  	testCases := []struct {
  3334  		name     string
  3335  		csi      *core.CSIVolumeSource
  3336  		errtype  field.ErrorType
  3337  		errfield string
  3338  	}{{
  3339  		name: "all required fields ok",
  3340  		csi:  &core.CSIVolumeSource{Driver: "test-driver"},
  3341  	}, {
  3342  		name:     "missing driver name",
  3343  		csi:      &core.CSIVolumeSource{Driver: ""},
  3344  		errtype:  field.ErrorTypeRequired,
  3345  		errfield: "driver",
  3346  	}, {
  3347  		name: "driver name: ok no punctuations",
  3348  		csi:  &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd"},
  3349  	}, {
  3350  		name: "driver name: ok dot only",
  3351  		csi:  &core.CSIVolumeSource{Driver: "io.kubernetes.storage.csi.flex"},
  3352  	}, {
  3353  		name: "driver name: ok dash only",
  3354  		csi:  &core.CSIVolumeSource{Driver: "io-kubernetes-storage-csi-flex"},
  3355  	}, {
  3356  		name:     "driver name: invalid underscore",
  3357  		csi:      &core.CSIVolumeSource{Driver: "io_kubernetes_storage_csi_flex"},
  3358  		errtype:  field.ErrorTypeInvalid,
  3359  		errfield: "driver",
  3360  	}, {
  3361  		name:     "driver name: invalid dot underscores",
  3362  		csi:      &core.CSIVolumeSource{Driver: "io.kubernetes.storage_csi.flex"},
  3363  		errtype:  field.ErrorTypeInvalid,
  3364  		errfield: "driver",
  3365  	}, {
  3366  		name: "driver name: ok beginning with number",
  3367  		csi:  &core.CSIVolumeSource{Driver: "2io.kubernetes.storage-csi.flex"},
  3368  	}, {
  3369  		name: "driver name: ok ending with number",
  3370  		csi:  &core.CSIVolumeSource{Driver: "io.kubernetes.storage-csi.flex2"},
  3371  	}, {
  3372  		name:     "driver name: invalid dot dash underscores",
  3373  		csi:      &core.CSIVolumeSource{Driver: "io.kubernetes-storage.csi_flex"},
  3374  		errtype:  field.ErrorTypeInvalid,
  3375  		errfield: "driver",
  3376  	},
  3377  
  3378  		{
  3379  			name: "driver name: ok length 1",
  3380  			csi:  &core.CSIVolumeSource{Driver: "a"},
  3381  		}, {
  3382  			name:     "driver name: invalid length > 63",
  3383  			csi:      &core.CSIVolumeSource{Driver: strings.Repeat("g", 65)},
  3384  			errtype:  field.ErrorTypeTooLong,
  3385  			errfield: "driver",
  3386  		}, {
  3387  			name:     "driver name: invalid start char",
  3388  			csi:      &core.CSIVolumeSource{Driver: "_comgooglestoragecsigcepd"},
  3389  			errtype:  field.ErrorTypeInvalid,
  3390  			errfield: "driver",
  3391  		}, {
  3392  			name:     "driver name: invalid end char",
  3393  			csi:      &core.CSIVolumeSource{Driver: "comgooglestoragecsigcepd/"},
  3394  			errtype:  field.ErrorTypeInvalid,
  3395  			errfield: "driver",
  3396  		}, {
  3397  			name:     "driver name: invalid separators",
  3398  			csi:      &core.CSIVolumeSource{Driver: "com/google/storage/csi~gcepd"},
  3399  			errtype:  field.ErrorTypeInvalid,
  3400  			errfield: "driver",
  3401  		}, {
  3402  			name: "valid nodePublishSecretRef",
  3403  			csi:  &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: "foobar"}},
  3404  		}, {
  3405  			name:     "nodePublishSecretRef: invalid name missing",
  3406  			csi:      &core.CSIVolumeSource{Driver: "com.google.gcepd", NodePublishSecretRef: &core.LocalObjectReference{Name: ""}},
  3407  			errtype:  field.ErrorTypeRequired,
  3408  			errfield: "nodePublishSecretRef.name",
  3409  		},
  3410  	}
  3411  
  3412  	for i, tc := range testCases {
  3413  		errs := validateCSIVolumeSource(tc.csi, field.NewPath("field"))
  3414  
  3415  		if len(errs) > 0 && tc.errtype == "" {
  3416  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3417  		} else if len(errs) == 0 && tc.errtype != "" {
  3418  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3419  		} else if len(errs) >= 1 {
  3420  			if errs[0].Type != tc.errtype {
  3421  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3422  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3423  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3424  			}
  3425  		}
  3426  	}
  3427  }
  3428  
  3429  func TestValidateCSIPersistentVolumeSource(t *testing.T) {
  3430  	testCases := []struct {
  3431  		name     string
  3432  		csi      *core.CSIPersistentVolumeSource
  3433  		errtype  field.ErrorType
  3434  		errfield string
  3435  	}{{
  3436  		name: "all required fields ok",
  3437  		csi:  &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
  3438  	}, {
  3439  		name: "with default values ok",
  3440  		csi:  &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
  3441  	}, {
  3442  		name:     "missing driver name",
  3443  		csi:      &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
  3444  		errtype:  field.ErrorTypeRequired,
  3445  		errfield: "driver",
  3446  	}, {
  3447  		name:     "missing volume handle",
  3448  		csi:      &core.CSIPersistentVolumeSource{Driver: "my-driver"},
  3449  		errtype:  field.ErrorTypeRequired,
  3450  		errfield: "volumeHandle",
  3451  	}, {
  3452  		name: "driver name: ok no punctuations",
  3453  		csi:  &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"},
  3454  	}, {
  3455  		name: "driver name: ok dot only",
  3456  		csi:  &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"},
  3457  	}, {
  3458  		name: "driver name: ok dash only",
  3459  		csi:  &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
  3460  	}, {
  3461  		name:     "driver name: invalid underscore",
  3462  		csi:      &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
  3463  		errtype:  field.ErrorTypeInvalid,
  3464  		errfield: "driver",
  3465  	}, {
  3466  		name:     "driver name: invalid dot underscores",
  3467  		csi:      &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
  3468  		errtype:  field.ErrorTypeInvalid,
  3469  		errfield: "driver",
  3470  	}, {
  3471  		name: "driver name: ok beginning with number",
  3472  		csi:  &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"},
  3473  	}, {
  3474  		name: "driver name: ok ending with number",
  3475  		csi:  &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"},
  3476  	}, {
  3477  		name:     "driver name: invalid dot dash underscores",
  3478  		csi:      &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
  3479  		errtype:  field.ErrorTypeInvalid,
  3480  		errfield: "driver",
  3481  	}, {
  3482  		name:     "driver name: invalid length 0",
  3483  		csi:      &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"},
  3484  		errtype:  field.ErrorTypeRequired,
  3485  		errfield: "driver",
  3486  	}, {
  3487  		name: "driver name: ok length 1",
  3488  		csi:  &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
  3489  	}, {
  3490  		name:     "driver name: invalid length > 63",
  3491  		csi:      &core.CSIPersistentVolumeSource{Driver: strings.Repeat("g", 65), VolumeHandle: "test-123"},
  3492  		errtype:  field.ErrorTypeTooLong,
  3493  		errfield: "driver",
  3494  	}, {
  3495  		name:     "driver name: invalid start char",
  3496  		csi:      &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"},
  3497  		errtype:  field.ErrorTypeInvalid,
  3498  		errfield: "driver",
  3499  	}, {
  3500  		name:     "driver name: invalid end char",
  3501  		csi:      &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"},
  3502  		errtype:  field.ErrorTypeInvalid,
  3503  		errfield: "driver",
  3504  	}, {
  3505  		name:     "driver name: invalid separators",
  3506  		csi:      &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"},
  3507  		errtype:  field.ErrorTypeInvalid,
  3508  		errfield: "driver",
  3509  	}, {
  3510  		name:     "controllerExpandSecretRef: invalid name missing",
  3511  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}},
  3512  		errtype:  field.ErrorTypeRequired,
  3513  		errfield: "controllerExpandSecretRef.name",
  3514  	}, {
  3515  		name:     "controllerExpandSecretRef: invalid namespace missing",
  3516  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}},
  3517  		errtype:  field.ErrorTypeRequired,
  3518  		errfield: "controllerExpandSecretRef.namespace",
  3519  	}, {
  3520  		name: "valid controllerExpandSecretRef",
  3521  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3522  	}, {
  3523  		name:     "controllerPublishSecretRef: invalid name missing",
  3524  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Namespace: "default"}},
  3525  		errtype:  field.ErrorTypeRequired,
  3526  		errfield: "controllerPublishSecretRef.name",
  3527  	}, {
  3528  		name:     "controllerPublishSecretRef: invalid namespace missing",
  3529  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar"}},
  3530  		errtype:  field.ErrorTypeRequired,
  3531  		errfield: "controllerPublishSecretRef.namespace",
  3532  	}, {
  3533  		name: "valid controllerPublishSecretRef",
  3534  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerPublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3535  	}, {
  3536  		name: "valid nodePublishSecretRef",
  3537  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3538  	}, {
  3539  		name:     "nodePublishSecretRef: invalid name missing",
  3540  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Namespace: "foobar"}},
  3541  		errtype:  field.ErrorTypeRequired,
  3542  		errfield: "nodePublishSecretRef.name",
  3543  	}, {
  3544  		name:     "nodePublishSecretRef: invalid namespace missing",
  3545  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar"}},
  3546  		errtype:  field.ErrorTypeRequired,
  3547  		errfield: "nodePublishSecretRef.namespace",
  3548  	}, {
  3549  		name:     "nodeExpandSecretRef: invalid name missing",
  3550  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Namespace: "default"}},
  3551  		errtype:  field.ErrorTypeRequired,
  3552  		errfield: "nodeExpandSecretRef.name",
  3553  	}, {
  3554  		name:     "nodeExpandSecretRef: invalid namespace missing",
  3555  		csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar"}},
  3556  		errtype:  field.ErrorTypeRequired,
  3557  		errfield: "nodeExpandSecretRef.namespace",
  3558  	}, {
  3559  		name: "valid nodeExpandSecretRef",
  3560  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3561  	}, {
  3562  		name: "Invalid nodePublishSecretRef",
  3563  		csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
  3564  	},
  3565  
  3566  		// tests with allowDNSSubDomainSecretName flag on/off
  3567  		{
  3568  			name: "valid nodeExpandSecretRef",
  3569  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3570  		}, {
  3571  			name: "valid long nodeExpandSecretRef",
  3572  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3573  		}, {
  3574  			name:     "Invalid nodeExpandSecretRef",
  3575  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodeExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3576  			errtype:  field.ErrorTypeInvalid,
  3577  			errfield: "nodeExpandSecretRef.name",
  3578  		}, {
  3579  			name: "valid nodePublishSecretRef",
  3580  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3581  		}, {
  3582  			name: "valid long nodePublishSecretRef",
  3583  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3584  		}, {
  3585  			name:     "Invalid nodePublishSecretRef",
  3586  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", NodePublishSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3587  			errtype:  field.ErrorTypeInvalid,
  3588  			errfield: "nodePublishSecretRef.name",
  3589  		}, {
  3590  			name: "valid ControllerExpandSecretRef",
  3591  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 63), Namespace: "default"}},
  3592  		}, {
  3593  			name: "valid long ControllerExpandSecretRef",
  3594  			csi:  &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 65), Namespace: "default"}},
  3595  		}, {
  3596  			name:     "Invalid ControllerExpandSecretRef",
  3597  			csi:      &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: strings.Repeat("g", 255), Namespace: "default"}},
  3598  			errtype:  field.ErrorTypeInvalid,
  3599  			errfield: "controllerExpandSecretRef.name",
  3600  		},
  3601  	}
  3602  
  3603  	for i, tc := range testCases {
  3604  		errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field"))
  3605  
  3606  		if len(errs) > 0 && tc.errtype == "" {
  3607  			t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
  3608  		} else if len(errs) == 0 && tc.errtype != "" {
  3609  			t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
  3610  		} else if len(errs) >= 1 {
  3611  			if errs[0].Type != tc.errtype {
  3612  				t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
  3613  			} else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
  3614  				t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
  3615  			}
  3616  		}
  3617  	}
  3618  }
  3619  
  3620  // This test is a little too top-to-bottom.  Ideally we would test each volume
  3621  // type on its own, but we want to also make sure that the logic works through
  3622  // the one-of wrapper, so we just do it all in one place.
  3623  func TestValidateVolumes(t *testing.T) {
  3624  	validInitiatorName := "iqn.2015-02.example.com:init"
  3625  	invalidInitiatorName := "2015-02.example.com:init"
  3626  
  3627  	type verr struct {
  3628  		etype  field.ErrorType
  3629  		field  string
  3630  		detail string
  3631  	}
  3632  
  3633  	testCases := []struct {
  3634  		name string
  3635  		vol  core.Volume
  3636  		errs []verr
  3637  		opts PodValidationOptions
  3638  	}{
  3639  		// EmptyDir and basic volume names
  3640  		{
  3641  			name: "valid alpha name",
  3642  			vol: core.Volume{
  3643  				Name: "empty",
  3644  				VolumeSource: core.VolumeSource{
  3645  					EmptyDir: &core.EmptyDirVolumeSource{},
  3646  				},
  3647  			},
  3648  		}, {
  3649  			name: "valid num name",
  3650  			vol: core.Volume{
  3651  				Name: "123",
  3652  				VolumeSource: core.VolumeSource{
  3653  					EmptyDir: &core.EmptyDirVolumeSource{},
  3654  				},
  3655  			},
  3656  		}, {
  3657  			name: "valid alphanum name",
  3658  			vol: core.Volume{
  3659  				Name: "empty-123",
  3660  				VolumeSource: core.VolumeSource{
  3661  					EmptyDir: &core.EmptyDirVolumeSource{},
  3662  				},
  3663  			},
  3664  		}, {
  3665  			name: "valid numalpha name",
  3666  			vol: core.Volume{
  3667  				Name: "123-empty",
  3668  				VolumeSource: core.VolumeSource{
  3669  					EmptyDir: &core.EmptyDirVolumeSource{},
  3670  				},
  3671  			},
  3672  		}, {
  3673  			name: "zero-length name",
  3674  			vol: core.Volume{
  3675  				Name:         "",
  3676  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3677  			},
  3678  			errs: []verr{{
  3679  				etype: field.ErrorTypeRequired,
  3680  				field: "name",
  3681  			}},
  3682  		}, {
  3683  			name: "name > 63 characters",
  3684  			vol: core.Volume{
  3685  				Name:         strings.Repeat("a", 64),
  3686  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3687  			},
  3688  			errs: []verr{{
  3689  				etype:  field.ErrorTypeInvalid,
  3690  				field:  "name",
  3691  				detail: "must be no more than",
  3692  			}},
  3693  		}, {
  3694  			name: "name has dots",
  3695  			vol: core.Volume{
  3696  				Name:         "a.b.c",
  3697  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3698  			},
  3699  			errs: []verr{{
  3700  				etype:  field.ErrorTypeInvalid,
  3701  				field:  "name",
  3702  				detail: "must not contain dots",
  3703  			}},
  3704  		}, {
  3705  			name: "name not a DNS label",
  3706  			vol: core.Volume{
  3707  				Name:         "Not a DNS label!",
  3708  				VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
  3709  			},
  3710  			errs: []verr{{
  3711  				etype:  field.ErrorTypeInvalid,
  3712  				field:  "name",
  3713  				detail: dnsLabelErrMsg,
  3714  			}},
  3715  		},
  3716  		// More than one source field specified.
  3717  		{
  3718  			name: "more than one source",
  3719  			vol: core.Volume{
  3720  				Name: "dups",
  3721  				VolumeSource: core.VolumeSource{
  3722  					EmptyDir: &core.EmptyDirVolumeSource{},
  3723  					HostPath: &core.HostPathVolumeSource{
  3724  						Path: "/mnt/path",
  3725  						Type: newHostPathType(string(core.HostPathDirectory)),
  3726  					},
  3727  				},
  3728  			},
  3729  			errs: []verr{{
  3730  				etype:  field.ErrorTypeForbidden,
  3731  				field:  "hostPath",
  3732  				detail: "may not specify more than 1 volume",
  3733  			}},
  3734  		},
  3735  		// HostPath Default
  3736  		{
  3737  			name: "default HostPath",
  3738  			vol: core.Volume{
  3739  				Name: "hostpath",
  3740  				VolumeSource: core.VolumeSource{
  3741  					HostPath: &core.HostPathVolumeSource{
  3742  						Path: "/mnt/path",
  3743  						Type: newHostPathType(string(core.HostPathDirectory)),
  3744  					},
  3745  				},
  3746  			},
  3747  		},
  3748  		// HostPath Supported
  3749  		{
  3750  			name: "valid HostPath",
  3751  			vol: core.Volume{
  3752  				Name: "hostpath",
  3753  				VolumeSource: core.VolumeSource{
  3754  					HostPath: &core.HostPathVolumeSource{
  3755  						Path: "/mnt/path",
  3756  						Type: newHostPathType(string(core.HostPathSocket)),
  3757  					},
  3758  				},
  3759  			},
  3760  		},
  3761  		// HostPath Invalid
  3762  		{
  3763  			name: "invalid HostPath",
  3764  			vol: core.Volume{
  3765  				Name: "hostpath",
  3766  				VolumeSource: core.VolumeSource{
  3767  					HostPath: &core.HostPathVolumeSource{
  3768  						Path: "/mnt/path",
  3769  						Type: newHostPathType("invalid"),
  3770  					},
  3771  				},
  3772  			},
  3773  			errs: []verr{{
  3774  				etype: field.ErrorTypeNotSupported,
  3775  				field: "type",
  3776  			}},
  3777  		}, {
  3778  			name: "invalid HostPath backsteps",
  3779  			vol: core.Volume{
  3780  				Name: "hostpath",
  3781  				VolumeSource: core.VolumeSource{
  3782  					HostPath: &core.HostPathVolumeSource{
  3783  						Path: "/mnt/path/..",
  3784  						Type: newHostPathType(string(core.HostPathDirectory)),
  3785  					},
  3786  				},
  3787  			},
  3788  			errs: []verr{{
  3789  				etype:  field.ErrorTypeInvalid,
  3790  				field:  "path",
  3791  				detail: "must not contain '..'",
  3792  			}},
  3793  		},
  3794  		// GcePersistentDisk
  3795  		{
  3796  			name: "valid GcePersistentDisk",
  3797  			vol: core.Volume{
  3798  				Name: "gce-pd",
  3799  				VolumeSource: core.VolumeSource{
  3800  					GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
  3801  						PDName:    "my-PD",
  3802  						FSType:    "ext4",
  3803  						Partition: 1,
  3804  						ReadOnly:  false,
  3805  					},
  3806  				},
  3807  			},
  3808  		},
  3809  		// AWSElasticBlockStore
  3810  		{
  3811  			name: "valid AWSElasticBlockStore",
  3812  			vol: core.Volume{
  3813  				Name: "aws-ebs",
  3814  				VolumeSource: core.VolumeSource{
  3815  					AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{
  3816  						VolumeID:  "my-PD",
  3817  						FSType:    "ext4",
  3818  						Partition: 1,
  3819  						ReadOnly:  false,
  3820  					},
  3821  				},
  3822  			},
  3823  		},
  3824  		// GitRepo
  3825  		{
  3826  			name: "valid GitRepo",
  3827  			vol: core.Volume{
  3828  				Name: "git-repo",
  3829  				VolumeSource: core.VolumeSource{
  3830  					GitRepo: &core.GitRepoVolumeSource{
  3831  						Repository: "my-repo",
  3832  						Revision:   "hashstring",
  3833  						Directory:  "target",
  3834  					},
  3835  				},
  3836  			},
  3837  		}, {
  3838  			name: "valid GitRepo in .",
  3839  			vol: core.Volume{
  3840  				Name: "git-repo-dot",
  3841  				VolumeSource: core.VolumeSource{
  3842  					GitRepo: &core.GitRepoVolumeSource{
  3843  						Repository: "my-repo",
  3844  						Directory:  ".",
  3845  					},
  3846  				},
  3847  			},
  3848  		}, {
  3849  			name: "valid GitRepo with .. in name",
  3850  			vol: core.Volume{
  3851  				Name: "git-repo-dot-dot-foo",
  3852  				VolumeSource: core.VolumeSource{
  3853  					GitRepo: &core.GitRepoVolumeSource{
  3854  						Repository: "my-repo",
  3855  						Directory:  "..foo",
  3856  					},
  3857  				},
  3858  			},
  3859  		}, {
  3860  			name: "GitRepo starts with ../",
  3861  			vol: core.Volume{
  3862  				Name: "gitrepo",
  3863  				VolumeSource: core.VolumeSource{
  3864  					GitRepo: &core.GitRepoVolumeSource{
  3865  						Repository: "foo",
  3866  						Directory:  "../dots/bar",
  3867  					},
  3868  				},
  3869  			},
  3870  			errs: []verr{{
  3871  				etype:  field.ErrorTypeInvalid,
  3872  				field:  "gitRepo.directory",
  3873  				detail: `must not contain '..'`,
  3874  			}},
  3875  		}, {
  3876  			name: "GitRepo contains ..",
  3877  			vol: core.Volume{
  3878  				Name: "gitrepo",
  3879  				VolumeSource: core.VolumeSource{
  3880  					GitRepo: &core.GitRepoVolumeSource{
  3881  						Repository: "foo",
  3882  						Directory:  "dots/../bar",
  3883  					},
  3884  				},
  3885  			},
  3886  			errs: []verr{{
  3887  				etype:  field.ErrorTypeInvalid,
  3888  				field:  "gitRepo.directory",
  3889  				detail: `must not contain '..'`,
  3890  			}},
  3891  		}, {
  3892  			name: "GitRepo absolute target",
  3893  			vol: core.Volume{
  3894  				Name: "gitrepo",
  3895  				VolumeSource: core.VolumeSource{
  3896  					GitRepo: &core.GitRepoVolumeSource{
  3897  						Repository: "foo",
  3898  						Directory:  "/abstarget",
  3899  					},
  3900  				},
  3901  			},
  3902  			errs: []verr{{
  3903  				etype: field.ErrorTypeInvalid,
  3904  				field: "gitRepo.directory",
  3905  			}},
  3906  		},
  3907  		// ISCSI
  3908  		{
  3909  			name: "valid ISCSI",
  3910  			vol: core.Volume{
  3911  				Name: "iscsi",
  3912  				VolumeSource: core.VolumeSource{
  3913  					ISCSI: &core.ISCSIVolumeSource{
  3914  						TargetPortal: "127.0.0.1",
  3915  						IQN:          "iqn.2015-02.example.com:test",
  3916  						Lun:          1,
  3917  						FSType:       "ext4",
  3918  						ReadOnly:     false,
  3919  					},
  3920  				},
  3921  			},
  3922  		}, {
  3923  			name: "valid IQN: eui format",
  3924  			vol: core.Volume{
  3925  				Name: "iscsi",
  3926  				VolumeSource: core.VolumeSource{
  3927  					ISCSI: &core.ISCSIVolumeSource{
  3928  						TargetPortal: "127.0.0.1",
  3929  						IQN:          "eui.0123456789ABCDEF",
  3930  						Lun:          1,
  3931  						FSType:       "ext4",
  3932  						ReadOnly:     false,
  3933  					},
  3934  				},
  3935  			},
  3936  		}, {
  3937  			name: "valid IQN: naa format",
  3938  			vol: core.Volume{
  3939  				Name: "iscsi",
  3940  				VolumeSource: core.VolumeSource{
  3941  					ISCSI: &core.ISCSIVolumeSource{
  3942  						TargetPortal: "127.0.0.1",
  3943  						IQN:          "naa.62004567BA64678D0123456789ABCDEF",
  3944  						Lun:          1,
  3945  						FSType:       "ext4",
  3946  						ReadOnly:     false,
  3947  					},
  3948  				},
  3949  			},
  3950  		}, {
  3951  			name: "empty portal",
  3952  			vol: core.Volume{
  3953  				Name: "iscsi",
  3954  				VolumeSource: core.VolumeSource{
  3955  					ISCSI: &core.ISCSIVolumeSource{
  3956  						TargetPortal: "",
  3957  						IQN:          "iqn.2015-02.example.com:test",
  3958  						Lun:          1,
  3959  						FSType:       "ext4",
  3960  						ReadOnly:     false,
  3961  					},
  3962  				},
  3963  			},
  3964  			errs: []verr{{
  3965  				etype: field.ErrorTypeRequired,
  3966  				field: "iscsi.targetPortal",
  3967  			}},
  3968  		}, {
  3969  			name: "empty iqn",
  3970  			vol: core.Volume{
  3971  				Name: "iscsi",
  3972  				VolumeSource: core.VolumeSource{
  3973  					ISCSI: &core.ISCSIVolumeSource{
  3974  						TargetPortal: "127.0.0.1",
  3975  						IQN:          "",
  3976  						Lun:          1,
  3977  						FSType:       "ext4",
  3978  						ReadOnly:     false,
  3979  					},
  3980  				},
  3981  			},
  3982  			errs: []verr{{
  3983  				etype: field.ErrorTypeRequired,
  3984  				field: "iscsi.iqn",
  3985  			}},
  3986  		}, {
  3987  			name: "invalid IQN: iqn format",
  3988  			vol: core.Volume{
  3989  				Name: "iscsi",
  3990  				VolumeSource: core.VolumeSource{
  3991  					ISCSI: &core.ISCSIVolumeSource{
  3992  						TargetPortal: "127.0.0.1",
  3993  						IQN:          "iqn.2015-02.example.com:test;ls;",
  3994  						Lun:          1,
  3995  						FSType:       "ext4",
  3996  						ReadOnly:     false,
  3997  					},
  3998  				},
  3999  			},
  4000  			errs: []verr{{
  4001  				etype: field.ErrorTypeInvalid,
  4002  				field: "iscsi.iqn",
  4003  			}},
  4004  		}, {
  4005  			name: "invalid IQN: eui format",
  4006  			vol: core.Volume{
  4007  				Name: "iscsi",
  4008  				VolumeSource: core.VolumeSource{
  4009  					ISCSI: &core.ISCSIVolumeSource{
  4010  						TargetPortal: "127.0.0.1",
  4011  						IQN:          "eui.0123456789ABCDEFGHIJ",
  4012  						Lun:          1,
  4013  						FSType:       "ext4",
  4014  						ReadOnly:     false,
  4015  					},
  4016  				},
  4017  			},
  4018  			errs: []verr{{
  4019  				etype: field.ErrorTypeInvalid,
  4020  				field: "iscsi.iqn",
  4021  			}},
  4022  		}, {
  4023  			name: "invalid IQN: naa format",
  4024  			vol: core.Volume{
  4025  				Name: "iscsi",
  4026  				VolumeSource: core.VolumeSource{
  4027  					ISCSI: &core.ISCSIVolumeSource{
  4028  						TargetPortal: "127.0.0.1",
  4029  						IQN:          "naa.62004567BA_4-78D.123456789ABCDEF",
  4030  						Lun:          1,
  4031  						FSType:       "ext4",
  4032  						ReadOnly:     false,
  4033  					},
  4034  				},
  4035  			},
  4036  			errs: []verr{{
  4037  				etype: field.ErrorTypeInvalid,
  4038  				field: "iscsi.iqn",
  4039  			}},
  4040  		}, {
  4041  			name: "valid initiatorName",
  4042  			vol: core.Volume{
  4043  				Name: "iscsi",
  4044  				VolumeSource: core.VolumeSource{
  4045  					ISCSI: &core.ISCSIVolumeSource{
  4046  						TargetPortal:  "127.0.0.1",
  4047  						IQN:           "iqn.2015-02.example.com:test",
  4048  						Lun:           1,
  4049  						InitiatorName: &validInitiatorName,
  4050  						FSType:        "ext4",
  4051  						ReadOnly:      false,
  4052  					},
  4053  				},
  4054  			},
  4055  		}, {
  4056  			name: "invalid initiatorName",
  4057  			vol: core.Volume{
  4058  				Name: "iscsi",
  4059  				VolumeSource: core.VolumeSource{
  4060  					ISCSI: &core.ISCSIVolumeSource{
  4061  						TargetPortal:  "127.0.0.1",
  4062  						IQN:           "iqn.2015-02.example.com:test",
  4063  						Lun:           1,
  4064  						InitiatorName: &invalidInitiatorName,
  4065  						FSType:        "ext4",
  4066  						ReadOnly:      false,
  4067  					},
  4068  				},
  4069  			},
  4070  			errs: []verr{{
  4071  				etype: field.ErrorTypeInvalid,
  4072  				field: "iscsi.initiatorname",
  4073  			}},
  4074  		}, {
  4075  			name: "empty secret",
  4076  			vol: core.Volume{
  4077  				Name: "iscsi",
  4078  				VolumeSource: core.VolumeSource{
  4079  					ISCSI: &core.ISCSIVolumeSource{
  4080  						TargetPortal:      "127.0.0.1",
  4081  						IQN:               "iqn.2015-02.example.com:test",
  4082  						Lun:               1,
  4083  						FSType:            "ext4",
  4084  						ReadOnly:          false,
  4085  						DiscoveryCHAPAuth: true,
  4086  					},
  4087  				},
  4088  			},
  4089  			errs: []verr{{
  4090  				etype: field.ErrorTypeRequired,
  4091  				field: "iscsi.secretRef",
  4092  			}},
  4093  		}, {
  4094  			name: "empty secret",
  4095  			vol: core.Volume{
  4096  				Name: "iscsi",
  4097  				VolumeSource: core.VolumeSource{
  4098  					ISCSI: &core.ISCSIVolumeSource{
  4099  						TargetPortal:    "127.0.0.1",
  4100  						IQN:             "iqn.2015-02.example.com:test",
  4101  						Lun:             1,
  4102  						FSType:          "ext4",
  4103  						ReadOnly:        false,
  4104  						SessionCHAPAuth: true,
  4105  					},
  4106  				},
  4107  			},
  4108  			errs: []verr{{
  4109  				etype: field.ErrorTypeRequired,
  4110  				field: "iscsi.secretRef",
  4111  			}},
  4112  		},
  4113  		// Secret
  4114  		{
  4115  			name: "valid Secret",
  4116  			vol: core.Volume{
  4117  				Name: "secret",
  4118  				VolumeSource: core.VolumeSource{
  4119  					Secret: &core.SecretVolumeSource{
  4120  						SecretName: "my-secret",
  4121  					},
  4122  				},
  4123  			},
  4124  		}, {
  4125  			name: "valid Secret with defaultMode",
  4126  			vol: core.Volume{
  4127  				Name: "secret",
  4128  				VolumeSource: core.VolumeSource{
  4129  					Secret: &core.SecretVolumeSource{
  4130  						SecretName:  "my-secret",
  4131  						DefaultMode: utilpointer.Int32(0644),
  4132  					},
  4133  				},
  4134  			},
  4135  		}, {
  4136  			name: "valid Secret with projection and mode",
  4137  			vol: core.Volume{
  4138  				Name: "secret",
  4139  				VolumeSource: core.VolumeSource{
  4140  					Secret: &core.SecretVolumeSource{
  4141  						SecretName: "my-secret",
  4142  						Items: []core.KeyToPath{{
  4143  							Key:  "key",
  4144  							Path: "filename",
  4145  							Mode: utilpointer.Int32(0644),
  4146  						}},
  4147  					},
  4148  				},
  4149  			},
  4150  		}, {
  4151  			name: "valid Secret with subdir projection",
  4152  			vol: core.Volume{
  4153  				Name: "secret",
  4154  				VolumeSource: core.VolumeSource{
  4155  					Secret: &core.SecretVolumeSource{
  4156  						SecretName: "my-secret",
  4157  						Items: []core.KeyToPath{{
  4158  							Key:  "key",
  4159  							Path: "dir/filename",
  4160  						}},
  4161  					},
  4162  				},
  4163  			},
  4164  		}, {
  4165  			name: "secret with missing path",
  4166  			vol: core.Volume{
  4167  				Name: "secret",
  4168  				VolumeSource: core.VolumeSource{
  4169  					Secret: &core.SecretVolumeSource{
  4170  						SecretName: "s",
  4171  						Items:      []core.KeyToPath{{Key: "key", Path: ""}},
  4172  					},
  4173  				},
  4174  			},
  4175  			errs: []verr{{
  4176  				etype: field.ErrorTypeRequired,
  4177  				field: "secret.items[0].path",
  4178  			}},
  4179  		}, {
  4180  			name: "secret with leading ..",
  4181  			vol: core.Volume{
  4182  				Name: "secret",
  4183  				VolumeSource: core.VolumeSource{
  4184  					Secret: &core.SecretVolumeSource{
  4185  						SecretName: "s",
  4186  						Items:      []core.KeyToPath{{Key: "key", Path: "../foo"}},
  4187  					},
  4188  				},
  4189  			},
  4190  			errs: []verr{{
  4191  				etype: field.ErrorTypeInvalid,
  4192  				field: "secret.items[0].path",
  4193  			}},
  4194  		}, {
  4195  			name: "secret with .. inside",
  4196  			vol: core.Volume{
  4197  				Name: "secret",
  4198  				VolumeSource: core.VolumeSource{
  4199  					Secret: &core.SecretVolumeSource{
  4200  						SecretName: "s",
  4201  						Items:      []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
  4202  					},
  4203  				},
  4204  			},
  4205  			errs: []verr{{
  4206  				etype: field.ErrorTypeInvalid,
  4207  				field: "secret.items[0].path",
  4208  			}},
  4209  		}, {
  4210  			name: "secret with invalid positive defaultMode",
  4211  			vol: core.Volume{
  4212  				Name: "secret",
  4213  				VolumeSource: core.VolumeSource{
  4214  					Secret: &core.SecretVolumeSource{
  4215  						SecretName:  "s",
  4216  						DefaultMode: utilpointer.Int32(01000),
  4217  					},
  4218  				},
  4219  			},
  4220  			errs: []verr{{
  4221  				etype: field.ErrorTypeInvalid,
  4222  				field: "secret.defaultMode",
  4223  			}},
  4224  		}, {
  4225  			name: "secret with invalid negative defaultMode",
  4226  			vol: core.Volume{
  4227  				Name: "secret",
  4228  				VolumeSource: core.VolumeSource{
  4229  					Secret: &core.SecretVolumeSource{
  4230  						SecretName:  "s",
  4231  						DefaultMode: utilpointer.Int32(-1),
  4232  					},
  4233  				},
  4234  			},
  4235  			errs: []verr{{
  4236  				etype: field.ErrorTypeInvalid,
  4237  				field: "secret.defaultMode",
  4238  			}},
  4239  		},
  4240  		// ConfigMap
  4241  		{
  4242  			name: "valid ConfigMap",
  4243  			vol: core.Volume{
  4244  				Name: "cfgmap",
  4245  				VolumeSource: core.VolumeSource{
  4246  					ConfigMap: &core.ConfigMapVolumeSource{
  4247  						LocalObjectReference: core.LocalObjectReference{
  4248  							Name: "my-cfgmap",
  4249  						},
  4250  					},
  4251  				},
  4252  			},
  4253  		}, {
  4254  			name: "valid ConfigMap with defaultMode",
  4255  			vol: core.Volume{
  4256  				Name: "cfgmap",
  4257  				VolumeSource: core.VolumeSource{
  4258  					ConfigMap: &core.ConfigMapVolumeSource{
  4259  						LocalObjectReference: core.LocalObjectReference{
  4260  							Name: "my-cfgmap",
  4261  						},
  4262  						DefaultMode: utilpointer.Int32(0644),
  4263  					},
  4264  				},
  4265  			},
  4266  		}, {
  4267  			name: "valid ConfigMap with projection and mode",
  4268  			vol: core.Volume{
  4269  				Name: "cfgmap",
  4270  				VolumeSource: core.VolumeSource{
  4271  					ConfigMap: &core.ConfigMapVolumeSource{
  4272  						LocalObjectReference: core.LocalObjectReference{
  4273  							Name: "my-cfgmap"},
  4274  						Items: []core.KeyToPath{{
  4275  							Key:  "key",
  4276  							Path: "filename",
  4277  							Mode: utilpointer.Int32(0644),
  4278  						}},
  4279  					},
  4280  				},
  4281  			},
  4282  		}, {
  4283  			name: "valid ConfigMap with subdir projection",
  4284  			vol: core.Volume{
  4285  				Name: "cfgmap",
  4286  				VolumeSource: core.VolumeSource{
  4287  					ConfigMap: &core.ConfigMapVolumeSource{
  4288  						LocalObjectReference: core.LocalObjectReference{
  4289  							Name: "my-cfgmap"},
  4290  						Items: []core.KeyToPath{{
  4291  							Key:  "key",
  4292  							Path: "dir/filename",
  4293  						}},
  4294  					},
  4295  				},
  4296  			},
  4297  		}, {
  4298  			name: "configmap with missing path",
  4299  			vol: core.Volume{
  4300  				Name: "cfgmap",
  4301  				VolumeSource: core.VolumeSource{
  4302  					ConfigMap: &core.ConfigMapVolumeSource{
  4303  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4304  						Items:                []core.KeyToPath{{Key: "key", Path: ""}},
  4305  					},
  4306  				},
  4307  			},
  4308  			errs: []verr{{
  4309  				etype: field.ErrorTypeRequired,
  4310  				field: "configMap.items[0].path",
  4311  			}},
  4312  		}, {
  4313  			name: "configmap with leading ..",
  4314  			vol: core.Volume{
  4315  				Name: "cfgmap",
  4316  				VolumeSource: core.VolumeSource{
  4317  					ConfigMap: &core.ConfigMapVolumeSource{
  4318  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4319  						Items:                []core.KeyToPath{{Key: "key", Path: "../foo"}},
  4320  					},
  4321  				},
  4322  			},
  4323  			errs: []verr{{
  4324  				etype: field.ErrorTypeInvalid,
  4325  				field: "configMap.items[0].path",
  4326  			}},
  4327  		}, {
  4328  			name: "configmap with .. inside",
  4329  			vol: core.Volume{
  4330  				Name: "cfgmap",
  4331  				VolumeSource: core.VolumeSource{
  4332  					ConfigMap: &core.ConfigMapVolumeSource{
  4333  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4334  						Items:                []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
  4335  					},
  4336  				},
  4337  			},
  4338  			errs: []verr{{
  4339  				etype: field.ErrorTypeInvalid,
  4340  				field: "configMap.items[0].path",
  4341  			}},
  4342  		}, {
  4343  			name: "configmap with invalid positive defaultMode",
  4344  			vol: core.Volume{
  4345  				Name: "cfgmap",
  4346  				VolumeSource: core.VolumeSource{
  4347  					ConfigMap: &core.ConfigMapVolumeSource{
  4348  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4349  						DefaultMode:          utilpointer.Int32(01000),
  4350  					},
  4351  				},
  4352  			},
  4353  			errs: []verr{{
  4354  				etype: field.ErrorTypeInvalid,
  4355  				field: "configMap.defaultMode",
  4356  			}},
  4357  		}, {
  4358  			name: "configmap with invalid negative defaultMode",
  4359  			vol: core.Volume{
  4360  				Name: "cfgmap",
  4361  				VolumeSource: core.VolumeSource{
  4362  					ConfigMap: &core.ConfigMapVolumeSource{
  4363  						LocalObjectReference: core.LocalObjectReference{Name: "c"},
  4364  						DefaultMode:          utilpointer.Int32(-1),
  4365  					},
  4366  				},
  4367  			},
  4368  			errs: []verr{{
  4369  				etype: field.ErrorTypeInvalid,
  4370  				field: "configMap.defaultMode",
  4371  			}},
  4372  		},
  4373  		// Glusterfs
  4374  		{
  4375  			name: "valid Glusterfs",
  4376  			vol: core.Volume{
  4377  				Name: "glusterfs",
  4378  				VolumeSource: core.VolumeSource{
  4379  					Glusterfs: &core.GlusterfsVolumeSource{
  4380  						EndpointsName: "host1",
  4381  						Path:          "path",
  4382  						ReadOnly:      false,
  4383  					},
  4384  				},
  4385  			},
  4386  		}, {
  4387  			name: "empty hosts",
  4388  			vol: core.Volume{
  4389  				Name: "glusterfs",
  4390  				VolumeSource: core.VolumeSource{
  4391  					Glusterfs: &core.GlusterfsVolumeSource{
  4392  						EndpointsName: "",
  4393  						Path:          "path",
  4394  						ReadOnly:      false,
  4395  					},
  4396  				},
  4397  			},
  4398  			errs: []verr{{
  4399  				etype: field.ErrorTypeRequired,
  4400  				field: "glusterfs.endpoints",
  4401  			}},
  4402  		}, {
  4403  			name: "empty path",
  4404  			vol: core.Volume{
  4405  				Name: "glusterfs",
  4406  				VolumeSource: core.VolumeSource{
  4407  					Glusterfs: &core.GlusterfsVolumeSource{
  4408  						EndpointsName: "host",
  4409  						Path:          "",
  4410  						ReadOnly:      false,
  4411  					},
  4412  				},
  4413  			},
  4414  			errs: []verr{{
  4415  				etype: field.ErrorTypeRequired,
  4416  				field: "glusterfs.path",
  4417  			}},
  4418  		},
  4419  		// Flocker
  4420  		{
  4421  			name: "valid Flocker -- datasetUUID",
  4422  			vol: core.Volume{
  4423  				Name: "flocker",
  4424  				VolumeSource: core.VolumeSource{
  4425  					Flocker: &core.FlockerVolumeSource{
  4426  						DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
  4427  					},
  4428  				},
  4429  			},
  4430  		}, {
  4431  			name: "valid Flocker -- datasetName",
  4432  			vol: core.Volume{
  4433  				Name: "flocker",
  4434  				VolumeSource: core.VolumeSource{
  4435  					Flocker: &core.FlockerVolumeSource{
  4436  						DatasetName: "datasetName",
  4437  					},
  4438  				},
  4439  			},
  4440  		}, {
  4441  			name: "both empty",
  4442  			vol: core.Volume{
  4443  				Name: "flocker",
  4444  				VolumeSource: core.VolumeSource{
  4445  					Flocker: &core.FlockerVolumeSource{
  4446  						DatasetName: "",
  4447  					},
  4448  				},
  4449  			},
  4450  			errs: []verr{{
  4451  				etype: field.ErrorTypeRequired,
  4452  				field: "flocker",
  4453  			}},
  4454  		}, {
  4455  			name: "both specified",
  4456  			vol: core.Volume{
  4457  				Name: "flocker",
  4458  				VolumeSource: core.VolumeSource{
  4459  					Flocker: &core.FlockerVolumeSource{
  4460  						DatasetName: "datasetName",
  4461  						DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
  4462  					},
  4463  				},
  4464  			},
  4465  			errs: []verr{{
  4466  				etype: field.ErrorTypeInvalid,
  4467  				field: "flocker",
  4468  			}},
  4469  		}, {
  4470  			name: "slash in flocker datasetName",
  4471  			vol: core.Volume{
  4472  				Name: "flocker",
  4473  				VolumeSource: core.VolumeSource{
  4474  					Flocker: &core.FlockerVolumeSource{
  4475  						DatasetName: "foo/bar",
  4476  					},
  4477  				},
  4478  			},
  4479  			errs: []verr{{
  4480  				etype:  field.ErrorTypeInvalid,
  4481  				field:  "flocker.datasetName",
  4482  				detail: "must not contain '/'",
  4483  			}},
  4484  		},
  4485  		// RBD
  4486  		{
  4487  			name: "valid RBD",
  4488  			vol: core.Volume{
  4489  				Name: "rbd",
  4490  				VolumeSource: core.VolumeSource{
  4491  					RBD: &core.RBDVolumeSource{
  4492  						CephMonitors: []string{"foo"},
  4493  						RBDImage:     "bar",
  4494  						FSType:       "ext4",
  4495  					},
  4496  				},
  4497  			},
  4498  		}, {
  4499  			name: "empty rbd monitors",
  4500  			vol: core.Volume{
  4501  				Name: "rbd",
  4502  				VolumeSource: core.VolumeSource{
  4503  					RBD: &core.RBDVolumeSource{
  4504  						CephMonitors: []string{},
  4505  						RBDImage:     "bar",
  4506  						FSType:       "ext4",
  4507  					},
  4508  				},
  4509  			},
  4510  			errs: []verr{{
  4511  				etype: field.ErrorTypeRequired,
  4512  				field: "rbd.monitors",
  4513  			}},
  4514  		}, {
  4515  			name: "empty image",
  4516  			vol: core.Volume{
  4517  				Name: "rbd",
  4518  				VolumeSource: core.VolumeSource{
  4519  					RBD: &core.RBDVolumeSource{
  4520  						CephMonitors: []string{"foo"},
  4521  						RBDImage:     "",
  4522  						FSType:       "ext4",
  4523  					},
  4524  				},
  4525  			},
  4526  			errs: []verr{{
  4527  				etype: field.ErrorTypeRequired,
  4528  				field: "rbd.image",
  4529  			}},
  4530  		},
  4531  		// Cinder
  4532  		{
  4533  			name: "valid Cinder",
  4534  			vol: core.Volume{
  4535  				Name: "cinder",
  4536  				VolumeSource: core.VolumeSource{
  4537  					Cinder: &core.CinderVolumeSource{
  4538  						VolumeID: "29ea5088-4f60-4757-962e-dba678767887",
  4539  						FSType:   "ext4",
  4540  						ReadOnly: false,
  4541  					},
  4542  				},
  4543  			},
  4544  		},
  4545  		// CephFS
  4546  		{
  4547  			name: "valid CephFS",
  4548  			vol: core.Volume{
  4549  				Name: "cephfs",
  4550  				VolumeSource: core.VolumeSource{
  4551  					CephFS: &core.CephFSVolumeSource{
  4552  						Monitors: []string{"foo"},
  4553  					},
  4554  				},
  4555  			},
  4556  		}, {
  4557  			name: "empty cephfs monitors",
  4558  			vol: core.Volume{
  4559  				Name: "cephfs",
  4560  				VolumeSource: core.VolumeSource{
  4561  					CephFS: &core.CephFSVolumeSource{
  4562  						Monitors: []string{},
  4563  					},
  4564  				},
  4565  			},
  4566  			errs: []verr{{
  4567  				etype: field.ErrorTypeRequired,
  4568  				field: "cephfs.monitors",
  4569  			}},
  4570  		},
  4571  		// DownwardAPI
  4572  		{
  4573  			name: "valid DownwardAPI",
  4574  			vol: core.Volume{
  4575  				Name: "downwardapi",
  4576  				VolumeSource: core.VolumeSource{
  4577  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4578  						Items: []core.DownwardAPIVolumeFile{{
  4579  							Path: "labels",
  4580  							FieldRef: &core.ObjectFieldSelector{
  4581  								APIVersion: "v1",
  4582  								FieldPath:  "metadata.labels",
  4583  							},
  4584  						}, {
  4585  							Path: "labels with subscript",
  4586  							FieldRef: &core.ObjectFieldSelector{
  4587  								APIVersion: "v1",
  4588  								FieldPath:  "metadata.labels['key']",
  4589  							},
  4590  						}, {
  4591  							Path: "labels with complex subscript",
  4592  							FieldRef: &core.ObjectFieldSelector{
  4593  								APIVersion: "v1",
  4594  								FieldPath:  "metadata.labels['test.example.com/key']",
  4595  							},
  4596  						}, {
  4597  							Path: "annotations",
  4598  							FieldRef: &core.ObjectFieldSelector{
  4599  								APIVersion: "v1",
  4600  								FieldPath:  "metadata.annotations",
  4601  							},
  4602  						}, {
  4603  							Path: "annotations with subscript",
  4604  							FieldRef: &core.ObjectFieldSelector{
  4605  								APIVersion: "v1",
  4606  								FieldPath:  "metadata.annotations['key']",
  4607  							},
  4608  						}, {
  4609  							Path: "annotations with complex subscript",
  4610  							FieldRef: &core.ObjectFieldSelector{
  4611  								APIVersion: "v1",
  4612  								FieldPath:  "metadata.annotations['TEST.EXAMPLE.COM/key']",
  4613  							},
  4614  						}, {
  4615  							Path: "namespace",
  4616  							FieldRef: &core.ObjectFieldSelector{
  4617  								APIVersion: "v1",
  4618  								FieldPath:  "metadata.namespace",
  4619  							},
  4620  						}, {
  4621  							Path: "name",
  4622  							FieldRef: &core.ObjectFieldSelector{
  4623  								APIVersion: "v1",
  4624  								FieldPath:  "metadata.name",
  4625  							},
  4626  						}, {
  4627  							Path: "path/with/subdirs",
  4628  							FieldRef: &core.ObjectFieldSelector{
  4629  								APIVersion: "v1",
  4630  								FieldPath:  "metadata.labels",
  4631  							},
  4632  						}, {
  4633  							Path: "path/./withdot",
  4634  							FieldRef: &core.ObjectFieldSelector{
  4635  								APIVersion: "v1",
  4636  								FieldPath:  "metadata.labels",
  4637  							},
  4638  						}, {
  4639  							Path: "path/with/embedded..dotdot",
  4640  							FieldRef: &core.ObjectFieldSelector{
  4641  								APIVersion: "v1",
  4642  								FieldPath:  "metadata.labels",
  4643  							},
  4644  						}, {
  4645  							Path: "path/with/leading/..dotdot",
  4646  							FieldRef: &core.ObjectFieldSelector{
  4647  								APIVersion: "v1",
  4648  								FieldPath:  "metadata.labels",
  4649  							},
  4650  						}, {
  4651  							Path: "cpu_limit",
  4652  							ResourceFieldRef: &core.ResourceFieldSelector{
  4653  								ContainerName: "test-container",
  4654  								Resource:      "limits.cpu",
  4655  							},
  4656  						}, {
  4657  							Path: "cpu_request",
  4658  							ResourceFieldRef: &core.ResourceFieldSelector{
  4659  								ContainerName: "test-container",
  4660  								Resource:      "requests.cpu",
  4661  							},
  4662  						}, {
  4663  							Path: "memory_limit",
  4664  							ResourceFieldRef: &core.ResourceFieldSelector{
  4665  								ContainerName: "test-container",
  4666  								Resource:      "limits.memory",
  4667  							},
  4668  						}, {
  4669  							Path: "memory_request",
  4670  							ResourceFieldRef: &core.ResourceFieldSelector{
  4671  								ContainerName: "test-container",
  4672  								Resource:      "requests.memory",
  4673  							},
  4674  						}},
  4675  					},
  4676  				},
  4677  			},
  4678  		}, {
  4679  			name: "hugepages-downwardAPI-enabled",
  4680  			vol: core.Volume{
  4681  				Name: "downwardapi",
  4682  				VolumeSource: core.VolumeSource{
  4683  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4684  						Items: []core.DownwardAPIVolumeFile{{
  4685  							Path: "hugepages_request",
  4686  							ResourceFieldRef: &core.ResourceFieldSelector{
  4687  								ContainerName: "test-container",
  4688  								Resource:      "requests.hugepages-2Mi",
  4689  							},
  4690  						}, {
  4691  							Path: "hugepages_limit",
  4692  							ResourceFieldRef: &core.ResourceFieldSelector{
  4693  								ContainerName: "test-container",
  4694  								Resource:      "limits.hugepages-2Mi",
  4695  							},
  4696  						}},
  4697  					},
  4698  				},
  4699  			},
  4700  		}, {
  4701  			name: "downapi valid defaultMode",
  4702  			vol: core.Volume{
  4703  				Name: "downapi",
  4704  				VolumeSource: core.VolumeSource{
  4705  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4706  						DefaultMode: utilpointer.Int32(0644),
  4707  					},
  4708  				},
  4709  			},
  4710  		}, {
  4711  			name: "downapi valid item mode",
  4712  			vol: core.Volume{
  4713  				Name: "downapi",
  4714  				VolumeSource: core.VolumeSource{
  4715  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4716  						Items: []core.DownwardAPIVolumeFile{{
  4717  							Mode: utilpointer.Int32(0644),
  4718  							Path: "path",
  4719  							FieldRef: &core.ObjectFieldSelector{
  4720  								APIVersion: "v1",
  4721  								FieldPath:  "metadata.labels",
  4722  							},
  4723  						}},
  4724  					},
  4725  				},
  4726  			},
  4727  		}, {
  4728  			name: "downapi invalid positive item mode",
  4729  			vol: core.Volume{
  4730  				Name: "downapi",
  4731  				VolumeSource: core.VolumeSource{
  4732  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4733  						Items: []core.DownwardAPIVolumeFile{{
  4734  							Mode: utilpointer.Int32(01000),
  4735  							Path: "path",
  4736  							FieldRef: &core.ObjectFieldSelector{
  4737  								APIVersion: "v1",
  4738  								FieldPath:  "metadata.labels",
  4739  							},
  4740  						}},
  4741  					},
  4742  				},
  4743  			},
  4744  			errs: []verr{{
  4745  				etype: field.ErrorTypeInvalid,
  4746  				field: "downwardAPI.mode",
  4747  			}},
  4748  		}, {
  4749  			name: "downapi invalid negative item mode",
  4750  			vol: core.Volume{
  4751  				Name: "downapi",
  4752  				VolumeSource: core.VolumeSource{
  4753  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4754  						Items: []core.DownwardAPIVolumeFile{{
  4755  							Mode: utilpointer.Int32(-1),
  4756  							Path: "path",
  4757  							FieldRef: &core.ObjectFieldSelector{
  4758  								APIVersion: "v1",
  4759  								FieldPath:  "metadata.labels",
  4760  							},
  4761  						}},
  4762  					},
  4763  				},
  4764  			},
  4765  			errs: []verr{{
  4766  				etype: field.ErrorTypeInvalid,
  4767  				field: "downwardAPI.mode",
  4768  			}},
  4769  		}, {
  4770  			name: "downapi empty metatada path",
  4771  			vol: core.Volume{
  4772  				Name: "downapi",
  4773  				VolumeSource: core.VolumeSource{
  4774  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4775  						Items: []core.DownwardAPIVolumeFile{{
  4776  							Path: "",
  4777  							FieldRef: &core.ObjectFieldSelector{
  4778  								APIVersion: "v1",
  4779  								FieldPath:  "metadata.labels",
  4780  							},
  4781  						}},
  4782  					},
  4783  				},
  4784  			},
  4785  			errs: []verr{{
  4786  				etype: field.ErrorTypeRequired,
  4787  				field: "downwardAPI.path",
  4788  			}},
  4789  		}, {
  4790  			name: "downapi absolute path",
  4791  			vol: core.Volume{
  4792  				Name: "downapi",
  4793  				VolumeSource: core.VolumeSource{
  4794  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4795  						Items: []core.DownwardAPIVolumeFile{{
  4796  							Path: "/absolutepath",
  4797  							FieldRef: &core.ObjectFieldSelector{
  4798  								APIVersion: "v1",
  4799  								FieldPath:  "metadata.labels",
  4800  							},
  4801  						}},
  4802  					},
  4803  				},
  4804  			},
  4805  			errs: []verr{{
  4806  				etype: field.ErrorTypeInvalid,
  4807  				field: "downwardAPI.path",
  4808  			}},
  4809  		}, {
  4810  			name: "downapi dot dot path",
  4811  			vol: core.Volume{
  4812  				Name: "downapi",
  4813  				VolumeSource: core.VolumeSource{
  4814  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4815  						Items: []core.DownwardAPIVolumeFile{{
  4816  							Path: "../../passwd",
  4817  							FieldRef: &core.ObjectFieldSelector{
  4818  								APIVersion: "v1",
  4819  								FieldPath:  "metadata.labels",
  4820  							},
  4821  						}},
  4822  					},
  4823  				},
  4824  			},
  4825  			errs: []verr{{
  4826  				etype:  field.ErrorTypeInvalid,
  4827  				field:  "downwardAPI.path",
  4828  				detail: `must not contain '..'`,
  4829  			}},
  4830  		}, {
  4831  			name: "downapi dot dot file name",
  4832  			vol: core.Volume{
  4833  				Name: "downapi",
  4834  				VolumeSource: core.VolumeSource{
  4835  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4836  						Items: []core.DownwardAPIVolumeFile{{
  4837  							Path: "..badFileName",
  4838  							FieldRef: &core.ObjectFieldSelector{
  4839  								APIVersion: "v1",
  4840  								FieldPath:  "metadata.labels",
  4841  							},
  4842  						}},
  4843  					},
  4844  				},
  4845  			},
  4846  			errs: []verr{{
  4847  				etype:  field.ErrorTypeInvalid,
  4848  				field:  "downwardAPI.path",
  4849  				detail: `must not start with '..'`,
  4850  			}},
  4851  		}, {
  4852  			name: "downapi dot dot first level dirent",
  4853  			vol: core.Volume{
  4854  				Name: "downapi",
  4855  				VolumeSource: core.VolumeSource{
  4856  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4857  						Items: []core.DownwardAPIVolumeFile{{
  4858  							Path: "..badDirName/goodFileName",
  4859  							FieldRef: &core.ObjectFieldSelector{
  4860  								APIVersion: "v1",
  4861  								FieldPath:  "metadata.labels",
  4862  							},
  4863  						}},
  4864  					},
  4865  				},
  4866  			},
  4867  			errs: []verr{{
  4868  				etype:  field.ErrorTypeInvalid,
  4869  				field:  "downwardAPI.path",
  4870  				detail: `must not start with '..'`,
  4871  			}},
  4872  		}, {
  4873  			name: "downapi fieldRef and ResourceFieldRef together",
  4874  			vol: core.Volume{
  4875  				Name: "downapi",
  4876  				VolumeSource: core.VolumeSource{
  4877  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4878  						Items: []core.DownwardAPIVolumeFile{{
  4879  							Path: "test",
  4880  							FieldRef: &core.ObjectFieldSelector{
  4881  								APIVersion: "v1",
  4882  								FieldPath:  "metadata.labels",
  4883  							},
  4884  							ResourceFieldRef: &core.ResourceFieldSelector{
  4885  								ContainerName: "test-container",
  4886  								Resource:      "requests.memory",
  4887  							},
  4888  						}},
  4889  					},
  4890  				},
  4891  			},
  4892  			errs: []verr{{
  4893  				etype:  field.ErrorTypeInvalid,
  4894  				field:  "downwardAPI",
  4895  				detail: "fieldRef and resourceFieldRef can not be specified simultaneously",
  4896  			}},
  4897  		}, {
  4898  			name: "downapi invalid positive defaultMode",
  4899  			vol: core.Volume{
  4900  				Name: "downapi",
  4901  				VolumeSource: core.VolumeSource{
  4902  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4903  						DefaultMode: utilpointer.Int32(01000),
  4904  					},
  4905  				},
  4906  			},
  4907  			errs: []verr{{
  4908  				etype: field.ErrorTypeInvalid,
  4909  				field: "downwardAPI.defaultMode",
  4910  			}},
  4911  		}, {
  4912  			name: "downapi invalid negative defaultMode",
  4913  			vol: core.Volume{
  4914  				Name: "downapi",
  4915  				VolumeSource: core.VolumeSource{
  4916  					DownwardAPI: &core.DownwardAPIVolumeSource{
  4917  						DefaultMode: utilpointer.Int32(-1),
  4918  					},
  4919  				},
  4920  			},
  4921  			errs: []verr{{
  4922  				etype: field.ErrorTypeInvalid,
  4923  				field: "downwardAPI.defaultMode",
  4924  			}},
  4925  		},
  4926  		// FC
  4927  		{
  4928  			name: "FC valid targetWWNs and lun",
  4929  			vol: core.Volume{
  4930  				Name: "fc",
  4931  				VolumeSource: core.VolumeSource{
  4932  					FC: &core.FCVolumeSource{
  4933  						TargetWWNs: []string{"some_wwn"},
  4934  						Lun:        utilpointer.Int32(1),
  4935  						FSType:     "ext4",
  4936  						ReadOnly:   false,
  4937  					},
  4938  				},
  4939  			},
  4940  		}, {
  4941  			name: "FC valid wwids",
  4942  			vol: core.Volume{
  4943  				Name: "fc",
  4944  				VolumeSource: core.VolumeSource{
  4945  					FC: &core.FCVolumeSource{
  4946  						WWIDs:    []string{"some_wwid"},
  4947  						FSType:   "ext4",
  4948  						ReadOnly: false,
  4949  					},
  4950  				},
  4951  			},
  4952  		}, {
  4953  			name: "FC empty targetWWNs and wwids",
  4954  			vol: core.Volume{
  4955  				Name: "fc",
  4956  				VolumeSource: core.VolumeSource{
  4957  					FC: &core.FCVolumeSource{
  4958  						TargetWWNs: []string{},
  4959  						Lun:        utilpointer.Int32(1),
  4960  						WWIDs:      []string{},
  4961  						FSType:     "ext4",
  4962  						ReadOnly:   false,
  4963  					},
  4964  				},
  4965  			},
  4966  			errs: []verr{{
  4967  				etype:  field.ErrorTypeRequired,
  4968  				field:  "fc.targetWWNs",
  4969  				detail: "must specify either targetWWNs or wwids",
  4970  			}},
  4971  		}, {
  4972  			name: "FC invalid: both targetWWNs and wwids simultaneously",
  4973  			vol: core.Volume{
  4974  				Name: "fc",
  4975  				VolumeSource: core.VolumeSource{
  4976  					FC: &core.FCVolumeSource{
  4977  						TargetWWNs: []string{"some_wwn"},
  4978  						Lun:        utilpointer.Int32(1),
  4979  						WWIDs:      []string{"some_wwid"},
  4980  						FSType:     "ext4",
  4981  						ReadOnly:   false,
  4982  					},
  4983  				},
  4984  			},
  4985  			errs: []verr{{
  4986  				etype:  field.ErrorTypeInvalid,
  4987  				field:  "fc.targetWWNs",
  4988  				detail: "targetWWNs and wwids can not be specified simultaneously",
  4989  			}},
  4990  		}, {
  4991  			name: "FC valid targetWWNs and empty lun",
  4992  			vol: core.Volume{
  4993  				Name: "fc",
  4994  				VolumeSource: core.VolumeSource{
  4995  					FC: &core.FCVolumeSource{
  4996  						TargetWWNs: []string{"wwn"},
  4997  						Lun:        nil,
  4998  						FSType:     "ext4",
  4999  						ReadOnly:   false,
  5000  					},
  5001  				},
  5002  			},
  5003  			errs: []verr{{
  5004  				etype:  field.ErrorTypeRequired,
  5005  				field:  "fc.lun",
  5006  				detail: "lun is required if targetWWNs is specified",
  5007  			}},
  5008  		}, {
  5009  			name: "FC valid targetWWNs and invalid lun",
  5010  			vol: core.Volume{
  5011  				Name: "fc",
  5012  				VolumeSource: core.VolumeSource{
  5013  					FC: &core.FCVolumeSource{
  5014  						TargetWWNs: []string{"wwn"},
  5015  						Lun:        utilpointer.Int32(256),
  5016  						FSType:     "ext4",
  5017  						ReadOnly:   false,
  5018  					},
  5019  				},
  5020  			},
  5021  			errs: []verr{{
  5022  				etype:  field.ErrorTypeInvalid,
  5023  				field:  "fc.lun",
  5024  				detail: validation.InclusiveRangeError(0, 255),
  5025  			}},
  5026  		},
  5027  		// FlexVolume
  5028  		{
  5029  			name: "valid FlexVolume",
  5030  			vol: core.Volume{
  5031  				Name: "flex-volume",
  5032  				VolumeSource: core.VolumeSource{
  5033  					FlexVolume: &core.FlexVolumeSource{
  5034  						Driver: "kubernetes.io/blue",
  5035  						FSType: "ext4",
  5036  					},
  5037  				},
  5038  			},
  5039  		},
  5040  		// AzureFile
  5041  		{
  5042  			name: "valid AzureFile",
  5043  			vol: core.Volume{
  5044  				Name: "azure-file",
  5045  				VolumeSource: core.VolumeSource{
  5046  					AzureFile: &core.AzureFileVolumeSource{
  5047  						SecretName: "key",
  5048  						ShareName:  "share",
  5049  						ReadOnly:   false,
  5050  					},
  5051  				},
  5052  			},
  5053  		}, {
  5054  			name: "AzureFile empty secret",
  5055  			vol: core.Volume{
  5056  				Name: "azure-file",
  5057  				VolumeSource: core.VolumeSource{
  5058  					AzureFile: &core.AzureFileVolumeSource{
  5059  						SecretName: "",
  5060  						ShareName:  "share",
  5061  						ReadOnly:   false,
  5062  					},
  5063  				},
  5064  			},
  5065  			errs: []verr{{
  5066  				etype: field.ErrorTypeRequired,
  5067  				field: "azureFile.secretName",
  5068  			}},
  5069  		}, {
  5070  			name: "AzureFile empty share",
  5071  			vol: core.Volume{
  5072  				Name: "azure-file",
  5073  				VolumeSource: core.VolumeSource{
  5074  					AzureFile: &core.AzureFileVolumeSource{
  5075  						SecretName: "name",
  5076  						ShareName:  "",
  5077  						ReadOnly:   false,
  5078  					},
  5079  				},
  5080  			},
  5081  			errs: []verr{{
  5082  				etype: field.ErrorTypeRequired,
  5083  				field: "azureFile.shareName",
  5084  			}},
  5085  		},
  5086  		// Quobyte
  5087  		{
  5088  			name: "valid Quobyte",
  5089  			vol: core.Volume{
  5090  				Name: "quobyte",
  5091  				VolumeSource: core.VolumeSource{
  5092  					Quobyte: &core.QuobyteVolumeSource{
  5093  						Registry: "registry:7861",
  5094  						Volume:   "volume",
  5095  						ReadOnly: false,
  5096  						User:     "root",
  5097  						Group:    "root",
  5098  						Tenant:   "ThisIsSomeTenantUUID",
  5099  					},
  5100  				},
  5101  			},
  5102  		}, {
  5103  			name: "empty registry quobyte",
  5104  			vol: core.Volume{
  5105  				Name: "quobyte",
  5106  				VolumeSource: core.VolumeSource{
  5107  					Quobyte: &core.QuobyteVolumeSource{
  5108  						Volume: "/test",
  5109  						Tenant: "ThisIsSomeTenantUUID",
  5110  					},
  5111  				},
  5112  			},
  5113  			errs: []verr{{
  5114  				etype: field.ErrorTypeRequired,
  5115  				field: "quobyte.registry",
  5116  			}},
  5117  		}, {
  5118  			name: "wrong format registry quobyte",
  5119  			vol: core.Volume{
  5120  				Name: "quobyte",
  5121  				VolumeSource: core.VolumeSource{
  5122  					Quobyte: &core.QuobyteVolumeSource{
  5123  						Registry: "registry7861",
  5124  						Volume:   "/test",
  5125  						Tenant:   "ThisIsSomeTenantUUID",
  5126  					},
  5127  				},
  5128  			},
  5129  			errs: []verr{{
  5130  				etype: field.ErrorTypeInvalid,
  5131  				field: "quobyte.registry",
  5132  			}},
  5133  		}, {
  5134  			name: "wrong format multiple registries quobyte",
  5135  			vol: core.Volume{
  5136  				Name: "quobyte",
  5137  				VolumeSource: core.VolumeSource{
  5138  					Quobyte: &core.QuobyteVolumeSource{
  5139  						Registry: "registry:7861,reg2",
  5140  						Volume:   "/test",
  5141  						Tenant:   "ThisIsSomeTenantUUID",
  5142  					},
  5143  				},
  5144  			},
  5145  			errs: []verr{{
  5146  				etype: field.ErrorTypeInvalid,
  5147  				field: "quobyte.registry",
  5148  			}},
  5149  		}, {
  5150  			name: "empty volume quobyte",
  5151  			vol: core.Volume{
  5152  				Name: "quobyte",
  5153  				VolumeSource: core.VolumeSource{
  5154  					Quobyte: &core.QuobyteVolumeSource{
  5155  						Registry: "registry:7861",
  5156  						Tenant:   "ThisIsSomeTenantUUID",
  5157  					},
  5158  				},
  5159  			},
  5160  			errs: []verr{{
  5161  				etype: field.ErrorTypeRequired,
  5162  				field: "quobyte.volume",
  5163  			}},
  5164  		}, {
  5165  			name: "empty tenant quobyte",
  5166  			vol: core.Volume{
  5167  				Name: "quobyte",
  5168  				VolumeSource: core.VolumeSource{
  5169  					Quobyte: &core.QuobyteVolumeSource{
  5170  						Registry: "registry:7861",
  5171  						Volume:   "/test",
  5172  						Tenant:   "",
  5173  					},
  5174  				},
  5175  			},
  5176  		}, {
  5177  			name: "too long tenant quobyte",
  5178  			vol: core.Volume{
  5179  				Name: "quobyte",
  5180  				VolumeSource: core.VolumeSource{
  5181  					Quobyte: &core.QuobyteVolumeSource{
  5182  						Registry: "registry:7861",
  5183  						Volume:   "/test",
  5184  						Tenant:   "this is too long to be a valid uuid so this test has to fail on the maximum length validation of the tenant.",
  5185  					},
  5186  				},
  5187  			},
  5188  			errs: []verr{{
  5189  				etype: field.ErrorTypeRequired,
  5190  				field: "quobyte.tenant",
  5191  			}},
  5192  		},
  5193  		// AzureDisk
  5194  		{
  5195  			name: "valid AzureDisk",
  5196  			vol: core.Volume{
  5197  				Name: "azure-disk",
  5198  				VolumeSource: core.VolumeSource{
  5199  					AzureDisk: &core.AzureDiskVolumeSource{
  5200  						DiskName:    "foo",
  5201  						DataDiskURI: "https://blob/vhds/bar.vhd",
  5202  					},
  5203  				},
  5204  			},
  5205  		}, {
  5206  			name: "AzureDisk empty disk name",
  5207  			vol: core.Volume{
  5208  				Name: "azure-disk",
  5209  				VolumeSource: core.VolumeSource{
  5210  					AzureDisk: &core.AzureDiskVolumeSource{
  5211  						DiskName:    "",
  5212  						DataDiskURI: "https://blob/vhds/bar.vhd",
  5213  					},
  5214  				},
  5215  			},
  5216  			errs: []verr{{
  5217  				etype: field.ErrorTypeRequired,
  5218  				field: "azureDisk.diskName",
  5219  			}},
  5220  		}, {
  5221  			name: "AzureDisk empty disk uri",
  5222  			vol: core.Volume{
  5223  				Name: "azure-disk",
  5224  				VolumeSource: core.VolumeSource{
  5225  					AzureDisk: &core.AzureDiskVolumeSource{
  5226  						DiskName:    "foo",
  5227  						DataDiskURI: "",
  5228  					},
  5229  				},
  5230  			},
  5231  			errs: []verr{{
  5232  				etype: field.ErrorTypeRequired,
  5233  				field: "azureDisk.diskURI",
  5234  			}},
  5235  		},
  5236  		// ScaleIO
  5237  		{
  5238  			name: "valid scaleio volume",
  5239  			vol: core.Volume{
  5240  				Name: "scaleio-volume",
  5241  				VolumeSource: core.VolumeSource{
  5242  					ScaleIO: &core.ScaleIOVolumeSource{
  5243  						Gateway:    "http://abcd/efg",
  5244  						System:     "test-system",
  5245  						VolumeName: "test-vol-1",
  5246  					},
  5247  				},
  5248  			},
  5249  		}, {
  5250  			name: "ScaleIO with empty name",
  5251  			vol: core.Volume{
  5252  				Name: "scaleio-volume",
  5253  				VolumeSource: core.VolumeSource{
  5254  					ScaleIO: &core.ScaleIOVolumeSource{
  5255  						Gateway:    "http://abcd/efg",
  5256  						System:     "test-system",
  5257  						VolumeName: "",
  5258  					},
  5259  				},
  5260  			},
  5261  			errs: []verr{{
  5262  				etype: field.ErrorTypeRequired,
  5263  				field: "scaleIO.volumeName",
  5264  			}},
  5265  		}, {
  5266  			name: "ScaleIO with empty gateway",
  5267  			vol: core.Volume{
  5268  				Name: "scaleio-volume",
  5269  				VolumeSource: core.VolumeSource{
  5270  					ScaleIO: &core.ScaleIOVolumeSource{
  5271  						Gateway:    "",
  5272  						System:     "test-system",
  5273  						VolumeName: "test-vol-1",
  5274  					},
  5275  				},
  5276  			},
  5277  			errs: []verr{{
  5278  				etype: field.ErrorTypeRequired,
  5279  				field: "scaleIO.gateway",
  5280  			}},
  5281  		}, {
  5282  			name: "ScaleIO with empty system",
  5283  			vol: core.Volume{
  5284  				Name: "scaleio-volume",
  5285  				VolumeSource: core.VolumeSource{
  5286  					ScaleIO: &core.ScaleIOVolumeSource{
  5287  						Gateway:    "http://agc/efg/gateway",
  5288  						System:     "",
  5289  						VolumeName: "test-vol-1",
  5290  					},
  5291  				},
  5292  			},
  5293  			errs: []verr{{
  5294  				etype: field.ErrorTypeRequired,
  5295  				field: "scaleIO.system",
  5296  			}},
  5297  		},
  5298  		// ProjectedVolumeSource
  5299  		{
  5300  			name: "ProjectedVolumeSource more than one projection in a source",
  5301  			vol: core.Volume{
  5302  				Name: "projected-volume",
  5303  				VolumeSource: core.VolumeSource{
  5304  					Projected: &core.ProjectedVolumeSource{
  5305  						Sources: []core.VolumeProjection{{
  5306  							Secret: &core.SecretProjection{
  5307  								LocalObjectReference: core.LocalObjectReference{
  5308  									Name: "foo",
  5309  								},
  5310  							},
  5311  						}, {
  5312  							Secret: &core.SecretProjection{
  5313  								LocalObjectReference: core.LocalObjectReference{
  5314  									Name: "foo",
  5315  								},
  5316  							},
  5317  							DownwardAPI: &core.DownwardAPIProjection{},
  5318  						}},
  5319  					},
  5320  				},
  5321  			},
  5322  			errs: []verr{{
  5323  				etype: field.ErrorTypeForbidden,
  5324  				field: "projected.sources[1]",
  5325  			}},
  5326  		}, {
  5327  			name: "ProjectedVolumeSource more than one projection in a source",
  5328  			vol: core.Volume{
  5329  				Name: "projected-volume",
  5330  				VolumeSource: core.VolumeSource{
  5331  					Projected: &core.ProjectedVolumeSource{
  5332  						Sources: []core.VolumeProjection{{
  5333  							Secret: &core.SecretProjection{},
  5334  						}, {
  5335  							Secret:      &core.SecretProjection{},
  5336  							DownwardAPI: &core.DownwardAPIProjection{},
  5337  						}},
  5338  					},
  5339  				},
  5340  			},
  5341  			errs: []verr{{
  5342  				etype: field.ErrorTypeRequired,
  5343  				field: "projected.sources[0].secret.name",
  5344  			}, {
  5345  				etype: field.ErrorTypeRequired,
  5346  				field: "projected.sources[1].secret.name",
  5347  			}, {
  5348  				etype: field.ErrorTypeForbidden,
  5349  				field: "projected.sources[1]",
  5350  			}},
  5351  		},
  5352  	}
  5353  
  5354  	for _, tc := range testCases {
  5355  		t.Run(tc.name, func(t *testing.T) {
  5356  			names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"), tc.opts)
  5357  			if len(errs) != len(tc.errs) {
  5358  				t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
  5359  			}
  5360  			if len(errs) == 0 && (len(names) > 1 || !IsMatchedVolume(tc.vol.Name, names)) {
  5361  				t.Errorf("wrong names result: %v", names)
  5362  			}
  5363  			for i, err := range errs {
  5364  				expErr := tc.errs[i]
  5365  				if err.Type != expErr.etype {
  5366  					t.Errorf("unexpected error type:\n\twant: %q\n\t got: %q", expErr.etype, err.Type)
  5367  				}
  5368  				if !strings.HasSuffix(err.Field, "."+expErr.field) {
  5369  					t.Errorf("unexpected error field:\n\twant: %q\n\t got: %q", expErr.field, err.Field)
  5370  				}
  5371  				if !strings.Contains(err.Detail, expErr.detail) {
  5372  					t.Errorf("unexpected error detail:\n\twant: %q\n\t got: %q", expErr.detail, err.Detail)
  5373  				}
  5374  			}
  5375  		})
  5376  	}
  5377  
  5378  	dupsCase := []core.Volume{
  5379  		{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  5380  		{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  5381  	}
  5382  	_, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"), PodValidationOptions{})
  5383  	if len(errs) == 0 {
  5384  		t.Errorf("expected error")
  5385  	} else if len(errs) != 1 {
  5386  		t.Errorf("expected 1 error, got %d: %v", len(errs), errs)
  5387  	} else if errs[0].Type != field.ErrorTypeDuplicate {
  5388  		t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
  5389  	}
  5390  
  5391  	// Validate HugePages medium type for EmptyDir
  5392  	hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
  5393  
  5394  	// Enable HugePages
  5395  	if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil, PodValidationOptions{}); len(errs) != 0 {
  5396  		t.Errorf("Unexpected error when HugePages feature is enabled.")
  5397  	}
  5398  
  5399  }
  5400  
  5401  func TestValidateReadOnlyPersistentDisks(t *testing.T) {
  5402  	cases := []struct {
  5403  		name        string
  5404  		volumes     []core.Volume
  5405  		oldVolume   []core.Volume
  5406  		gateValue   bool
  5407  		expectError bool
  5408  	}{{
  5409  		name:        "gate on, read-only disk, nil old",
  5410  		gateValue:   true,
  5411  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5412  		oldVolume:   []core.Volume(nil),
  5413  		expectError: false,
  5414  	}, {
  5415  		name:        "gate off, read-only disk, nil old",
  5416  		gateValue:   false,
  5417  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5418  		oldVolume:   []core.Volume(nil),
  5419  		expectError: false,
  5420  	}, {
  5421  		name:        "gate on, read-write, nil old",
  5422  		gateValue:   true,
  5423  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5424  		oldVolume:   []core.Volume(nil),
  5425  		expectError: false,
  5426  	}, {
  5427  		name:        "gate off, read-write, nil old",
  5428  		gateValue:   false,
  5429  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5430  		oldVolume:   []core.Volume(nil),
  5431  		expectError: true,
  5432  	}, {
  5433  		name:        "gate on, new read-only and old read-write",
  5434  		gateValue:   true,
  5435  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5436  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5437  		expectError: false,
  5438  	}, {
  5439  		name:        "gate off, new read-only and old read-write",
  5440  		gateValue:   false,
  5441  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5442  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5443  		expectError: false,
  5444  	}, {
  5445  		name:        "gate on, new read-write and old read-write",
  5446  		gateValue:   true,
  5447  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5448  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5449  		expectError: false,
  5450  	}, {
  5451  		name:        "gate off, new read-write and old read-write",
  5452  		gateValue:   false,
  5453  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5454  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5455  		expectError: false,
  5456  	}, {
  5457  		name:        "gate on, new read-only and old read-only",
  5458  		gateValue:   true,
  5459  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5460  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5461  		expectError: false,
  5462  	}, {
  5463  		name:        "gate off, new read-only and old read-only",
  5464  		gateValue:   false,
  5465  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5466  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5467  		expectError: false,
  5468  	}, {
  5469  		name:        "gate on, new read-write and old read-only",
  5470  		gateValue:   true,
  5471  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5472  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5473  		expectError: false,
  5474  	}, {
  5475  		name:        "gate off, new read-write and old read-only",
  5476  		gateValue:   false,
  5477  		volumes:     []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: false}}}},
  5478  		oldVolume:   []core.Volume{{VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{ReadOnly: true}}}},
  5479  		expectError: true,
  5480  	},
  5481  	}
  5482  	for _, testCase := range cases {
  5483  		t.Run(testCase.name, func(t *testing.T) {
  5484  			fidPath := field.NewPath("testField")
  5485  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, testCase.gateValue)()
  5486  			errs := ValidateReadOnlyPersistentDisks(testCase.volumes, testCase.oldVolume, fidPath)
  5487  			if !testCase.expectError && len(errs) != 0 {
  5488  				t.Errorf("expected success, got:%v", errs)
  5489  			}
  5490  		})
  5491  	}
  5492  }
  5493  
  5494  func TestHugePagesIsolation(t *testing.T) {
  5495  	testCases := map[string]struct {
  5496  		pod         *core.Pod
  5497  		expectError bool
  5498  	}{
  5499  		"Valid: request hugepages-2Mi": {
  5500  			pod: &core.Pod{
  5501  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
  5502  				Spec: core.PodSpec{
  5503  					Containers: []core.Container{{
  5504  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5505  						Resources: core.ResourceRequirements{
  5506  							Requests: core.ResourceList{
  5507  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5508  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5509  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5510  							},
  5511  							Limits: core.ResourceList{
  5512  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5513  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5514  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5515  							},
  5516  						},
  5517  					}},
  5518  					RestartPolicy: core.RestartPolicyAlways,
  5519  					DNSPolicy:     core.DNSClusterFirst,
  5520  				},
  5521  			},
  5522  		},
  5523  		"Valid: request more than one hugepages size": {
  5524  			pod: &core.Pod{
  5525  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5526  				Spec: core.PodSpec{
  5527  					Containers: []core.Container{{
  5528  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5529  						Resources: core.ResourceRequirements{
  5530  							Requests: core.ResourceList{
  5531  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5532  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5533  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5534  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5535  							},
  5536  							Limits: core.ResourceList{
  5537  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5538  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5539  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5540  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5541  							},
  5542  						},
  5543  					}},
  5544  					RestartPolicy: core.RestartPolicyAlways,
  5545  					DNSPolicy:     core.DNSClusterFirst,
  5546  				},
  5547  			},
  5548  			expectError: false,
  5549  		},
  5550  		"Valid: request hugepages-1Gi, limit hugepages-2Mi and hugepages-1Gi": {
  5551  			pod: &core.Pod{
  5552  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
  5553  				Spec: core.PodSpec{
  5554  					Containers: []core.Container{{
  5555  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5556  						Resources: core.ResourceRequirements{
  5557  							Requests: core.ResourceList{
  5558  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5559  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5560  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5561  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5562  							},
  5563  							Limits: core.ResourceList{
  5564  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5565  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5566  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5567  								core.ResourceName("hugepages-1Gi"):     resource.MustParse("2Gi"),
  5568  							},
  5569  						},
  5570  					}},
  5571  					RestartPolicy: core.RestartPolicyAlways,
  5572  					DNSPolicy:     core.DNSClusterFirst,
  5573  				},
  5574  			},
  5575  		},
  5576  		"Invalid: not requesting cpu and memory": {
  5577  			pod: &core.Pod{
  5578  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
  5579  				Spec: core.PodSpec{
  5580  					Containers: []core.Container{{
  5581  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5582  						Resources: core.ResourceRequirements{
  5583  							Requests: core.ResourceList{
  5584  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5585  							},
  5586  							Limits: core.ResourceList{
  5587  								core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
  5588  							},
  5589  						},
  5590  					}},
  5591  					RestartPolicy: core.RestartPolicyAlways,
  5592  					DNSPolicy:     core.DNSClusterFirst,
  5593  				},
  5594  			},
  5595  			expectError: true,
  5596  		},
  5597  		"Invalid: request 1Gi hugepages-2Mi but limit 2Gi": {
  5598  			pod: &core.Pod{
  5599  				ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
  5600  				Spec: core.PodSpec{
  5601  					Containers: []core.Container{{
  5602  						Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  5603  						Resources: core.ResourceRequirements{
  5604  							Requests: core.ResourceList{
  5605  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5606  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5607  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("1Gi"),
  5608  							},
  5609  							Limits: core.ResourceList{
  5610  								core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  5611  								core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  5612  								core.ResourceName("hugepages-2Mi"):     resource.MustParse("2Gi"),
  5613  							},
  5614  						},
  5615  					}},
  5616  					RestartPolicy: core.RestartPolicyAlways,
  5617  					DNSPolicy:     core.DNSClusterFirst,
  5618  				},
  5619  			},
  5620  			expectError: true,
  5621  		},
  5622  	}
  5623  	for tcName, tc := range testCases {
  5624  		t.Run(tcName, func(t *testing.T) {
  5625  			errs := ValidatePodCreate(tc.pod, PodValidationOptions{})
  5626  			if tc.expectError && len(errs) == 0 {
  5627  				t.Errorf("Unexpected success")
  5628  			}
  5629  			if !tc.expectError && len(errs) != 0 {
  5630  				t.Errorf("Unexpected error(s): %v", errs)
  5631  			}
  5632  		})
  5633  	}
  5634  }
  5635  
  5636  func TestPVCVolumeMode(t *testing.T) {
  5637  	block := core.PersistentVolumeBlock
  5638  	file := core.PersistentVolumeFilesystem
  5639  	fake := core.PersistentVolumeMode("fake")
  5640  	empty := core.PersistentVolumeMode("")
  5641  
  5642  	// Success Cases
  5643  	successCasesPVC := map[string]*core.PersistentVolumeClaim{
  5644  		"valid block value":      createTestVolModePVC(&block),
  5645  		"valid filesystem value": createTestVolModePVC(&file),
  5646  		"valid nil value":        createTestVolModePVC(nil),
  5647  	}
  5648  	for k, v := range successCasesPVC {
  5649  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5650  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) != 0 {
  5651  			t.Errorf("expected success for %s", k)
  5652  		}
  5653  	}
  5654  
  5655  	// Error Cases
  5656  	errorCasesPVC := map[string]*core.PersistentVolumeClaim{
  5657  		"invalid value": createTestVolModePVC(&fake),
  5658  		"empty value":   createTestVolModePVC(&empty),
  5659  	}
  5660  	for k, v := range errorCasesPVC {
  5661  		opts := ValidationOptionsForPersistentVolumeClaim(v, nil)
  5662  		if errs := ValidatePersistentVolumeClaim(v, opts); len(errs) == 0 {
  5663  			t.Errorf("expected failure for %s", k)
  5664  		}
  5665  	}
  5666  }
  5667  
  5668  func TestPVVolumeMode(t *testing.T) {
  5669  	block := core.PersistentVolumeBlock
  5670  	file := core.PersistentVolumeFilesystem
  5671  	fake := core.PersistentVolumeMode("fake")
  5672  	empty := core.PersistentVolumeMode("")
  5673  
  5674  	// Success Cases
  5675  	successCasesPV := map[string]*core.PersistentVolume{
  5676  		"valid block value":      createTestVolModePV(&block),
  5677  		"valid filesystem value": createTestVolModePV(&file),
  5678  		"valid nil value":        createTestVolModePV(nil),
  5679  	}
  5680  	for k, v := range successCasesPV {
  5681  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5682  		if errs := ValidatePersistentVolume(v, opts); len(errs) != 0 {
  5683  			t.Errorf("expected success for %s", k)
  5684  		}
  5685  	}
  5686  
  5687  	// Error Cases
  5688  	errorCasesPV := map[string]*core.PersistentVolume{
  5689  		"invalid value": createTestVolModePV(&fake),
  5690  		"empty value":   createTestVolModePV(&empty),
  5691  	}
  5692  	for k, v := range errorCasesPV {
  5693  		opts := ValidationOptionsForPersistentVolume(v, nil)
  5694  		if errs := ValidatePersistentVolume(v, opts); len(errs) == 0 {
  5695  			t.Errorf("expected failure for %s", k)
  5696  		}
  5697  	}
  5698  }
  5699  
  5700  func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
  5701  	validName := "valid-storage-class"
  5702  
  5703  	pvc := core.PersistentVolumeClaim{
  5704  		ObjectMeta: metav1.ObjectMeta{
  5705  			Name:      "foo",
  5706  			Namespace: "default",
  5707  		},
  5708  		Spec: core.PersistentVolumeClaimSpec{
  5709  			Resources: core.VolumeResourceRequirements{
  5710  				Requests: core.ResourceList{
  5711  					core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5712  				},
  5713  			},
  5714  			AccessModes:      []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5715  			StorageClassName: &validName,
  5716  			VolumeMode:       vmode,
  5717  		},
  5718  	}
  5719  	return &pvc
  5720  }
  5721  
  5722  func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
  5723  
  5724  	// PersistentVolume with VolumeMode set (valid and invalid)
  5725  	pv := core.PersistentVolume{
  5726  		ObjectMeta: metav1.ObjectMeta{
  5727  			Name:      "foo",
  5728  			Namespace: "",
  5729  		},
  5730  		Spec: core.PersistentVolumeSpec{
  5731  			Capacity: core.ResourceList{
  5732  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5733  			},
  5734  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5735  			PersistentVolumeSource: core.PersistentVolumeSource{
  5736  				HostPath: &core.HostPathVolumeSource{
  5737  					Path: "/foo",
  5738  					Type: newHostPathType(string(core.HostPathDirectory)),
  5739  				},
  5740  			},
  5741  			StorageClassName: "test-storage-class",
  5742  			VolumeMode:       vmode,
  5743  		},
  5744  	}
  5745  	return &pv
  5746  }
  5747  
  5748  func createTestPV() *core.PersistentVolume {
  5749  
  5750  	// PersistentVolume with VolumeMode set (valid and invalid)
  5751  	pv := core.PersistentVolume{
  5752  		ObjectMeta: metav1.ObjectMeta{
  5753  			Name:      "foo",
  5754  			Namespace: "",
  5755  		},
  5756  		Spec: core.PersistentVolumeSpec{
  5757  			Capacity: core.ResourceList{
  5758  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  5759  			},
  5760  			AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
  5761  			PersistentVolumeSource: core.PersistentVolumeSource{
  5762  				HostPath: &core.HostPathVolumeSource{
  5763  					Path: "/foo",
  5764  					Type: newHostPathType(string(core.HostPathDirectory)),
  5765  				},
  5766  			},
  5767  			StorageClassName: "test-storage-class",
  5768  		},
  5769  	}
  5770  	return &pv
  5771  }
  5772  
  5773  func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5774  
  5775  	testCases := []core.VolumeSource{
  5776  		{EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}},
  5777  	}
  5778  
  5779  	for _, tc := range testCases {
  5780  		if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil, PodValidationOptions{}); len(errs) != 0 {
  5781  			t.Errorf("expected success: %v", errs)
  5782  		}
  5783  	}
  5784  
  5785  	containerLimitCase := core.ResourceRequirements{
  5786  		Limits: core.ResourceList{
  5787  			core.ResourceEphemeralStorage: *resource.NewMilliQuantity(
  5788  				int64(40000),
  5789  				resource.BinarySI),
  5790  		},
  5791  	}
  5792  	if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
  5793  		t.Errorf("expected success: %v", errs)
  5794  	}
  5795  }
  5796  
  5797  func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) {
  5798  	spec := core.ResourceQuotaSpec{
  5799  		Hard: core.ResourceList{
  5800  			core.ResourceCPU:                      resource.MustParse("100"),
  5801  			core.ResourceMemory:                   resource.MustParse("10000"),
  5802  			core.ResourceRequestsCPU:              resource.MustParse("100"),
  5803  			core.ResourceRequestsMemory:           resource.MustParse("10000"),
  5804  			core.ResourceLimitsCPU:                resource.MustParse("100"),
  5805  			core.ResourceLimitsMemory:             resource.MustParse("10000"),
  5806  			core.ResourcePods:                     resource.MustParse("10"),
  5807  			core.ResourceServices:                 resource.MustParse("0"),
  5808  			core.ResourceReplicationControllers:   resource.MustParse("10"),
  5809  			core.ResourceQuotas:                   resource.MustParse("10"),
  5810  			core.ResourceConfigMaps:               resource.MustParse("10"),
  5811  			core.ResourceSecrets:                  resource.MustParse("10"),
  5812  			core.ResourceEphemeralStorage:         resource.MustParse("10000"),
  5813  			core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"),
  5814  			core.ResourceLimitsEphemeralStorage:   resource.MustParse("10000"),
  5815  		},
  5816  	}
  5817  	resourceQuota := &core.ResourceQuota{
  5818  		ObjectMeta: metav1.ObjectMeta{
  5819  			Name:      "abc",
  5820  			Namespace: "foo",
  5821  		},
  5822  		Spec: spec,
  5823  	}
  5824  
  5825  	if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
  5826  		t.Errorf("expected success: %v", errs)
  5827  	}
  5828  }
  5829  
  5830  func TestValidatePorts(t *testing.T) {
  5831  	successCase := []core.ContainerPort{
  5832  		{Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  5833  		{Name: "easy", ContainerPort: 82, Protocol: "TCP"},
  5834  		{Name: "as", ContainerPort: 83, Protocol: "UDP"},
  5835  		{Name: "do-re-me", ContainerPort: 84, Protocol: "SCTP"},
  5836  		{ContainerPort: 85, Protocol: "TCP"},
  5837  	}
  5838  	if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 {
  5839  		t.Errorf("expected success: %v", errs)
  5840  	}
  5841  
  5842  	nonCanonicalCase := []core.ContainerPort{
  5843  		{ContainerPort: 80, Protocol: "TCP"},
  5844  	}
  5845  	if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 {
  5846  		t.Errorf("expected success: %v", errs)
  5847  	}
  5848  
  5849  	errorCases := map[string]struct {
  5850  		P []core.ContainerPort
  5851  		T field.ErrorType
  5852  		F string
  5853  		D string
  5854  	}{
  5855  		"name > 15 characters": {
  5856  			[]core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
  5857  			field.ErrorTypeInvalid,
  5858  			"name", "15",
  5859  		},
  5860  		"name contains invalid characters": {
  5861  			[]core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
  5862  			field.ErrorTypeInvalid,
  5863  			"name", "alpha-numeric",
  5864  		},
  5865  		"name is a number": {
  5866  			[]core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
  5867  			field.ErrorTypeInvalid,
  5868  			"name", "at least one letter",
  5869  		},
  5870  		"name not unique": {
  5871  			[]core.ContainerPort{
  5872  				{Name: "abc", ContainerPort: 80, Protocol: "TCP"},
  5873  				{Name: "abc", ContainerPort: 81, Protocol: "TCP"},
  5874  			},
  5875  			field.ErrorTypeDuplicate,
  5876  			"[1].name", "",
  5877  		},
  5878  		"zero container port": {
  5879  			[]core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
  5880  			field.ErrorTypeRequired,
  5881  			"containerPort", "",
  5882  		},
  5883  		"invalid container port": {
  5884  			[]core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
  5885  			field.ErrorTypeInvalid,
  5886  			"containerPort", "between",
  5887  		},
  5888  		"invalid host port": {
  5889  			[]core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
  5890  			field.ErrorTypeInvalid,
  5891  			"hostPort", "between",
  5892  		},
  5893  		"invalid protocol case": {
  5894  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
  5895  			field.ErrorTypeNotSupported,
  5896  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5897  		},
  5898  		"invalid protocol": {
  5899  			[]core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
  5900  			field.ErrorTypeNotSupported,
  5901  			"protocol", `supported values: "SCTP", "TCP", "UDP"`,
  5902  		},
  5903  		"protocol required": {
  5904  			[]core.ContainerPort{{Name: "abc", ContainerPort: 80}},
  5905  			field.ErrorTypeRequired,
  5906  			"protocol", "",
  5907  		},
  5908  	}
  5909  	for k, v := range errorCases {
  5910  		errs := validateContainerPorts(v.P, field.NewPath("field"))
  5911  		if len(errs) == 0 {
  5912  			t.Errorf("expected failure for %s", k)
  5913  		}
  5914  		for i := range errs {
  5915  			if errs[i].Type != v.T {
  5916  				t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
  5917  			}
  5918  			if !strings.Contains(errs[i].Field, v.F) {
  5919  				t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
  5920  			}
  5921  			if !strings.Contains(errs[i].Detail, v.D) {
  5922  				t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
  5923  			}
  5924  		}
  5925  	}
  5926  }
  5927  
  5928  func TestLocalStorageEnvWithFeatureGate(t *testing.T) {
  5929  	testCases := []core.EnvVar{{
  5930  		Name: "ephemeral-storage-limits",
  5931  		ValueFrom: &core.EnvVarSource{
  5932  			ResourceFieldRef: &core.ResourceFieldSelector{
  5933  				ContainerName: "test-container",
  5934  				Resource:      "limits.ephemeral-storage",
  5935  			},
  5936  		},
  5937  	}, {
  5938  		Name: "ephemeral-storage-requests",
  5939  		ValueFrom: &core.EnvVarSource{
  5940  			ResourceFieldRef: &core.ResourceFieldSelector{
  5941  				ContainerName: "test-container",
  5942  				Resource:      "requests.ephemeral-storage",
  5943  			},
  5944  		},
  5945  	},
  5946  	}
  5947  	for _, testCase := range testCases {
  5948  		if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  5949  			t.Errorf("expected success, got: %v", errs)
  5950  		}
  5951  	}
  5952  }
  5953  
  5954  func TestHugePagesEnv(t *testing.T) {
  5955  	testCases := []core.EnvVar{{
  5956  		Name: "hugepages-limits",
  5957  		ValueFrom: &core.EnvVarSource{
  5958  			ResourceFieldRef: &core.ResourceFieldSelector{
  5959  				ContainerName: "test-container",
  5960  				Resource:      "limits.hugepages-2Mi",
  5961  			},
  5962  		},
  5963  	}, {
  5964  		Name: "hugepages-requests",
  5965  		ValueFrom: &core.EnvVarSource{
  5966  			ResourceFieldRef: &core.ResourceFieldSelector{
  5967  				ContainerName: "test-container",
  5968  				Resource:      "requests.hugepages-2Mi",
  5969  			},
  5970  		},
  5971  	},
  5972  	}
  5973  	// enable gate
  5974  	for _, testCase := range testCases {
  5975  		t.Run(testCase.Name, func(t *testing.T) {
  5976  			opts := PodValidationOptions{}
  5977  			if errs := validateEnvVarValueFrom(testCase, field.NewPath("field"), opts); len(errs) != 0 {
  5978  				t.Errorf("expected success, got: %v", errs)
  5979  			}
  5980  		})
  5981  	}
  5982  }
  5983  
  5984  func TestValidateEnv(t *testing.T) {
  5985  	successCase := []core.EnvVar{
  5986  		{Name: "abc", Value: "value"},
  5987  		{Name: "ABC", Value: "value"},
  5988  		{Name: "AbC_123", Value: "value"},
  5989  		{Name: "abc", Value: ""},
  5990  		{Name: "a.b.c", Value: "value"},
  5991  		{Name: "a-b-c", Value: "value"}, {
  5992  			Name: "abc",
  5993  			ValueFrom: &core.EnvVarSource{
  5994  				FieldRef: &core.ObjectFieldSelector{
  5995  					APIVersion: "v1",
  5996  					FieldPath:  "metadata.annotations['key']",
  5997  				},
  5998  			},
  5999  		}, {
  6000  			Name: "abc",
  6001  			ValueFrom: &core.EnvVarSource{
  6002  				FieldRef: &core.ObjectFieldSelector{
  6003  					APIVersion: "v1",
  6004  					FieldPath:  "metadata.labels['key']",
  6005  				},
  6006  			},
  6007  		}, {
  6008  			Name: "abc",
  6009  			ValueFrom: &core.EnvVarSource{
  6010  				FieldRef: &core.ObjectFieldSelector{
  6011  					APIVersion: "v1",
  6012  					FieldPath:  "metadata.name",
  6013  				},
  6014  			},
  6015  		}, {
  6016  			Name: "abc",
  6017  			ValueFrom: &core.EnvVarSource{
  6018  				FieldRef: &core.ObjectFieldSelector{
  6019  					APIVersion: "v1",
  6020  					FieldPath:  "metadata.namespace",
  6021  				},
  6022  			},
  6023  		}, {
  6024  			Name: "abc",
  6025  			ValueFrom: &core.EnvVarSource{
  6026  				FieldRef: &core.ObjectFieldSelector{
  6027  					APIVersion: "v1",
  6028  					FieldPath:  "metadata.uid",
  6029  				},
  6030  			},
  6031  		}, {
  6032  			Name: "abc",
  6033  			ValueFrom: &core.EnvVarSource{
  6034  				FieldRef: &core.ObjectFieldSelector{
  6035  					APIVersion: "v1",
  6036  					FieldPath:  "spec.nodeName",
  6037  				},
  6038  			},
  6039  		}, {
  6040  			Name: "abc",
  6041  			ValueFrom: &core.EnvVarSource{
  6042  				FieldRef: &core.ObjectFieldSelector{
  6043  					APIVersion: "v1",
  6044  					FieldPath:  "spec.serviceAccountName",
  6045  				},
  6046  			},
  6047  		}, {
  6048  			Name: "abc",
  6049  			ValueFrom: &core.EnvVarSource{
  6050  				FieldRef: &core.ObjectFieldSelector{
  6051  					APIVersion: "v1",
  6052  					FieldPath:  "status.hostIP",
  6053  				},
  6054  			},
  6055  		}, {
  6056  			Name: "abc",
  6057  			ValueFrom: &core.EnvVarSource{
  6058  				FieldRef: &core.ObjectFieldSelector{
  6059  					APIVersion: "v1",
  6060  					FieldPath:  "status.podIP",
  6061  				},
  6062  			},
  6063  		}, {
  6064  			Name: "abc",
  6065  			ValueFrom: &core.EnvVarSource{
  6066  				FieldRef: &core.ObjectFieldSelector{
  6067  					APIVersion: "v1",
  6068  					FieldPath:  "status.podIPs",
  6069  				},
  6070  			},
  6071  		}, {
  6072  			Name: "secret_value",
  6073  			ValueFrom: &core.EnvVarSource{
  6074  				SecretKeyRef: &core.SecretKeySelector{
  6075  					LocalObjectReference: core.LocalObjectReference{
  6076  						Name: "some-secret",
  6077  					},
  6078  					Key: "secret-key",
  6079  				},
  6080  			},
  6081  		}, {
  6082  			Name: "ENV_VAR_1",
  6083  			ValueFrom: &core.EnvVarSource{
  6084  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6085  					LocalObjectReference: core.LocalObjectReference{
  6086  						Name: "some-config-map",
  6087  					},
  6088  					Key: "some-key",
  6089  				},
  6090  			},
  6091  		},
  6092  	}
  6093  	if errs := ValidateEnv(successCase, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
  6094  		t.Errorf("expected success, got: %v", errs)
  6095  	}
  6096  
  6097  	errorCases := []struct {
  6098  		name          string
  6099  		envs          []core.EnvVar
  6100  		expectedError string
  6101  	}{{
  6102  		name:          "zero-length name",
  6103  		envs:          []core.EnvVar{{Name: ""}},
  6104  		expectedError: "[0].name: Required value",
  6105  	}, {
  6106  		name:          "illegal character",
  6107  		envs:          []core.EnvVar{{Name: "a!b"}},
  6108  		expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg,
  6109  	}, {
  6110  		name:          "dot only",
  6111  		envs:          []core.EnvVar{{Name: "."}},
  6112  		expectedError: `[0].name: Invalid value: ".": must not be`,
  6113  	}, {
  6114  		name:          "double dots only",
  6115  		envs:          []core.EnvVar{{Name: ".."}},
  6116  		expectedError: `[0].name: Invalid value: "..": must not be`,
  6117  	}, {
  6118  		name:          "leading double dots",
  6119  		envs:          []core.EnvVar{{Name: "..abc"}},
  6120  		expectedError: `[0].name: Invalid value: "..abc": must not start with`,
  6121  	}, {
  6122  		name: "value and valueFrom specified",
  6123  		envs: []core.EnvVar{{
  6124  			Name:  "abc",
  6125  			Value: "foo",
  6126  			ValueFrom: &core.EnvVarSource{
  6127  				FieldRef: &core.ObjectFieldSelector{
  6128  					APIVersion: "v1",
  6129  					FieldPath:  "metadata.name",
  6130  				},
  6131  			},
  6132  		}},
  6133  		expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
  6134  	}, {
  6135  		name: "valueFrom without a source",
  6136  		envs: []core.EnvVar{{
  6137  			Name:      "abc",
  6138  			ValueFrom: &core.EnvVarSource{},
  6139  		}},
  6140  		expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
  6141  	}, {
  6142  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6143  		envs: []core.EnvVar{{
  6144  			Name: "abc",
  6145  			ValueFrom: &core.EnvVarSource{
  6146  				FieldRef: &core.ObjectFieldSelector{
  6147  					APIVersion: "v1",
  6148  					FieldPath:  "metadata.name",
  6149  				},
  6150  				SecretKeyRef: &core.SecretKeySelector{
  6151  					LocalObjectReference: core.LocalObjectReference{
  6152  						Name: "a-secret",
  6153  					},
  6154  					Key: "a-key",
  6155  				},
  6156  			},
  6157  		}},
  6158  		expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
  6159  	}, {
  6160  		name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
  6161  		envs: []core.EnvVar{{
  6162  			Name: "some_var_name",
  6163  			ValueFrom: &core.EnvVarSource{
  6164  				FieldRef: &core.ObjectFieldSelector{
  6165  					APIVersion: "v1",
  6166  					FieldPath:  "metadata.name",
  6167  				},
  6168  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6169  					LocalObjectReference: core.LocalObjectReference{
  6170  						Name: "some-config-map",
  6171  					},
  6172  					Key: "some-key",
  6173  				},
  6174  			},
  6175  		}},
  6176  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6177  	}, {
  6178  		name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
  6179  		envs: []core.EnvVar{{
  6180  			Name: "abc",
  6181  			ValueFrom: &core.EnvVarSource{
  6182  				FieldRef: &core.ObjectFieldSelector{
  6183  					APIVersion: "v1",
  6184  					FieldPath:  "metadata.name",
  6185  				},
  6186  				SecretKeyRef: &core.SecretKeySelector{
  6187  					LocalObjectReference: core.LocalObjectReference{
  6188  						Name: "a-secret",
  6189  					},
  6190  					Key: "a-key",
  6191  				},
  6192  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6193  					LocalObjectReference: core.LocalObjectReference{
  6194  						Name: "some-config-map",
  6195  					},
  6196  					Key: "some-key",
  6197  				},
  6198  			},
  6199  		}},
  6200  		expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
  6201  	}, {
  6202  		name: "valueFrom.secretKeyRef.name invalid",
  6203  		envs: []core.EnvVar{{
  6204  			Name: "abc",
  6205  			ValueFrom: &core.EnvVarSource{
  6206  				SecretKeyRef: &core.SecretKeySelector{
  6207  					LocalObjectReference: core.LocalObjectReference{
  6208  						Name: "$%^&*#",
  6209  					},
  6210  					Key: "a-key",
  6211  				},
  6212  			},
  6213  		}},
  6214  	}, {
  6215  		name: "valueFrom.configMapKeyRef.name invalid",
  6216  		envs: []core.EnvVar{{
  6217  			Name: "abc",
  6218  			ValueFrom: &core.EnvVarSource{
  6219  				ConfigMapKeyRef: &core.ConfigMapKeySelector{
  6220  					LocalObjectReference: core.LocalObjectReference{
  6221  						Name: "$%^&*#",
  6222  					},
  6223  					Key: "some-key",
  6224  				},
  6225  			},
  6226  		}},
  6227  	}, {
  6228  		name: "missing FieldPath on ObjectFieldSelector",
  6229  		envs: []core.EnvVar{{
  6230  			Name: "abc",
  6231  			ValueFrom: &core.EnvVarSource{
  6232  				FieldRef: &core.ObjectFieldSelector{
  6233  					APIVersion: "v1",
  6234  				},
  6235  			},
  6236  		}},
  6237  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
  6238  	}, {
  6239  		name: "missing APIVersion on ObjectFieldSelector",
  6240  		envs: []core.EnvVar{{
  6241  			Name: "abc",
  6242  			ValueFrom: &core.EnvVarSource{
  6243  				FieldRef: &core.ObjectFieldSelector{
  6244  					FieldPath: "metadata.name",
  6245  				},
  6246  			},
  6247  		}},
  6248  		expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
  6249  	}, {
  6250  		name: "invalid fieldPath",
  6251  		envs: []core.EnvVar{{
  6252  			Name: "abc",
  6253  			ValueFrom: &core.EnvVarSource{
  6254  				FieldRef: &core.ObjectFieldSelector{
  6255  					FieldPath:  "metadata.whoops",
  6256  					APIVersion: "v1",
  6257  				},
  6258  			},
  6259  		}},
  6260  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
  6261  	}, {
  6262  		name: "metadata.name with subscript",
  6263  		envs: []core.EnvVar{{
  6264  			Name: "labels",
  6265  			ValueFrom: &core.EnvVarSource{
  6266  				FieldRef: &core.ObjectFieldSelector{
  6267  					FieldPath:  "metadata.name['key']",
  6268  					APIVersion: "v1",
  6269  				},
  6270  			},
  6271  		}},
  6272  		expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
  6273  	}, {
  6274  		name: "metadata.labels without subscript",
  6275  		envs: []core.EnvVar{{
  6276  			Name: "labels",
  6277  			ValueFrom: &core.EnvVarSource{
  6278  				FieldRef: &core.ObjectFieldSelector{
  6279  					FieldPath:  "metadata.labels",
  6280  					APIVersion: "v1",
  6281  				},
  6282  			},
  6283  		}},
  6284  		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"`,
  6285  	}, {
  6286  		name: "metadata.annotations without subscript",
  6287  		envs: []core.EnvVar{{
  6288  			Name: "abc",
  6289  			ValueFrom: &core.EnvVarSource{
  6290  				FieldRef: &core.ObjectFieldSelector{
  6291  					FieldPath:  "metadata.annotations",
  6292  					APIVersion: "v1",
  6293  				},
  6294  			},
  6295  		}},
  6296  		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"`,
  6297  	}, {
  6298  		name: "metadata.annotations with invalid key",
  6299  		envs: []core.EnvVar{{
  6300  			Name: "abc",
  6301  			ValueFrom: &core.EnvVarSource{
  6302  				FieldRef: &core.ObjectFieldSelector{
  6303  					FieldPath:  "metadata.annotations['invalid~key']",
  6304  					APIVersion: "v1",
  6305  				},
  6306  			},
  6307  		}},
  6308  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
  6309  	}, {
  6310  		name: "metadata.labels with invalid key",
  6311  		envs: []core.EnvVar{{
  6312  			Name: "abc",
  6313  			ValueFrom: &core.EnvVarSource{
  6314  				FieldRef: &core.ObjectFieldSelector{
  6315  					FieldPath:  "metadata.labels['Www.k8s.io/test']",
  6316  					APIVersion: "v1",
  6317  				},
  6318  			},
  6319  		}},
  6320  		expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
  6321  	}, {
  6322  		name: "unsupported fieldPath",
  6323  		envs: []core.EnvVar{{
  6324  			Name: "abc",
  6325  			ValueFrom: &core.EnvVarSource{
  6326  				FieldRef: &core.ObjectFieldSelector{
  6327  					FieldPath:  "status.phase",
  6328  					APIVersion: "v1",
  6329  				},
  6330  			},
  6331  		}},
  6332  		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"`,
  6333  	},
  6334  	}
  6335  	for _, tc := range errorCases {
  6336  		if errs := ValidateEnv(tc.envs, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
  6337  			t.Errorf("expected failure for %s", tc.name)
  6338  		} else {
  6339  			for i := range errs {
  6340  				str := errs[i].Error()
  6341  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6342  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6343  				}
  6344  			}
  6345  		}
  6346  	}
  6347  }
  6348  
  6349  func TestValidateEnvFrom(t *testing.T) {
  6350  	successCase := []core.EnvFromSource{{
  6351  		ConfigMapRef: &core.ConfigMapEnvSource{
  6352  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6353  		},
  6354  	}, {
  6355  		Prefix: "pre_",
  6356  		ConfigMapRef: &core.ConfigMapEnvSource{
  6357  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6358  		},
  6359  	}, {
  6360  		Prefix: "a.b",
  6361  		ConfigMapRef: &core.ConfigMapEnvSource{
  6362  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6363  		},
  6364  	}, {
  6365  		SecretRef: &core.SecretEnvSource{
  6366  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6367  		},
  6368  	}, {
  6369  		Prefix: "pre_",
  6370  		SecretRef: &core.SecretEnvSource{
  6371  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6372  		},
  6373  	}, {
  6374  		Prefix: "a.b",
  6375  		SecretRef: &core.SecretEnvSource{
  6376  			LocalObjectReference: core.LocalObjectReference{Name: "abc"},
  6377  		},
  6378  	},
  6379  	}
  6380  	if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 {
  6381  		t.Errorf("expected success: %v", errs)
  6382  	}
  6383  
  6384  	errorCases := []struct {
  6385  		name          string
  6386  		envs          []core.EnvFromSource
  6387  		expectedError string
  6388  	}{{
  6389  		name: "zero-length name",
  6390  		envs: []core.EnvFromSource{{
  6391  			ConfigMapRef: &core.ConfigMapEnvSource{
  6392  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6393  		}},
  6394  		expectedError: "field[0].configMapRef.name: Required value",
  6395  	}, {
  6396  		name: "invalid name",
  6397  		envs: []core.EnvFromSource{{
  6398  			ConfigMapRef: &core.ConfigMapEnvSource{
  6399  				LocalObjectReference: core.LocalObjectReference{Name: "$"}},
  6400  		}},
  6401  		expectedError: "field[0].configMapRef.name: Invalid value",
  6402  	}, {
  6403  		name: "invalid prefix",
  6404  		envs: []core.EnvFromSource{{
  6405  			Prefix: "a!b",
  6406  			ConfigMapRef: &core.ConfigMapEnvSource{
  6407  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6408  		}},
  6409  		expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg,
  6410  	}, {
  6411  		name: "zero-length name",
  6412  		envs: []core.EnvFromSource{{
  6413  			SecretRef: &core.SecretEnvSource{
  6414  				LocalObjectReference: core.LocalObjectReference{Name: ""}},
  6415  		}},
  6416  		expectedError: "field[0].secretRef.name: Required value",
  6417  	}, {
  6418  		name: "invalid name",
  6419  		envs: []core.EnvFromSource{{
  6420  			SecretRef: &core.SecretEnvSource{
  6421  				LocalObjectReference: core.LocalObjectReference{Name: "&"}},
  6422  		}},
  6423  		expectedError: "field[0].secretRef.name: Invalid value",
  6424  	}, {
  6425  		name: "invalid prefix",
  6426  		envs: []core.EnvFromSource{{
  6427  			Prefix: "a!b",
  6428  			SecretRef: &core.SecretEnvSource{
  6429  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6430  		}},
  6431  		expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg,
  6432  	}, {
  6433  		name: "no refs",
  6434  		envs: []core.EnvFromSource{
  6435  			{},
  6436  		},
  6437  		expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
  6438  	}, {
  6439  		name: "multiple refs",
  6440  		envs: []core.EnvFromSource{{
  6441  			SecretRef: &core.SecretEnvSource{
  6442  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6443  			ConfigMapRef: &core.ConfigMapEnvSource{
  6444  				LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
  6445  		}},
  6446  		expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
  6447  	}, {
  6448  		name: "invalid secret ref name",
  6449  		envs: []core.EnvFromSource{{
  6450  			SecretRef: &core.SecretEnvSource{
  6451  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6452  		}},
  6453  		expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6454  	}, {
  6455  		name: "invalid config ref name",
  6456  		envs: []core.EnvFromSource{{
  6457  			ConfigMapRef: &core.ConfigMapEnvSource{
  6458  				LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
  6459  		}},
  6460  		expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
  6461  	},
  6462  	}
  6463  	for _, tc := range errorCases {
  6464  		if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 {
  6465  			t.Errorf("expected failure for %s", tc.name)
  6466  		} else {
  6467  			for i := range errs {
  6468  				str := errs[i].Error()
  6469  				if str != "" && !strings.Contains(str, tc.expectedError) {
  6470  					t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
  6471  				}
  6472  			}
  6473  		}
  6474  	}
  6475  }
  6476  
  6477  func TestValidateVolumeMounts(t *testing.T) {
  6478  	volumes := []core.Volume{
  6479  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  6480  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  6481  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6482  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  6483  			Spec: core.PersistentVolumeClaimSpec{
  6484  				AccessModes: []core.PersistentVolumeAccessMode{
  6485  					core.ReadWriteOnce,
  6486  				},
  6487  				Resources: core.VolumeResourceRequirements{
  6488  					Requests: core.ResourceList{
  6489  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  6490  					},
  6491  				},
  6492  			},
  6493  		}}}},
  6494  	}
  6495  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  6496  	if len(v1err) > 0 {
  6497  		t.Errorf("Invalid test volume - expected success %v", v1err)
  6498  		return
  6499  	}
  6500  	container := core.Container{
  6501  		SecurityContext: nil,
  6502  	}
  6503  	propagation := core.MountPropagationBidirectional
  6504  
  6505  	successCase := []core.VolumeMount{
  6506  		{Name: "abc", MountPath: "/foo"},
  6507  		{Name: "123", MountPath: "/bar"},
  6508  		{Name: "abc-123", MountPath: "/baz"},
  6509  		{Name: "abc-123", MountPath: "/baa", SubPath: ""},
  6510  		{Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
  6511  		{Name: "abc-123", MountPath: "d:", SubPath: ""},
  6512  		{Name: "abc-123", MountPath: "F:", SubPath: ""},
  6513  		{Name: "abc-123", MountPath: "G:\\mount", SubPath: ""},
  6514  		{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
  6515  		{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
  6516  		{Name: "ephemeral", MountPath: "/foobar"},
  6517  	}
  6518  	goodVolumeDevices := []core.VolumeDevice{
  6519  		{Name: "xyz", DevicePath: "/foofoo"},
  6520  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  6521  	}
  6522  	if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
  6523  		t.Errorf("expected success: %v", errs)
  6524  	}
  6525  
  6526  	errorCases := map[string][]core.VolumeMount{
  6527  		"empty name":                             {{Name: "", MountPath: "/foo"}},
  6528  		"name not found":                         {{Name: "", MountPath: "/foo"}},
  6529  		"empty mountpath":                        {{Name: "abc", MountPath: ""}},
  6530  		"mountpath collision":                    {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
  6531  		"absolute subpath":                       {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
  6532  		"subpath in ..":                          {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
  6533  		"subpath contains ..":                    {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
  6534  		"subpath ends in ..":                     {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
  6535  		"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
  6536  		"name exists in volumeDevice":            {{Name: "xyz", MountPath: "/bar"}},
  6537  		"mountpath exists in volumeDevice":       {{Name: "uvw", MountPath: "/mnt/exists"}},
  6538  		"both exist in volumeDevice":             {{Name: "xyz", MountPath: "/mnt/exists"}},
  6539  	}
  6540  	badVolumeDevice := []core.VolumeDevice{
  6541  		{Name: "xyz", DevicePath: "/mnt/exists"},
  6542  	}
  6543  
  6544  	for k, v := range errorCases {
  6545  		if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
  6546  			t.Errorf("expected failure for %s", k)
  6547  		}
  6548  	}
  6549  }
  6550  
  6551  func TestValidateSubpathMutuallyExclusive(t *testing.T) {
  6552  	volumes := []core.Volume{
  6553  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  6554  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  6555  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6556  	}
  6557  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  6558  	if len(v1err) > 0 {
  6559  		t.Errorf("Invalid test volume - expected success %v", v1err)
  6560  		return
  6561  	}
  6562  
  6563  	container := core.Container{
  6564  		SecurityContext: nil,
  6565  	}
  6566  
  6567  	goodVolumeDevices := []core.VolumeDevice{
  6568  		{Name: "xyz", DevicePath: "/foofoo"},
  6569  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  6570  	}
  6571  
  6572  	cases := map[string]struct {
  6573  		mounts      []core.VolumeMount
  6574  		expectError bool
  6575  	}{
  6576  		"subpath and subpathexpr not specified": {
  6577  			[]core.VolumeMount{{
  6578  				Name:      "abc-123",
  6579  				MountPath: "/bab",
  6580  			}},
  6581  			false,
  6582  		},
  6583  		"subpath expr specified": {
  6584  			[]core.VolumeMount{{
  6585  				Name:        "abc-123",
  6586  				MountPath:   "/bab",
  6587  				SubPathExpr: "$(POD_NAME)",
  6588  			}},
  6589  			false,
  6590  		},
  6591  		"subpath specified": {
  6592  			[]core.VolumeMount{{
  6593  				Name:      "abc-123",
  6594  				MountPath: "/bab",
  6595  				SubPath:   "baz",
  6596  			}},
  6597  			false,
  6598  		},
  6599  		"subpath and subpathexpr specified": {
  6600  			[]core.VolumeMount{{
  6601  				Name:        "abc-123",
  6602  				MountPath:   "/bab",
  6603  				SubPath:     "baz",
  6604  				SubPathExpr: "$(POD_NAME)",
  6605  			}},
  6606  			true,
  6607  		},
  6608  	}
  6609  
  6610  	for name, test := range cases {
  6611  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  6612  
  6613  		if len(errs) != 0 && !test.expectError {
  6614  			t.Errorf("test %v failed: %+v", name, errs)
  6615  		}
  6616  
  6617  		if len(errs) == 0 && test.expectError {
  6618  			t.Errorf("test %v failed, expected error", name)
  6619  		}
  6620  	}
  6621  }
  6622  
  6623  func TestValidateDisabledSubpathExpr(t *testing.T) {
  6624  
  6625  	volumes := []core.Volume{
  6626  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  6627  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  6628  		{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6629  	}
  6630  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  6631  	if len(v1err) > 0 {
  6632  		t.Errorf("Invalid test volume - expected success %v", v1err)
  6633  		return
  6634  	}
  6635  
  6636  	container := core.Container{
  6637  		SecurityContext: nil,
  6638  	}
  6639  
  6640  	goodVolumeDevices := []core.VolumeDevice{
  6641  		{Name: "xyz", DevicePath: "/foofoo"},
  6642  		{Name: "uvw", DevicePath: "/foofoo/share/test"},
  6643  	}
  6644  
  6645  	cases := map[string]struct {
  6646  		mounts      []core.VolumeMount
  6647  		expectError bool
  6648  	}{
  6649  		"subpath expr not specified": {
  6650  			[]core.VolumeMount{{
  6651  				Name:      "abc-123",
  6652  				MountPath: "/bab",
  6653  			}},
  6654  			false,
  6655  		},
  6656  		"subpath expr specified": {
  6657  			[]core.VolumeMount{{
  6658  				Name:        "abc-123",
  6659  				MountPath:   "/bab",
  6660  				SubPathExpr: "$(POD_NAME)",
  6661  			}},
  6662  			false,
  6663  		},
  6664  	}
  6665  
  6666  	for name, test := range cases {
  6667  		errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
  6668  
  6669  		if len(errs) != 0 && !test.expectError {
  6670  			t.Errorf("test %v failed: %+v", name, errs)
  6671  		}
  6672  
  6673  		if len(errs) == 0 && test.expectError {
  6674  			t.Errorf("test %v failed, expected error", name)
  6675  		}
  6676  	}
  6677  }
  6678  
  6679  func TestValidateMountPropagation(t *testing.T) {
  6680  	bTrue := true
  6681  	bFalse := false
  6682  	privilegedContainer := &core.Container{
  6683  		SecurityContext: &core.SecurityContext{
  6684  			Privileged: &bTrue,
  6685  		},
  6686  	}
  6687  	nonPrivilegedContainer := &core.Container{
  6688  		SecurityContext: &core.SecurityContext{
  6689  			Privileged: &bFalse,
  6690  		},
  6691  	}
  6692  	defaultContainer := &core.Container{}
  6693  
  6694  	propagationBidirectional := core.MountPropagationBidirectional
  6695  	propagationHostToContainer := core.MountPropagationHostToContainer
  6696  	propagationNone := core.MountPropagationNone
  6697  	propagationInvalid := core.MountPropagationMode("invalid")
  6698  
  6699  	tests := []struct {
  6700  		mount       core.VolumeMount
  6701  		container   *core.Container
  6702  		expectError bool
  6703  	}{{
  6704  		// implicitly non-privileged container + no propagation
  6705  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  6706  		defaultContainer,
  6707  		false,
  6708  	}, {
  6709  		// implicitly non-privileged container + HostToContainer
  6710  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  6711  		defaultContainer,
  6712  		false,
  6713  	}, {
  6714  		// non-privileged container + None
  6715  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone},
  6716  		defaultContainer,
  6717  		false,
  6718  	}, {
  6719  		// error: implicitly non-privileged container + Bidirectional
  6720  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  6721  		defaultContainer,
  6722  		true,
  6723  	}, {
  6724  		// explicitly non-privileged container + no propagation
  6725  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  6726  		nonPrivilegedContainer,
  6727  		false,
  6728  	}, {
  6729  		// explicitly non-privileged container + HostToContainer
  6730  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  6731  		nonPrivilegedContainer,
  6732  		false,
  6733  	}, {
  6734  		// explicitly non-privileged container + HostToContainer
  6735  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  6736  		nonPrivilegedContainer,
  6737  		true,
  6738  	}, {
  6739  		// privileged container + no propagation
  6740  		core.VolumeMount{Name: "foo", MountPath: "/foo"},
  6741  		privilegedContainer,
  6742  		false,
  6743  	}, {
  6744  		// privileged container + HostToContainer
  6745  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
  6746  		privilegedContainer,
  6747  		false,
  6748  	}, {
  6749  		// privileged container + Bidirectional
  6750  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  6751  		privilegedContainer,
  6752  		false,
  6753  	}, {
  6754  		// error: privileged container + invalid mount propagation
  6755  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
  6756  		privilegedContainer,
  6757  		true,
  6758  	}, {
  6759  		// no container + Bidirectional
  6760  		core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
  6761  		nil,
  6762  		false,
  6763  	},
  6764  	}
  6765  
  6766  	volumes := []core.Volume{
  6767  		{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6768  	}
  6769  	vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  6770  	if len(v2err) > 0 {
  6771  		t.Errorf("Invalid test volume - expected success %v", v2err)
  6772  		return
  6773  	}
  6774  	for i, test := range tests {
  6775  		errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
  6776  		if test.expectError && len(errs) == 0 {
  6777  			t.Errorf("test %d expected error, got none", i)
  6778  		}
  6779  		if !test.expectError && len(errs) != 0 {
  6780  			t.Errorf("test %d expected success, got error: %v", i, errs)
  6781  		}
  6782  	}
  6783  }
  6784  
  6785  func TestAlphaValidateVolumeDevices(t *testing.T) {
  6786  	volumes := []core.Volume{
  6787  		{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
  6788  		{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
  6789  		{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
  6790  		{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
  6791  			Spec: core.PersistentVolumeClaimSpec{
  6792  				AccessModes: []core.PersistentVolumeAccessMode{
  6793  					core.ReadWriteOnce,
  6794  				},
  6795  				Resources: core.VolumeResourceRequirements{
  6796  					Requests: core.ResourceList{
  6797  						core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  6798  					},
  6799  				},
  6800  			},
  6801  		}}}},
  6802  	}
  6803  
  6804  	vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
  6805  	if len(v1err) > 0 {
  6806  		t.Errorf("Invalid test volumes - expected success %v", v1err)
  6807  		return
  6808  	}
  6809  
  6810  	successCase := []core.VolumeDevice{
  6811  		{Name: "abc", DevicePath: "/foo"},
  6812  		{Name: "abc-123", DevicePath: "/usr/share/test"},
  6813  		{Name: "ephemeral", DevicePath: "/disk"},
  6814  	}
  6815  	goodVolumeMounts := []core.VolumeMount{
  6816  		{Name: "xyz", MountPath: "/foofoo"},
  6817  		{Name: "ghi", MountPath: "/foo/usr/share/test"},
  6818  	}
  6819  
  6820  	errorCases := map[string][]core.VolumeDevice{
  6821  		"empty name":                    {{Name: "", DevicePath: "/foo"}},
  6822  		"duplicate name":                {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
  6823  		"name not found":                {{Name: "not-found", DevicePath: "/usr/share/test"}},
  6824  		"name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
  6825  		"empty devicepath":              {{Name: "abc", DevicePath: ""}},
  6826  		"relative devicepath":           {{Name: "abc-123", DevicePath: "baz"}},
  6827  		"duplicate devicepath":          {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
  6828  		"no backsteps":                  {{Name: "def", DevicePath: "/baz/../"}},
  6829  		"name exists in volumemounts":   {{Name: "abc", DevicePath: "/baz/../"}},
  6830  		"path exists in volumemounts":   {{Name: "xyz", DevicePath: "/this/path/exists"}},
  6831  		"both exist in volumemounts":    {{Name: "abc", DevicePath: "/this/path/exists"}},
  6832  	}
  6833  	badVolumeMounts := []core.VolumeMount{
  6834  		{Name: "abc", MountPath: "/foo"},
  6835  		{Name: "abc-123", MountPath: "/this/path/exists"},
  6836  	}
  6837  
  6838  	// Success Cases:
  6839  	// Validate normal success cases - only PVC volumeSource or generic ephemeral volume
  6840  	if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
  6841  		t.Errorf("expected success: %v", errs)
  6842  	}
  6843  
  6844  	// Error Cases:
  6845  	// Validate normal error cases - only PVC volumeSource
  6846  	for k, v := range errorCases {
  6847  		if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
  6848  			t.Errorf("expected failure for %s", k)
  6849  		}
  6850  	}
  6851  }
  6852  
  6853  func TestValidateProbe(t *testing.T) {
  6854  	handler := core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}}
  6855  	// These fields must be positive.
  6856  	positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"}
  6857  	successCases := []*core.Probe{nil}
  6858  	for _, field := range positiveFields {
  6859  		probe := &core.Probe{ProbeHandler: handler}
  6860  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10)
  6861  		successCases = append(successCases, probe)
  6862  	}
  6863  
  6864  	for _, p := range successCases {
  6865  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  6866  			t.Errorf("expected success: %v", errs)
  6867  		}
  6868  	}
  6869  
  6870  	errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}}
  6871  	for _, field := range positiveFields {
  6872  		probe := &core.Probe{ProbeHandler: handler}
  6873  		reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10)
  6874  		errorCases = append(errorCases, probe)
  6875  	}
  6876  	for _, p := range errorCases {
  6877  		if errs := validateProbe(p, defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  6878  			t.Errorf("expected failure for %v", p)
  6879  		}
  6880  	}
  6881  }
  6882  
  6883  func Test_validateProbe(t *testing.T) {
  6884  	fldPath := field.NewPath("test")
  6885  	type args struct {
  6886  		probe   *core.Probe
  6887  		fldPath *field.Path
  6888  	}
  6889  	tests := []struct {
  6890  		name string
  6891  		args args
  6892  		want field.ErrorList
  6893  	}{{
  6894  		args: args{
  6895  			probe:   &core.Probe{},
  6896  			fldPath: fldPath,
  6897  		},
  6898  		want: field.ErrorList{field.Required(fldPath, "must specify a handler type")},
  6899  	}, {
  6900  		args: args{
  6901  			probe: &core.Probe{
  6902  				ProbeHandler: core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6903  			},
  6904  			fldPath: fldPath,
  6905  		},
  6906  		want: field.ErrorList{},
  6907  	}, {
  6908  		args: args{
  6909  			probe: &core.Probe{
  6910  				ProbeHandler:        core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6911  				InitialDelaySeconds: -1,
  6912  			},
  6913  			fldPath: fldPath,
  6914  		},
  6915  		want: field.ErrorList{field.Invalid(fldPath.Child("initialDelaySeconds"), -1, "must be greater than or equal to 0")},
  6916  	}, {
  6917  		args: args{
  6918  			probe: &core.Probe{
  6919  				ProbeHandler:   core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6920  				TimeoutSeconds: -1,
  6921  			},
  6922  			fldPath: fldPath,
  6923  		},
  6924  		want: field.ErrorList{field.Invalid(fldPath.Child("timeoutSeconds"), -1, "must be greater than or equal to 0")},
  6925  	}, {
  6926  		args: args{
  6927  			probe: &core.Probe{
  6928  				ProbeHandler:  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6929  				PeriodSeconds: -1,
  6930  			},
  6931  			fldPath: fldPath,
  6932  		},
  6933  		want: field.ErrorList{field.Invalid(fldPath.Child("periodSeconds"), -1, "must be greater than or equal to 0")},
  6934  	}, {
  6935  		args: args{
  6936  			probe: &core.Probe{
  6937  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6938  				SuccessThreshold: -1,
  6939  			},
  6940  			fldPath: fldPath,
  6941  		},
  6942  		want: field.ErrorList{field.Invalid(fldPath.Child("successThreshold"), -1, "must be greater than or equal to 0")},
  6943  	}, {
  6944  		args: args{
  6945  			probe: &core.Probe{
  6946  				ProbeHandler:     core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6947  				FailureThreshold: -1,
  6948  			},
  6949  			fldPath: fldPath,
  6950  		},
  6951  		want: field.ErrorList{field.Invalid(fldPath.Child("failureThreshold"), -1, "must be greater than or equal to 0")},
  6952  	}, {
  6953  		args: args{
  6954  			probe: &core.Probe{
  6955  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6956  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  6957  			},
  6958  			fldPath: fldPath,
  6959  		},
  6960  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), -1, "must be greater than 0")},
  6961  	}, {
  6962  		args: args{
  6963  			probe: &core.Probe{
  6964  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6965  				TerminationGracePeriodSeconds: utilpointer.Int64(0),
  6966  			},
  6967  			fldPath: fldPath,
  6968  		},
  6969  		want: field.ErrorList{field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), 0, "must be greater than 0")},
  6970  	}, {
  6971  		args: args{
  6972  			probe: &core.Probe{
  6973  				ProbeHandler:                  core.ProbeHandler{Exec: &core.ExecAction{Command: []string{"echo"}}},
  6974  				TerminationGracePeriodSeconds: utilpointer.Int64(1),
  6975  			},
  6976  			fldPath: fldPath,
  6977  		},
  6978  		want: field.ErrorList{},
  6979  	},
  6980  	}
  6981  	for _, tt := range tests {
  6982  		t.Run(tt.name, func(t *testing.T) {
  6983  			got := validateProbe(tt.args.probe, defaultGracePeriod, tt.args.fldPath)
  6984  			if len(got) != len(tt.want) {
  6985  				t.Errorf("validateProbe() = %v, want %v", got, tt.want)
  6986  				return
  6987  			}
  6988  			for i := range got {
  6989  				if got[i].Type != tt.want[i].Type ||
  6990  					got[i].Field != tt.want[i].Field {
  6991  					t.Errorf("validateProbe()[%d] = %v, want %v", i, got[i], tt.want[i])
  6992  				}
  6993  			}
  6994  		})
  6995  	}
  6996  }
  6997  
  6998  func TestValidateHandler(t *testing.T) {
  6999  	successCases := []core.ProbeHandler{
  7000  		{Exec: &core.ExecAction{Command: []string{"echo"}}},
  7001  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt32(1), Host: "", Scheme: "HTTP"}},
  7002  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65535), Host: "host", Scheme: "HTTP"}},
  7003  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
  7004  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}},
  7005  		{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"}}}},
  7006  	}
  7007  	for _, h := range successCases {
  7008  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) != 0 {
  7009  			t.Errorf("expected success: %v", errs)
  7010  		}
  7011  	}
  7012  
  7013  	errorCases := []core.ProbeHandler{
  7014  		{},
  7015  		{Exec: &core.ExecAction{Command: []string{}}},
  7016  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt32(0), Host: ""}},
  7017  		{HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt32(65536), Host: "host"}},
  7018  		{HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
  7019  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}},
  7020  		{HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
  7021  	}
  7022  	for _, h := range errorCases {
  7023  		if errs := validateHandler(handlerFromProbe(&h), defaultGracePeriod, field.NewPath("field")); len(errs) == 0 {
  7024  			t.Errorf("expected failure for %#v", h)
  7025  		}
  7026  	}
  7027  }
  7028  
  7029  func TestValidatePullPolicy(t *testing.T) {
  7030  	type T struct {
  7031  		Container      core.Container
  7032  		ExpectedPolicy core.PullPolicy
  7033  	}
  7034  	testCases := map[string]T{
  7035  		"NotPresent1": {
  7036  			core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7037  			core.PullIfNotPresent,
  7038  		},
  7039  		"NotPresent2": {
  7040  			core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7041  			core.PullIfNotPresent,
  7042  		},
  7043  		"Always1": {
  7044  			core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"},
  7045  			core.PullAlways,
  7046  		},
  7047  		"Always2": {
  7048  			core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"},
  7049  			core.PullAlways,
  7050  		},
  7051  		"Never1": {
  7052  			core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"},
  7053  			core.PullNever,
  7054  		},
  7055  		"Never2": {
  7056  			core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
  7057  			core.PullNever,
  7058  		},
  7059  	}
  7060  	for k, v := range testCases {
  7061  		ctr := &v.Container
  7062  		errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field"))
  7063  		if len(errs) != 0 {
  7064  			t.Errorf("case[%s] expected success, got %#v", k, errs)
  7065  		}
  7066  		if ctr.ImagePullPolicy != v.ExpectedPolicy {
  7067  			t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
  7068  		}
  7069  	}
  7070  }
  7071  
  7072  func TestValidateResizePolicy(t *testing.T) {
  7073  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
  7074  	tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
  7075  	tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer))
  7076  	type T struct {
  7077  		PolicyList       []core.ContainerResizePolicy
  7078  		ExpectError      bool
  7079  		Errors           field.ErrorList
  7080  		PodRestartPolicy core.RestartPolicy
  7081  	}
  7082  
  7083  	testCases := map[string]T{
  7084  		"ValidCPUandMemoryPolicies": {
  7085  			PolicyList: []core.ContainerResizePolicy{
  7086  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7087  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7088  			},
  7089  			ExpectError:      false,
  7090  			Errors:           nil,
  7091  			PodRestartPolicy: "Always",
  7092  		},
  7093  		"ValidCPUPolicy": {
  7094  			PolicyList: []core.ContainerResizePolicy{
  7095  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7096  			},
  7097  			ExpectError:      false,
  7098  			Errors:           nil,
  7099  			PodRestartPolicy: "Always",
  7100  		},
  7101  		"ValidMemoryPolicy": {
  7102  			PolicyList: []core.ContainerResizePolicy{
  7103  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7104  			},
  7105  			ExpectError:      false,
  7106  			Errors:           nil,
  7107  			PodRestartPolicy: "Always",
  7108  		},
  7109  		"NoPolicy": {
  7110  			PolicyList:       []core.ContainerResizePolicy{},
  7111  			ExpectError:      false,
  7112  			Errors:           nil,
  7113  			PodRestartPolicy: "Always",
  7114  		},
  7115  		"ValidCPUandInvalidMemoryPolicy": {
  7116  			PolicyList: []core.ContainerResizePolicy{
  7117  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7118  				{ResourceName: "memory", RestartPolicy: "Restarrrt"},
  7119  			},
  7120  			ExpectError:      true,
  7121  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("Restarrrt"), tSupportedResizePolicies.List())},
  7122  			PodRestartPolicy: "Always",
  7123  		},
  7124  		"ValidMemoryandInvalidCPUPolicy": {
  7125  			PolicyList: []core.ContainerResizePolicy{
  7126  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequirrred"},
  7127  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7128  			},
  7129  			ExpectError:      true,
  7130  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartNotRequirrred"), tSupportedResizePolicies.List())},
  7131  			PodRestartPolicy: "Always",
  7132  		},
  7133  		"InvalidResourceNameValidPolicy": {
  7134  			PolicyList: []core.ContainerResizePolicy{
  7135  				{ResourceName: "cpuuu", RestartPolicy: "NotRequired"},
  7136  			},
  7137  			ExpectError:      true,
  7138  			Errors:           field.ErrorList{field.NotSupported(field.NewPath("field"), core.ResourceName("cpuuu"), tSupportedResizeResources.List())},
  7139  			PodRestartPolicy: "Always",
  7140  		},
  7141  		"ValidResourceNameMissingPolicy": {
  7142  			PolicyList: []core.ContainerResizePolicy{
  7143  				{ResourceName: "memory", RestartPolicy: ""},
  7144  			},
  7145  			ExpectError:      true,
  7146  			Errors:           field.ErrorList{field.Required(field.NewPath("field"), "")},
  7147  			PodRestartPolicy: "Always",
  7148  		},
  7149  		"RepeatedPolicies": {
  7150  			PolicyList: []core.ContainerResizePolicy{
  7151  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7152  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7153  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7154  			},
  7155  			ExpectError:      true,
  7156  			Errors:           field.ErrorList{field.Duplicate(field.NewPath("field").Index(2), core.ResourceCPU)},
  7157  			PodRestartPolicy: "Always",
  7158  		},
  7159  		"InvalidCPUPolicyWithPodRestartPolicy": {
  7160  			PolicyList: []core.ContainerResizePolicy{
  7161  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7162  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7163  			},
  7164  			ExpectError:      true,
  7165  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7166  			PodRestartPolicy: "Never",
  7167  		},
  7168  		"InvalidMemoryPolicyWithPodRestartPolicy": {
  7169  			PolicyList: []core.ContainerResizePolicy{
  7170  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7171  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7172  			},
  7173  			ExpectError:      true,
  7174  			Errors:           field.ErrorList{field.Invalid(field.NewPath("field"), core.ResourceResizeRestartPolicy("RestartContainer"), "must be 'NotRequired' when `restartPolicy` is 'Never'")},
  7175  			PodRestartPolicy: "Never",
  7176  		},
  7177  		"InvalidMemoryCPUPolicyWithPodRestartPolicy": {
  7178  			PolicyList: []core.ContainerResizePolicy{
  7179  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7180  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7181  			},
  7182  			ExpectError:      true,
  7183  			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'")},
  7184  			PodRestartPolicy: "Never",
  7185  		},
  7186  		"ValidMemoryCPUPolicyWithPodRestartPolicy": {
  7187  			PolicyList: []core.ContainerResizePolicy{
  7188  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7189  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7190  			},
  7191  			ExpectError:      false,
  7192  			Errors:           nil,
  7193  			PodRestartPolicy: "Never",
  7194  		},
  7195  	}
  7196  	for k, v := range testCases {
  7197  		errs := validateResizePolicy(v.PolicyList, field.NewPath("field"), &v.PodRestartPolicy)
  7198  		if !v.ExpectError && len(errs) > 0 {
  7199  			t.Errorf("Testcase %s - expected success, got error: %+v", k, errs)
  7200  		}
  7201  		if v.ExpectError {
  7202  			if len(errs) == 0 {
  7203  				t.Errorf("Testcase %s - expected error, got success", k)
  7204  			}
  7205  			delta := cmp.Diff(errs, v.Errors)
  7206  			if delta != "" {
  7207  				t.Errorf("Testcase %s - expected errors '%v', got '%v', diff: '%v'", k, v.Errors, errs, delta)
  7208  			}
  7209  		}
  7210  	}
  7211  }
  7212  
  7213  func getResourceLimits(cpu, memory string) core.ResourceList {
  7214  	res := core.ResourceList{}
  7215  	res[core.ResourceCPU] = resource.MustParse(cpu)
  7216  	res[core.ResourceMemory] = resource.MustParse(memory)
  7217  	return res
  7218  }
  7219  
  7220  func getResources(cpu, memory, storage string) core.ResourceList {
  7221  	res := core.ResourceList{}
  7222  	if cpu != "" {
  7223  		res[core.ResourceCPU] = resource.MustParse(cpu)
  7224  	}
  7225  	if memory != "" {
  7226  		res[core.ResourceMemory] = resource.MustParse(memory)
  7227  	}
  7228  	if storage != "" {
  7229  		res[core.ResourceEphemeralStorage] = resource.MustParse(storage)
  7230  	}
  7231  	return res
  7232  }
  7233  
  7234  func TestValidateEphemeralContainers(t *testing.T) {
  7235  	containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7236  	initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
  7237  	vols := map[string]core.VolumeSource{
  7238  		"blk": {PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "pvc"}},
  7239  		"vol": {EmptyDir: &core.EmptyDirVolumeSource{}},
  7240  	}
  7241  
  7242  	// Success Cases
  7243  	for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
  7244  		"Empty Ephemeral Containers": {},
  7245  		"Single Container": {
  7246  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7247  		},
  7248  		"Multiple Containers": {
  7249  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7250  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7251  		},
  7252  		"Single Container with Target": {{
  7253  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7254  			TargetContainerName:      "ctr",
  7255  		}},
  7256  		"All allowed fields": {{
  7257  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7258  
  7259  				Name:       "debug",
  7260  				Image:      "image",
  7261  				Command:    []string{"bash"},
  7262  				Args:       []string{"bash"},
  7263  				WorkingDir: "/",
  7264  				EnvFrom: []core.EnvFromSource{{
  7265  					ConfigMapRef: &core.ConfigMapEnvSource{
  7266  						LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
  7267  						Optional:             &[]bool{true}[0],
  7268  					},
  7269  				}},
  7270  				Env: []core.EnvVar{
  7271  					{Name: "TEST", Value: "TRUE"},
  7272  				},
  7273  				VolumeMounts: []core.VolumeMount{
  7274  					{Name: "vol", MountPath: "/vol"},
  7275  				},
  7276  				VolumeDevices: []core.VolumeDevice{
  7277  					{Name: "blk", DevicePath: "/dev/block"},
  7278  				},
  7279  				TerminationMessagePath:   "/dev/termination-log",
  7280  				TerminationMessagePolicy: "File",
  7281  				ImagePullPolicy:          "IfNotPresent",
  7282  				SecurityContext: &core.SecurityContext{
  7283  					Capabilities: &core.Capabilities{
  7284  						Add: []core.Capability{"SYS_ADMIN"},
  7285  					},
  7286  				},
  7287  				Stdin:     true,
  7288  				StdinOnce: true,
  7289  				TTY:       true,
  7290  			},
  7291  		}},
  7292  	} {
  7293  		var PodRestartPolicy core.RestartPolicy
  7294  		PodRestartPolicy = "Never"
  7295  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 {
  7296  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7297  		}
  7298  
  7299  		PodRestartPolicy = "Always"
  7300  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 {
  7301  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7302  		}
  7303  
  7304  		PodRestartPolicy = "OnFailure"
  7305  		if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 {
  7306  			t.Errorf("expected success for '%s' but got errors: %v", title, errs)
  7307  		}
  7308  	}
  7309  
  7310  	// Failure Cases
  7311  	tcs := []struct {
  7312  		title, line         string
  7313  		ephemeralContainers []core.EphemeralContainer
  7314  		expectedErrors      field.ErrorList
  7315  	}{{
  7316  		"Name Collision with Container.Containers",
  7317  		line(),
  7318  		[]core.EphemeralContainer{
  7319  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7320  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7321  		},
  7322  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7323  	}, {
  7324  		"Name Collision with Container.InitContainers",
  7325  		line(),
  7326  		[]core.EphemeralContainer{
  7327  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7328  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7329  		},
  7330  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"}},
  7331  	}, {
  7332  		"Name Collision with EphemeralContainers",
  7333  		line(),
  7334  		[]core.EphemeralContainer{
  7335  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7336  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7337  		},
  7338  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"}},
  7339  	}, {
  7340  		"empty Container",
  7341  		line(),
  7342  		[]core.EphemeralContainer{
  7343  			{EphemeralContainerCommon: core.EphemeralContainerCommon{}},
  7344  		},
  7345  		field.ErrorList{
  7346  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"},
  7347  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].image"},
  7348  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].terminationMessagePolicy"},
  7349  			{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].imagePullPolicy"},
  7350  		},
  7351  	}, {
  7352  		"empty Container Name",
  7353  		line(),
  7354  		[]core.EphemeralContainer{
  7355  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7356  		},
  7357  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0].name"}},
  7358  	}, {
  7359  		"whitespace padded image name",
  7360  		line(),
  7361  		[]core.EphemeralContainer{
  7362  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7363  		},
  7364  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0].image"}},
  7365  	}, {
  7366  		"invalid image pull policy",
  7367  		line(),
  7368  		[]core.EphemeralContainer{
  7369  			{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "PullThreeTimes", TerminationMessagePolicy: "File"}},
  7370  		},
  7371  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "ephemeralContainers[0].imagePullPolicy"}},
  7372  	}, {
  7373  		"TargetContainerName doesn't exist",
  7374  		line(),
  7375  		[]core.EphemeralContainer{{
  7376  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7377  			TargetContainerName:      "bogus",
  7378  		}},
  7379  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"}},
  7380  	}, {
  7381  		"Targets an ephemeral container",
  7382  		line(),
  7383  		[]core.EphemeralContainer{{
  7384  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7385  		}, {
  7386  			EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debugception", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7387  			TargetContainerName:      "debug",
  7388  		}},
  7389  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[1].targetContainerName"}},
  7390  	}, {
  7391  		"Container uses disallowed field: Lifecycle",
  7392  		line(),
  7393  		[]core.EphemeralContainer{{
  7394  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7395  				Name:                     "debug",
  7396  				Image:                    "image",
  7397  				ImagePullPolicy:          "IfNotPresent",
  7398  				TerminationMessagePolicy: "File",
  7399  				Lifecycle: &core.Lifecycle{
  7400  					PreStop: &core.LifecycleHandler{
  7401  						Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  7402  					},
  7403  				},
  7404  			},
  7405  		}},
  7406  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  7407  	}, {
  7408  		"Container uses disallowed field: LivenessProbe",
  7409  		line(),
  7410  		[]core.EphemeralContainer{{
  7411  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7412  				Name:                     "debug",
  7413  				Image:                    "image",
  7414  				ImagePullPolicy:          "IfNotPresent",
  7415  				TerminationMessagePolicy: "File",
  7416  				LivenessProbe: &core.Probe{
  7417  					ProbeHandler: core.ProbeHandler{
  7418  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7419  					},
  7420  					SuccessThreshold: 1,
  7421  				},
  7422  			},
  7423  		}},
  7424  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"}},
  7425  	}, {
  7426  		"Container uses disallowed field: Ports",
  7427  		line(),
  7428  		[]core.EphemeralContainer{{
  7429  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7430  				Name:                     "debug",
  7431  				Image:                    "image",
  7432  				ImagePullPolicy:          "IfNotPresent",
  7433  				TerminationMessagePolicy: "File",
  7434  				Ports: []core.ContainerPort{
  7435  					{Protocol: "TCP", ContainerPort: 80},
  7436  				},
  7437  			},
  7438  		}},
  7439  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"}},
  7440  	}, {
  7441  		"Container uses disallowed field: ReadinessProbe",
  7442  		line(),
  7443  		[]core.EphemeralContainer{{
  7444  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7445  				Name:                     "debug",
  7446  				Image:                    "image",
  7447  				ImagePullPolicy:          "IfNotPresent",
  7448  				TerminationMessagePolicy: "File",
  7449  				ReadinessProbe: &core.Probe{
  7450  					ProbeHandler: core.ProbeHandler{
  7451  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7452  					},
  7453  				},
  7454  			},
  7455  		}},
  7456  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"}},
  7457  	}, {
  7458  		"Container uses disallowed field: StartupProbe",
  7459  		line(),
  7460  		[]core.EphemeralContainer{{
  7461  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7462  				Name:                     "debug",
  7463  				Image:                    "image",
  7464  				ImagePullPolicy:          "IfNotPresent",
  7465  				TerminationMessagePolicy: "File",
  7466  				StartupProbe: &core.Probe{
  7467  					ProbeHandler: core.ProbeHandler{
  7468  						TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  7469  					},
  7470  					SuccessThreshold: 1,
  7471  				},
  7472  			},
  7473  		}},
  7474  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].startupProbe"}},
  7475  	}, {
  7476  		"Container uses disallowed field: Resources",
  7477  		line(),
  7478  		[]core.EphemeralContainer{{
  7479  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7480  				Name:                     "debug",
  7481  				Image:                    "image",
  7482  				ImagePullPolicy:          "IfNotPresent",
  7483  				TerminationMessagePolicy: "File",
  7484  				Resources: core.ResourceRequirements{
  7485  					Limits: core.ResourceList{
  7486  						core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  7487  					},
  7488  				},
  7489  			},
  7490  		}},
  7491  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"}},
  7492  	}, {
  7493  		"Container uses disallowed field: VolumeMount.SubPath",
  7494  		line(),
  7495  		[]core.EphemeralContainer{{
  7496  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7497  				Name:                     "debug",
  7498  				Image:                    "image",
  7499  				ImagePullPolicy:          "IfNotPresent",
  7500  				TerminationMessagePolicy: "File",
  7501  				VolumeMounts: []core.VolumeMount{
  7502  					{Name: "vol", MountPath: "/vol"},
  7503  					{Name: "vol", MountPath: "/volsub", SubPath: "foo"},
  7504  				},
  7505  			},
  7506  		}},
  7507  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPath"}},
  7508  	}, {
  7509  		"Container uses disallowed field: VolumeMount.SubPathExpr",
  7510  		line(),
  7511  		[]core.EphemeralContainer{{
  7512  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7513  				Name:                     "debug",
  7514  				Image:                    "image",
  7515  				ImagePullPolicy:          "IfNotPresent",
  7516  				TerminationMessagePolicy: "File",
  7517  				VolumeMounts: []core.VolumeMount{
  7518  					{Name: "vol", MountPath: "/vol"},
  7519  					{Name: "vol", MountPath: "/volsub", SubPathExpr: "$(POD_NAME)"},
  7520  				},
  7521  			},
  7522  		}},
  7523  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].volumeMounts[1].subPathExpr"}},
  7524  	}, {
  7525  		"Disallowed field with other errors should only return a single Forbidden",
  7526  		line(),
  7527  		[]core.EphemeralContainer{{
  7528  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7529  				Name:                     "debug",
  7530  				Image:                    "image",
  7531  				ImagePullPolicy:          "IfNotPresent",
  7532  				TerminationMessagePolicy: "File",
  7533  				Lifecycle: &core.Lifecycle{
  7534  					PreStop: &core.LifecycleHandler{
  7535  						Exec: &core.ExecAction{Command: []string{}},
  7536  					},
  7537  				},
  7538  			},
  7539  		}},
  7540  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"}},
  7541  	}, {
  7542  		"Container uses disallowed field: ResizePolicy",
  7543  		line(),
  7544  		[]core.EphemeralContainer{{
  7545  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7546  				Name:                     "resources-resize-policy",
  7547  				Image:                    "image",
  7548  				ImagePullPolicy:          "IfNotPresent",
  7549  				TerminationMessagePolicy: "File",
  7550  				ResizePolicy: []core.ContainerResizePolicy{
  7551  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7552  				},
  7553  			},
  7554  		}},
  7555  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resizePolicy"}},
  7556  	}, {
  7557  		"Forbidden RestartPolicy: Always",
  7558  		line(),
  7559  		[]core.EphemeralContainer{{
  7560  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7561  				Name:                     "foo",
  7562  				Image:                    "image",
  7563  				ImagePullPolicy:          "IfNotPresent",
  7564  				TerminationMessagePolicy: "File",
  7565  				RestartPolicy:            &containerRestartPolicyAlways,
  7566  			},
  7567  		}},
  7568  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  7569  	}, {
  7570  		"Forbidden RestartPolicy: OnFailure",
  7571  		line(),
  7572  		[]core.EphemeralContainer{{
  7573  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7574  				Name:                     "foo",
  7575  				Image:                    "image",
  7576  				ImagePullPolicy:          "IfNotPresent",
  7577  				TerminationMessagePolicy: "File",
  7578  				RestartPolicy:            &containerRestartPolicyOnFailure,
  7579  			},
  7580  		}},
  7581  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  7582  	}, {
  7583  		"Forbidden RestartPolicy: Never",
  7584  		line(),
  7585  		[]core.EphemeralContainer{{
  7586  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7587  				Name:                     "foo",
  7588  				Image:                    "image",
  7589  				ImagePullPolicy:          "IfNotPresent",
  7590  				TerminationMessagePolicy: "File",
  7591  				RestartPolicy:            &containerRestartPolicyNever,
  7592  			},
  7593  		}},
  7594  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  7595  	}, {
  7596  		"Forbidden RestartPolicy: invalid",
  7597  		line(),
  7598  		[]core.EphemeralContainer{{
  7599  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7600  				Name:                     "foo",
  7601  				Image:                    "image",
  7602  				ImagePullPolicy:          "IfNotPresent",
  7603  				TerminationMessagePolicy: "File",
  7604  				RestartPolicy:            &containerRestartPolicyInvalid,
  7605  			},
  7606  		}},
  7607  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  7608  	}, {
  7609  		"Forbidden RestartPolicy: empty",
  7610  		line(),
  7611  		[]core.EphemeralContainer{{
  7612  			EphemeralContainerCommon: core.EphemeralContainerCommon{
  7613  				Name:                     "foo",
  7614  				Image:                    "image",
  7615  				ImagePullPolicy:          "IfNotPresent",
  7616  				TerminationMessagePolicy: "File",
  7617  				RestartPolicy:            &containerRestartPolicyEmpty,
  7618  			},
  7619  		}},
  7620  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].restartPolicy"}},
  7621  	},
  7622  	}
  7623  
  7624  	var PodRestartPolicy core.RestartPolicy
  7625  
  7626  	for _, tc := range tcs {
  7627  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  7628  
  7629  			PodRestartPolicy = "Never"
  7630  			errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy)
  7631  			if len(errs) == 0 {
  7632  				t.Fatal("expected error but received none")
  7633  			}
  7634  
  7635  			PodRestartPolicy = "Always"
  7636  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy)
  7637  			if len(errs) == 0 {
  7638  				t.Fatal("expected error but received none")
  7639  			}
  7640  
  7641  			PodRestartPolicy = "OnFailure"
  7642  			errs = validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}, &PodRestartPolicy)
  7643  			if len(errs) == 0 {
  7644  				t.Fatal("expected error but received none")
  7645  			}
  7646  
  7647  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  7648  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  7649  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  7650  			}
  7651  		})
  7652  	}
  7653  }
  7654  
  7655  func TestValidateWindowsPodSecurityContext(t *testing.T) {
  7656  	validWindowsSC := &core.PodSecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}
  7657  	invalidWindowsSC := &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummyRole"}}
  7658  	cases := map[string]struct {
  7659  		podSec      *core.PodSpec
  7660  		expectErr   bool
  7661  		errorType   field.ErrorType
  7662  		errorDetail string
  7663  	}{
  7664  		"valid SC, windows, no error": {
  7665  			podSec:    &core.PodSpec{SecurityContext: validWindowsSC},
  7666  			expectErr: false,
  7667  		},
  7668  		"invalid SC, windows, error": {
  7669  			podSec:      &core.PodSpec{SecurityContext: invalidWindowsSC},
  7670  			errorType:   "FieldValueForbidden",
  7671  			errorDetail: "cannot be set for a windows pod",
  7672  			expectErr:   true,
  7673  		},
  7674  	}
  7675  	for k, v := range cases {
  7676  		t.Run(k, func(t *testing.T) {
  7677  			errs := validateWindows(v.podSec, field.NewPath("field"))
  7678  			if v.expectErr && len(errs) > 0 {
  7679  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  7680  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  7681  				}
  7682  			} else if v.expectErr && len(errs) == 0 {
  7683  				t.Errorf("Unexpected success")
  7684  			}
  7685  			if !v.expectErr && len(errs) != 0 {
  7686  				t.Errorf("Unexpected error(s): %v", errs)
  7687  			}
  7688  		})
  7689  	}
  7690  }
  7691  
  7692  func TestValidateLinuxPodSecurityContext(t *testing.T) {
  7693  	runAsUser := int64(1)
  7694  	validLinuxSC := &core.PodSecurityContext{
  7695  		SELinuxOptions: &core.SELinuxOptions{
  7696  			User:  "user",
  7697  			Role:  "role",
  7698  			Type:  "type",
  7699  			Level: "level",
  7700  		},
  7701  		RunAsUser: &runAsUser,
  7702  	}
  7703  	invalidLinuxSC := &core.PodSecurityContext{
  7704  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
  7705  	}
  7706  
  7707  	cases := map[string]struct {
  7708  		podSpec     *core.PodSpec
  7709  		expectErr   bool
  7710  		errorType   field.ErrorType
  7711  		errorDetail string
  7712  	}{
  7713  		"valid SC, linux, no error": {
  7714  			podSpec:   &core.PodSpec{SecurityContext: validLinuxSC},
  7715  			expectErr: false,
  7716  		},
  7717  		"invalid SC, linux, error": {
  7718  			podSpec:     &core.PodSpec{SecurityContext: invalidLinuxSC},
  7719  			errorType:   "FieldValueForbidden",
  7720  			errorDetail: "windows options cannot be set for a linux pod",
  7721  			expectErr:   true,
  7722  		},
  7723  	}
  7724  	for k, v := range cases {
  7725  		t.Run(k, func(t *testing.T) {
  7726  			errs := validateLinux(v.podSpec, field.NewPath("field"))
  7727  			if v.expectErr && len(errs) > 0 {
  7728  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
  7729  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
  7730  				}
  7731  			} else if v.expectErr && len(errs) == 0 {
  7732  				t.Errorf("Unexpected success")
  7733  			}
  7734  			if !v.expectErr && len(errs) != 0 {
  7735  				t.Errorf("Unexpected error(s): %v", errs)
  7736  			}
  7737  		})
  7738  	}
  7739  }
  7740  
  7741  func TestValidateContainers(t *testing.T) {
  7742  	volumeDevices := make(map[string]core.VolumeSource)
  7743  	capabilities.SetForTests(capabilities.Capabilities{
  7744  		AllowPrivileged: true,
  7745  	})
  7746  
  7747  	successCase := []core.Container{
  7748  		{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7749  		// backwards compatibility to ensure containers in pod template spec do not check for this
  7750  		{Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7751  		{Name: "ghi", Image: " some  ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7752  		{Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7753  		{Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {
  7754  			Name:  "life-123",
  7755  			Image: "image",
  7756  			Lifecycle: &core.Lifecycle{
  7757  				PreStop: &core.LifecycleHandler{
  7758  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  7759  				},
  7760  			},
  7761  			ImagePullPolicy:          "IfNotPresent",
  7762  			TerminationMessagePolicy: "File",
  7763  		}, {
  7764  			Name:  "resources-test",
  7765  			Image: "image",
  7766  			Resources: core.ResourceRequirements{
  7767  				Limits: core.ResourceList{
  7768  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  7769  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7770  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  7771  				},
  7772  			},
  7773  			ImagePullPolicy:          "IfNotPresent",
  7774  			TerminationMessagePolicy: "File",
  7775  		}, {
  7776  			Name:  "resources-test-with-request-and-limit",
  7777  			Image: "image",
  7778  			Resources: core.ResourceRequirements{
  7779  				Requests: core.ResourceList{
  7780  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  7781  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7782  				},
  7783  				Limits: core.ResourceList{
  7784  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  7785  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7786  				},
  7787  			},
  7788  			ImagePullPolicy:          "IfNotPresent",
  7789  			TerminationMessagePolicy: "File",
  7790  		}, {
  7791  			Name:  "resources-request-limit-simple",
  7792  			Image: "image",
  7793  			Resources: core.ResourceRequirements{
  7794  				Requests: core.ResourceList{
  7795  					core.ResourceName(core.ResourceCPU): resource.MustParse("8"),
  7796  				},
  7797  				Limits: core.ResourceList{
  7798  					core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
  7799  				},
  7800  			},
  7801  			ImagePullPolicy:          "IfNotPresent",
  7802  			TerminationMessagePolicy: "File",
  7803  		}, {
  7804  			Name:  "resources-request-limit-edge",
  7805  			Image: "image",
  7806  			Resources: core.ResourceRequirements{
  7807  				Requests: core.ResourceList{
  7808  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  7809  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7810  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  7811  				},
  7812  				Limits: core.ResourceList{
  7813  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
  7814  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7815  					core.ResourceName("my.org/resource"):   resource.MustParse("10"),
  7816  				},
  7817  			},
  7818  			ImagePullPolicy:          "IfNotPresent",
  7819  			TerminationMessagePolicy: "File",
  7820  		}, {
  7821  			Name:  "resources-request-limit-partials",
  7822  			Image: "image",
  7823  			Resources: core.ResourceRequirements{
  7824  				Requests: core.ResourceList{
  7825  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  7826  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7827  				},
  7828  				Limits: core.ResourceList{
  7829  					core.ResourceName(core.ResourceCPU):  resource.MustParse("10"),
  7830  					core.ResourceName("my.org/resource"): resource.MustParse("10"),
  7831  				},
  7832  			},
  7833  			ImagePullPolicy:          "IfNotPresent",
  7834  			TerminationMessagePolicy: "File",
  7835  		}, {
  7836  			Name:  "resources-request",
  7837  			Image: "image",
  7838  			Resources: core.ResourceRequirements{
  7839  				Requests: core.ResourceList{
  7840  					core.ResourceName(core.ResourceCPU):    resource.MustParse("9.5"),
  7841  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
  7842  				},
  7843  			},
  7844  			ImagePullPolicy:          "IfNotPresent",
  7845  			TerminationMessagePolicy: "File",
  7846  		}, {
  7847  			Name:  "resources-resize-policy",
  7848  			Image: "image",
  7849  			ResizePolicy: []core.ContainerResizePolicy{
  7850  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7851  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7852  			},
  7853  			ImagePullPolicy:          "IfNotPresent",
  7854  			TerminationMessagePolicy: "File",
  7855  		}, {
  7856  			Name:  "same-host-port-different-protocol",
  7857  			Image: "image",
  7858  			Ports: []core.ContainerPort{
  7859  				{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  7860  				{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  7861  			},
  7862  			ImagePullPolicy:          "IfNotPresent",
  7863  			TerminationMessagePolicy: "File",
  7864  		}, {
  7865  			Name:                     "fallback-to-logs-termination-message",
  7866  			Image:                    "image",
  7867  			ImagePullPolicy:          "IfNotPresent",
  7868  			TerminationMessagePolicy: "FallbackToLogsOnError",
  7869  		}, {
  7870  			Name:                     "file-termination-message",
  7871  			Image:                    "image",
  7872  			ImagePullPolicy:          "IfNotPresent",
  7873  			TerminationMessagePolicy: "File",
  7874  		}, {
  7875  			Name:                     "env-from-source",
  7876  			Image:                    "image",
  7877  			ImagePullPolicy:          "IfNotPresent",
  7878  			TerminationMessagePolicy: "File",
  7879  			EnvFrom: []core.EnvFromSource{{
  7880  				ConfigMapRef: &core.ConfigMapEnvSource{
  7881  					LocalObjectReference: core.LocalObjectReference{
  7882  						Name: "test",
  7883  					},
  7884  				},
  7885  			}},
  7886  		},
  7887  		{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)}, {
  7888  			Name:  "live-123",
  7889  			Image: "image",
  7890  			LivenessProbe: &core.Probe{
  7891  				ProbeHandler: core.ProbeHandler{
  7892  					TCPSocket: &core.TCPSocketAction{
  7893  						Port: intstr.FromInt32(80),
  7894  					},
  7895  				},
  7896  				SuccessThreshold: 1,
  7897  			},
  7898  			ImagePullPolicy:          "IfNotPresent",
  7899  			TerminationMessagePolicy: "File",
  7900  		}, {
  7901  			Name:  "startup-123",
  7902  			Image: "image",
  7903  			StartupProbe: &core.Probe{
  7904  				ProbeHandler: core.ProbeHandler{
  7905  					TCPSocket: &core.TCPSocketAction{
  7906  						Port: intstr.FromInt32(80),
  7907  					},
  7908  				},
  7909  				SuccessThreshold: 1,
  7910  			},
  7911  			ImagePullPolicy:          "IfNotPresent",
  7912  			TerminationMessagePolicy: "File",
  7913  		}, {
  7914  			Name:                     "resize-policy-cpu",
  7915  			Image:                    "image",
  7916  			ImagePullPolicy:          "IfNotPresent",
  7917  			TerminationMessagePolicy: "File",
  7918  			ResizePolicy: []core.ContainerResizePolicy{
  7919  				{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  7920  			},
  7921  		}, {
  7922  			Name:                     "resize-policy-mem",
  7923  			Image:                    "image",
  7924  			ImagePullPolicy:          "IfNotPresent",
  7925  			TerminationMessagePolicy: "File",
  7926  			ResizePolicy: []core.ContainerResizePolicy{
  7927  				{ResourceName: "memory", RestartPolicy: "RestartContainer"},
  7928  			},
  7929  		}, {
  7930  			Name:                     "resize-policy-cpu-and-mem",
  7931  			Image:                    "image",
  7932  			ImagePullPolicy:          "IfNotPresent",
  7933  			TerminationMessagePolicy: "File",
  7934  			ResizePolicy: []core.ContainerResizePolicy{
  7935  				{ResourceName: "memory", RestartPolicy: "NotRequired"},
  7936  				{ResourceName: "cpu", RestartPolicy: "RestartContainer"},
  7937  			},
  7938  		},
  7939  	}
  7940  
  7941  	var PodRestartPolicy core.RestartPolicy = "Always"
  7942  	if errs := validateContainers(successCase, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 {
  7943  		t.Errorf("expected success: %v", errs)
  7944  	}
  7945  
  7946  	capabilities.SetForTests(capabilities.Capabilities{
  7947  		AllowPrivileged: false,
  7948  	})
  7949  	errorCases := []struct {
  7950  		title, line    string
  7951  		containers     []core.Container
  7952  		expectedErrors field.ErrorList
  7953  	}{{
  7954  		"zero-length name",
  7955  		line(),
  7956  		[]core.Container{{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7957  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].name"}},
  7958  	}, {
  7959  		"zero-length-image",
  7960  		line(),
  7961  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7962  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  7963  	}, {
  7964  		"name > 63 characters",
  7965  		line(),
  7966  		[]core.Container{{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7967  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  7968  	}, {
  7969  		"name not a DNS label",
  7970  		line(),
  7971  		[]core.Container{{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7972  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].name"}},
  7973  	}, {
  7974  		"name not unique",
  7975  		line(),
  7976  		[]core.Container{
  7977  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7978  			{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7979  		},
  7980  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].name"}},
  7981  	}, {
  7982  		"zero-length image",
  7983  		line(),
  7984  		[]core.Container{{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  7985  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].image"}},
  7986  	}, {
  7987  		"host port not unique",
  7988  		line(),
  7989  		[]core.Container{
  7990  			{Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
  7991  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7992  			{Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
  7993  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  7994  		},
  7995  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "containers[1].ports[0].hostPort"}},
  7996  	}, {
  7997  		"invalid env var name",
  7998  		line(),
  7999  		[]core.Container{
  8000  			{Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8001  		},
  8002  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].env[0].name"}},
  8003  	}, {
  8004  		"unknown volume name",
  8005  		line(),
  8006  		[]core.Container{
  8007  			{Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}},
  8008  				ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
  8009  		},
  8010  		field.ErrorList{{Type: field.ErrorTypeNotFound, Field: "containers[0].volumeMounts[0].name"}},
  8011  	}, {
  8012  		"invalid lifecycle, no exec command.",
  8013  		line(),
  8014  		[]core.Container{{
  8015  			Name:  "life-123",
  8016  			Image: "image",
  8017  			Lifecycle: &core.Lifecycle{
  8018  				PreStop: &core.LifecycleHandler{
  8019  					Exec: &core.ExecAction{},
  8020  				},
  8021  			},
  8022  			ImagePullPolicy:          "IfNotPresent",
  8023  			TerminationMessagePolicy: "File",
  8024  		}},
  8025  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.exec.command"}},
  8026  	}, {
  8027  		"invalid lifecycle, no http path.",
  8028  		line(),
  8029  		[]core.Container{{
  8030  			Name:  "life-123",
  8031  			Image: "image",
  8032  			Lifecycle: &core.Lifecycle{
  8033  				PreStop: &core.LifecycleHandler{
  8034  					HTTPGet: &core.HTTPGetAction{
  8035  						Port:   intstr.FromInt32(80),
  8036  						Scheme: "HTTP",
  8037  					},
  8038  				},
  8039  			},
  8040  			ImagePullPolicy:          "IfNotPresent",
  8041  			TerminationMessagePolicy: "File",
  8042  		}},
  8043  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop.httpGet.path"}},
  8044  	}, {
  8045  		"invalid lifecycle, no http port.",
  8046  		line(),
  8047  		[]core.Container{{
  8048  			Name:  "life-123",
  8049  			Image: "image",
  8050  			Lifecycle: &core.Lifecycle{
  8051  				PreStop: &core.LifecycleHandler{
  8052  					HTTPGet: &core.HTTPGetAction{
  8053  						Path:   "/",
  8054  						Scheme: "HTTP",
  8055  					},
  8056  				},
  8057  			},
  8058  			ImagePullPolicy:          "IfNotPresent",
  8059  			TerminationMessagePolicy: "File",
  8060  		}},
  8061  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.httpGet.port"}},
  8062  	}, {
  8063  		"invalid lifecycle, no http scheme.",
  8064  		line(),
  8065  		[]core.Container{{
  8066  			Name:  "life-123",
  8067  			Image: "image",
  8068  			Lifecycle: &core.Lifecycle{
  8069  				PreStop: &core.LifecycleHandler{
  8070  					HTTPGet: &core.HTTPGetAction{
  8071  						Path: "/",
  8072  						Port: intstr.FromInt32(80),
  8073  					},
  8074  				},
  8075  			},
  8076  			ImagePullPolicy:          "IfNotPresent",
  8077  			TerminationMessagePolicy: "File",
  8078  		}},
  8079  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].lifecycle.preStop.httpGet.scheme"}},
  8080  	}, {
  8081  		"invalid lifecycle, no tcp socket port.",
  8082  		line(),
  8083  		[]core.Container{{
  8084  			Name:  "life-123",
  8085  			Image: "image",
  8086  			Lifecycle: &core.Lifecycle{
  8087  				PreStop: &core.LifecycleHandler{
  8088  					TCPSocket: &core.TCPSocketAction{},
  8089  				},
  8090  			},
  8091  			ImagePullPolicy:          "IfNotPresent",
  8092  			TerminationMessagePolicy: "File",
  8093  		}},
  8094  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8095  	}, {
  8096  		"invalid lifecycle, zero tcp socket port.",
  8097  		line(),
  8098  		[]core.Container{{
  8099  			Name:  "life-123",
  8100  			Image: "image",
  8101  			Lifecycle: &core.Lifecycle{
  8102  				PreStop: &core.LifecycleHandler{
  8103  					TCPSocket: &core.TCPSocketAction{
  8104  						Port: intstr.FromInt32(0),
  8105  					},
  8106  				},
  8107  			},
  8108  			ImagePullPolicy:          "IfNotPresent",
  8109  			TerminationMessagePolicy: "File",
  8110  		}},
  8111  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].lifecycle.preStop.tcpSocket.port"}},
  8112  	}, {
  8113  		"invalid lifecycle, no action.",
  8114  		line(),
  8115  		[]core.Container{{
  8116  			Name:  "life-123",
  8117  			Image: "image",
  8118  			Lifecycle: &core.Lifecycle{
  8119  				PreStop: &core.LifecycleHandler{},
  8120  			},
  8121  			ImagePullPolicy:          "IfNotPresent",
  8122  			TerminationMessagePolicy: "File",
  8123  		}},
  8124  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].lifecycle.preStop"}},
  8125  	}, {
  8126  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  8127  		line(),
  8128  		[]core.Container{{
  8129  			Name:  "life-123",
  8130  			Image: "image",
  8131  			ReadinessProbe: &core.Probe{
  8132  				ProbeHandler: core.ProbeHandler{
  8133  					TCPSocket: &core.TCPSocketAction{
  8134  						Port: intstr.FromInt32(80),
  8135  					},
  8136  				},
  8137  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  8138  			},
  8139  			ImagePullPolicy:          "IfNotPresent",
  8140  			TerminationMessagePolicy: "File",
  8141  		}},
  8142  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"}},
  8143  	}, {
  8144  		"invalid liveness probe, no tcp socket port.",
  8145  		line(),
  8146  		[]core.Container{{
  8147  			Name:  "live-123",
  8148  			Image: "image",
  8149  			LivenessProbe: &core.Probe{
  8150  				ProbeHandler: core.ProbeHandler{
  8151  					TCPSocket: &core.TCPSocketAction{},
  8152  				},
  8153  				SuccessThreshold: 1,
  8154  			},
  8155  			ImagePullPolicy:          "IfNotPresent",
  8156  			TerminationMessagePolicy: "File",
  8157  		}},
  8158  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.tcpSocket.port"}},
  8159  	}, {
  8160  		"invalid liveness probe, no action.",
  8161  		line(),
  8162  		[]core.Container{{
  8163  			Name:  "live-123",
  8164  			Image: "image",
  8165  			LivenessProbe: &core.Probe{
  8166  				ProbeHandler:     core.ProbeHandler{},
  8167  				SuccessThreshold: 1,
  8168  			},
  8169  			ImagePullPolicy:          "IfNotPresent",
  8170  			TerminationMessagePolicy: "File",
  8171  		}},
  8172  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].livenessProbe"}},
  8173  	}, {
  8174  		"invalid liveness probe, successThreshold != 1",
  8175  		line(),
  8176  		[]core.Container{{
  8177  			Name:  "live-123",
  8178  			Image: "image",
  8179  			LivenessProbe: &core.Probe{
  8180  				ProbeHandler: core.ProbeHandler{
  8181  					TCPSocket: &core.TCPSocketAction{
  8182  						Port: intstr.FromInt32(80),
  8183  					},
  8184  				},
  8185  				SuccessThreshold: 2,
  8186  			},
  8187  			ImagePullPolicy:          "IfNotPresent",
  8188  			TerminationMessagePolicy: "File",
  8189  		}},
  8190  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"}},
  8191  	}, {
  8192  		"invalid startup probe, successThreshold != 1",
  8193  		line(),
  8194  		[]core.Container{{
  8195  			Name:  "startup-123",
  8196  			Image: "image",
  8197  			StartupProbe: &core.Probe{
  8198  				ProbeHandler: core.ProbeHandler{
  8199  					TCPSocket: &core.TCPSocketAction{
  8200  						Port: intstr.FromInt32(80),
  8201  					},
  8202  				},
  8203  				SuccessThreshold: 2,
  8204  			},
  8205  			ImagePullPolicy:          "IfNotPresent",
  8206  			TerminationMessagePolicy: "File",
  8207  		}},
  8208  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"}},
  8209  	}, {
  8210  		"invalid liveness probe, negative numbers",
  8211  		line(),
  8212  		[]core.Container{{
  8213  			Name:  "live-123",
  8214  			Image: "image",
  8215  			LivenessProbe: &core.Probe{
  8216  				ProbeHandler: core.ProbeHandler{
  8217  					TCPSocket: &core.TCPSocketAction{
  8218  						Port: intstr.FromInt32(80),
  8219  					},
  8220  				},
  8221  				InitialDelaySeconds:           -1,
  8222  				TimeoutSeconds:                -1,
  8223  				PeriodSeconds:                 -1,
  8224  				SuccessThreshold:              -1,
  8225  				FailureThreshold:              -1,
  8226  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8227  			},
  8228  			ImagePullPolicy:          "IfNotPresent",
  8229  			TerminationMessagePolicy: "File",
  8230  		}},
  8231  		field.ErrorList{
  8232  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.initialDelaySeconds"},
  8233  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.timeoutSeconds"},
  8234  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.periodSeconds"},
  8235  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8236  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.failureThreshold"},
  8237  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.terminationGracePeriodSeconds"},
  8238  			{Type: field.ErrorTypeInvalid, Field: "containers[0].livenessProbe.successThreshold"},
  8239  		},
  8240  	}, {
  8241  		"invalid readiness probe, negative numbers",
  8242  		line(),
  8243  		[]core.Container{{
  8244  			Name:  "ready-123",
  8245  			Image: "image",
  8246  			ReadinessProbe: &core.Probe{
  8247  				ProbeHandler: core.ProbeHandler{
  8248  					TCPSocket: &core.TCPSocketAction{
  8249  						Port: intstr.FromInt32(80),
  8250  					},
  8251  				},
  8252  				InitialDelaySeconds:           -1,
  8253  				TimeoutSeconds:                -1,
  8254  				PeriodSeconds:                 -1,
  8255  				SuccessThreshold:              -1,
  8256  				FailureThreshold:              -1,
  8257  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8258  			},
  8259  			ImagePullPolicy:          "IfNotPresent",
  8260  			TerminationMessagePolicy: "File",
  8261  		}},
  8262  		field.ErrorList{
  8263  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.initialDelaySeconds"},
  8264  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.timeoutSeconds"},
  8265  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.periodSeconds"},
  8266  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.successThreshold"},
  8267  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.failureThreshold"},
  8268  			// terminationGracePeriodSeconds returns multiple validation errors here:
  8269  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must be greater than 0
  8270  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8271  			// containers[0].readinessProbe.terminationGracePeriodSeconds: Invalid value: -1: must not be set for readinessProbes
  8272  			{Type: field.ErrorTypeInvalid, Field: "containers[0].readinessProbe.terminationGracePeriodSeconds"},
  8273  		},
  8274  	}, {
  8275  		"invalid startup probe, negative numbers",
  8276  		line(),
  8277  		[]core.Container{{
  8278  			Name:  "startup-123",
  8279  			Image: "image",
  8280  			StartupProbe: &core.Probe{
  8281  				ProbeHandler: core.ProbeHandler{
  8282  					TCPSocket: &core.TCPSocketAction{
  8283  						Port: intstr.FromInt32(80),
  8284  					},
  8285  				},
  8286  				InitialDelaySeconds:           -1,
  8287  				TimeoutSeconds:                -1,
  8288  				PeriodSeconds:                 -1,
  8289  				SuccessThreshold:              -1,
  8290  				FailureThreshold:              -1,
  8291  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8292  			},
  8293  			ImagePullPolicy:          "IfNotPresent",
  8294  			TerminationMessagePolicy: "File",
  8295  		}},
  8296  		field.ErrorList{
  8297  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.initialDelaySeconds"},
  8298  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.timeoutSeconds"},
  8299  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.periodSeconds"},
  8300  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8301  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.failureThreshold"},
  8302  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.terminationGracePeriodSeconds"},
  8303  			{Type: field.ErrorTypeInvalid, Field: "containers[0].startupProbe.successThreshold"},
  8304  		},
  8305  	}, {
  8306  		"invalid message termination policy",
  8307  		line(),
  8308  		[]core.Container{{
  8309  			Name:                     "life-123",
  8310  			Image:                    "image",
  8311  			ImagePullPolicy:          "IfNotPresent",
  8312  			TerminationMessagePolicy: "Unknown",
  8313  		}},
  8314  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].terminationMessagePolicy"}},
  8315  	}, {
  8316  		"empty message termination policy",
  8317  		line(),
  8318  		[]core.Container{{
  8319  			Name:                     "life-123",
  8320  			Image:                    "image",
  8321  			ImagePullPolicy:          "IfNotPresent",
  8322  			TerminationMessagePolicy: "",
  8323  		}},
  8324  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "containers[0].terminationMessagePolicy"}},
  8325  	}, {
  8326  		"privilege disabled",
  8327  		line(),
  8328  		[]core.Container{{
  8329  			Name:                     "abc",
  8330  			Image:                    "image",
  8331  			SecurityContext:          fakeValidSecurityContext(true),
  8332  			ImagePullPolicy:          "IfNotPresent",
  8333  			TerminationMessagePolicy: "File",
  8334  		}},
  8335  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].securityContext.privileged"}},
  8336  	}, {
  8337  		"invalid compute resource",
  8338  		line(),
  8339  		[]core.Container{{
  8340  			Name:  "abc-123",
  8341  			Image: "image",
  8342  			Resources: core.ResourceRequirements{
  8343  				Limits: core.ResourceList{
  8344  					"disk": resource.MustParse("10G"),
  8345  				},
  8346  			},
  8347  			ImagePullPolicy:          "IfNotPresent",
  8348  			TerminationMessagePolicy: "File",
  8349  		}},
  8350  		field.ErrorList{
  8351  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8352  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[disk]"},
  8353  		},
  8354  	}, {
  8355  		"Resource CPU invalid",
  8356  		line(),
  8357  		[]core.Container{{
  8358  			Name:  "abc-123",
  8359  			Image: "image",
  8360  			Resources: core.ResourceRequirements{
  8361  				Limits: getResourceLimits("-10", "0"),
  8362  			},
  8363  			ImagePullPolicy:          "IfNotPresent",
  8364  			TerminationMessagePolicy: "File",
  8365  		}},
  8366  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[cpu]"}},
  8367  	}, {
  8368  		"Resource Requests CPU invalid",
  8369  		line(),
  8370  		[]core.Container{{
  8371  			Name:  "abc-123",
  8372  			Image: "image",
  8373  			Resources: core.ResourceRequirements{
  8374  				Requests: getResourceLimits("-10", "0"),
  8375  			},
  8376  			ImagePullPolicy:          "IfNotPresent",
  8377  			TerminationMessagePolicy: "File",
  8378  		}},
  8379  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests[cpu]"}},
  8380  	}, {
  8381  		"Resource Memory invalid",
  8382  		line(),
  8383  		[]core.Container{{
  8384  			Name:  "abc-123",
  8385  			Image: "image",
  8386  			Resources: core.ResourceRequirements{
  8387  				Limits: getResourceLimits("0", "-10"),
  8388  			},
  8389  			ImagePullPolicy:          "IfNotPresent",
  8390  			TerminationMessagePolicy: "File",
  8391  		}},
  8392  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[memory]"}},
  8393  	}, {
  8394  		"Request limit simple invalid",
  8395  		line(),
  8396  		[]core.Container{{
  8397  			Name:  "abc-123",
  8398  			Image: "image",
  8399  			Resources: core.ResourceRequirements{
  8400  				Limits:   getResourceLimits("5", "3"),
  8401  				Requests: getResourceLimits("6", "3"),
  8402  			},
  8403  			ImagePullPolicy:          "IfNotPresent",
  8404  			TerminationMessagePolicy: "File",
  8405  		}},
  8406  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8407  	}, {
  8408  		"Invalid storage limit request",
  8409  		line(),
  8410  		[]core.Container{{
  8411  			Name:  "abc-123",
  8412  			Image: "image",
  8413  			Resources: core.ResourceRequirements{
  8414  				Limits: core.ResourceList{
  8415  					core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI),
  8416  				},
  8417  			},
  8418  			ImagePullPolicy:          "IfNotPresent",
  8419  			TerminationMessagePolicy: "File",
  8420  		}},
  8421  		field.ErrorList{
  8422  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  8423  			{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.limits[attachable-volumes-aws-ebs]"},
  8424  		},
  8425  	}, {
  8426  		"CPU request limit multiple invalid",
  8427  		line(),
  8428  		[]core.Container{{
  8429  			Name:  "abc-123",
  8430  			Image: "image",
  8431  			Resources: core.ResourceRequirements{
  8432  				Limits:   getResourceLimits("5", "3"),
  8433  				Requests: getResourceLimits("6", "3"),
  8434  			},
  8435  			ImagePullPolicy:          "IfNotPresent",
  8436  			TerminationMessagePolicy: "File",
  8437  		}},
  8438  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8439  	}, {
  8440  		"Memory request limit multiple invalid",
  8441  		line(),
  8442  		[]core.Container{{
  8443  			Name:  "abc-123",
  8444  			Image: "image",
  8445  			Resources: core.ResourceRequirements{
  8446  				Limits:   getResourceLimits("5", "3"),
  8447  				Requests: getResourceLimits("5", "4"),
  8448  			},
  8449  			ImagePullPolicy:          "IfNotPresent",
  8450  			TerminationMessagePolicy: "File",
  8451  		}},
  8452  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].resources.requests"}},
  8453  	}, {
  8454  		"Invalid env from",
  8455  		line(),
  8456  		[]core.Container{{
  8457  			Name:                     "env-from-source",
  8458  			Image:                    "image",
  8459  			ImagePullPolicy:          "IfNotPresent",
  8460  			TerminationMessagePolicy: "File",
  8461  			EnvFrom: []core.EnvFromSource{{
  8462  				ConfigMapRef: &core.ConfigMapEnvSource{
  8463  					LocalObjectReference: core.LocalObjectReference{
  8464  						Name: "$%^&*#",
  8465  					},
  8466  				},
  8467  			}},
  8468  		}},
  8469  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "containers[0].envFrom[0].configMapRef.name"}},
  8470  	}, {
  8471  		"Unsupported resize policy for memory",
  8472  		line(),
  8473  		[]core.Container{{
  8474  			Name:                     "resize-policy-mem-invalid",
  8475  			Image:                    "image",
  8476  			ImagePullPolicy:          "IfNotPresent",
  8477  			TerminationMessagePolicy: "File",
  8478  			ResizePolicy: []core.ContainerResizePolicy{
  8479  				{ResourceName: "memory", RestartPolicy: "RestartContainerrrr"},
  8480  			},
  8481  		}},
  8482  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  8483  	}, {
  8484  		"Unsupported resize policy for CPU",
  8485  		line(),
  8486  		[]core.Container{{
  8487  			Name:                     "resize-policy-cpu-invalid",
  8488  			Image:                    "image",
  8489  			ImagePullPolicy:          "IfNotPresent",
  8490  			TerminationMessagePolicy: "File",
  8491  			ResizePolicy: []core.ContainerResizePolicy{
  8492  				{ResourceName: "cpu", RestartPolicy: "RestartNotRequired"},
  8493  			},
  8494  		}},
  8495  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "containers[0].resizePolicy"}},
  8496  	}, {
  8497  		"Forbidden RestartPolicy: Always",
  8498  		line(),
  8499  		[]core.Container{{
  8500  			Name:                     "foo",
  8501  			Image:                    "image",
  8502  			ImagePullPolicy:          "IfNotPresent",
  8503  			TerminationMessagePolicy: "File",
  8504  			RestartPolicy:            &containerRestartPolicyAlways,
  8505  		}},
  8506  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  8507  	}, {
  8508  		"Forbidden RestartPolicy: OnFailure",
  8509  		line(),
  8510  		[]core.Container{{
  8511  			Name:                     "foo",
  8512  			Image:                    "image",
  8513  			ImagePullPolicy:          "IfNotPresent",
  8514  			TerminationMessagePolicy: "File",
  8515  			RestartPolicy:            &containerRestartPolicyOnFailure,
  8516  		}},
  8517  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  8518  	}, {
  8519  		"Forbidden RestartPolicy: Never",
  8520  		line(),
  8521  		[]core.Container{{
  8522  			Name:                     "foo",
  8523  			Image:                    "image",
  8524  			ImagePullPolicy:          "IfNotPresent",
  8525  			TerminationMessagePolicy: "File",
  8526  			RestartPolicy:            &containerRestartPolicyNever,
  8527  		}},
  8528  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  8529  	}, {
  8530  		"Forbidden RestartPolicy: invalid",
  8531  		line(),
  8532  		[]core.Container{{
  8533  			Name:                     "foo",
  8534  			Image:                    "image",
  8535  			ImagePullPolicy:          "IfNotPresent",
  8536  			TerminationMessagePolicy: "File",
  8537  			RestartPolicy:            &containerRestartPolicyInvalid,
  8538  		}},
  8539  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  8540  	}, {
  8541  		"Forbidden RestartPolicy: empty",
  8542  		line(),
  8543  		[]core.Container{{
  8544  			Name:                     "foo",
  8545  			Image:                    "image",
  8546  			ImagePullPolicy:          "IfNotPresent",
  8547  			TerminationMessagePolicy: "File",
  8548  			RestartPolicy:            &containerRestartPolicyEmpty,
  8549  		}},
  8550  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "containers[0].restartPolicy"}},
  8551  	},
  8552  	}
  8553  
  8554  	for _, tc := range errorCases {
  8555  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  8556  			errs := validateContainers(tc.containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("containers"), PodValidationOptions{}, &PodRestartPolicy)
  8557  			if len(errs) == 0 {
  8558  				t.Fatal("expected error but received none")
  8559  			}
  8560  
  8561  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
  8562  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  8563  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  8564  			}
  8565  		})
  8566  	}
  8567  }
  8568  
  8569  func TestValidateInitContainers(t *testing.T) {
  8570  	volumeDevices := make(map[string]core.VolumeSource)
  8571  	capabilities.SetForTests(capabilities.Capabilities{
  8572  		AllowPrivileged: true,
  8573  	})
  8574  
  8575  	containers := []core.Container{{
  8576  		Name:                     "app",
  8577  		Image:                    "nginx",
  8578  		ImagePullPolicy:          "IfNotPresent",
  8579  		TerminationMessagePolicy: "File",
  8580  	},
  8581  	}
  8582  
  8583  	successCase := []core.Container{{
  8584  		Name:  "container-1-same-host-port-different-protocol",
  8585  		Image: "image",
  8586  		Ports: []core.ContainerPort{
  8587  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  8588  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  8589  		},
  8590  		ImagePullPolicy:          "IfNotPresent",
  8591  		TerminationMessagePolicy: "File",
  8592  	}, {
  8593  		Name:  "container-2-same-host-port-different-protocol",
  8594  		Image: "image",
  8595  		Ports: []core.ContainerPort{
  8596  			{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
  8597  			{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
  8598  		},
  8599  		ImagePullPolicy:          "IfNotPresent",
  8600  		TerminationMessagePolicy: "File",
  8601  	}, {
  8602  		Name:                     "container-3-restart-always-with-lifecycle-hook-and-probes",
  8603  		Image:                    "image",
  8604  		ImagePullPolicy:          "IfNotPresent",
  8605  		TerminationMessagePolicy: "File",
  8606  		RestartPolicy:            &containerRestartPolicyAlways,
  8607  		Lifecycle: &core.Lifecycle{
  8608  			PostStart: &core.LifecycleHandler{
  8609  				Exec: &core.ExecAction{
  8610  					Command: []string{"echo", "post start"},
  8611  				},
  8612  			},
  8613  			PreStop: &core.LifecycleHandler{
  8614  				Exec: &core.ExecAction{
  8615  					Command: []string{"echo", "pre stop"},
  8616  				},
  8617  			},
  8618  		},
  8619  		LivenessProbe: &core.Probe{
  8620  			ProbeHandler: core.ProbeHandler{
  8621  				TCPSocket: &core.TCPSocketAction{
  8622  					Port: intstr.FromInt32(80),
  8623  				},
  8624  			},
  8625  			SuccessThreshold: 1,
  8626  		},
  8627  		ReadinessProbe: &core.Probe{
  8628  			ProbeHandler: core.ProbeHandler{
  8629  				TCPSocket: &core.TCPSocketAction{
  8630  					Port: intstr.FromInt32(80),
  8631  				},
  8632  			},
  8633  		},
  8634  		StartupProbe: &core.Probe{
  8635  			ProbeHandler: core.ProbeHandler{
  8636  				TCPSocket: &core.TCPSocketAction{
  8637  					Port: intstr.FromInt32(80),
  8638  				},
  8639  			},
  8640  			SuccessThreshold: 1,
  8641  		},
  8642  	},
  8643  	}
  8644  	var PodRestartPolicy core.RestartPolicy = "Never"
  8645  	if errs := validateInitContainers(successCase, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{}, &PodRestartPolicy); len(errs) != 0 {
  8646  		t.Errorf("expected success: %v", errs)
  8647  	}
  8648  
  8649  	capabilities.SetForTests(capabilities.Capabilities{
  8650  		AllowPrivileged: false,
  8651  	})
  8652  	errorCases := []struct {
  8653  		title, line    string
  8654  		initContainers []core.Container
  8655  		expectedErrors field.ErrorList
  8656  	}{{
  8657  		"empty name",
  8658  		line(),
  8659  		[]core.Container{{
  8660  			Name:                     "",
  8661  			Image:                    "image",
  8662  			ImagePullPolicy:          "IfNotPresent",
  8663  			TerminationMessagePolicy: "File",
  8664  		}},
  8665  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].name", BadValue: ""}},
  8666  	}, {
  8667  		"name collision with regular container",
  8668  		line(),
  8669  		[]core.Container{{
  8670  			Name:                     "app",
  8671  			Image:                    "image",
  8672  			ImagePullPolicy:          "IfNotPresent",
  8673  			TerminationMessagePolicy: "File",
  8674  		}},
  8675  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].name", BadValue: "app"}},
  8676  	}, {
  8677  		"invalid termination message policy",
  8678  		line(),
  8679  		[]core.Container{{
  8680  			Name:                     "init",
  8681  			Image:                    "image",
  8682  			ImagePullPolicy:          "IfNotPresent",
  8683  			TerminationMessagePolicy: "Unknown",
  8684  		}},
  8685  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].terminationMessagePolicy", BadValue: core.TerminationMessagePolicy("Unknown")}},
  8686  	}, {
  8687  		"duplicate names",
  8688  		line(),
  8689  		[]core.Container{{
  8690  			Name:                     "init",
  8691  			Image:                    "image",
  8692  			ImagePullPolicy:          "IfNotPresent",
  8693  			TerminationMessagePolicy: "File",
  8694  		}, {
  8695  			Name:                     "init",
  8696  			Image:                    "image",
  8697  			ImagePullPolicy:          "IfNotPresent",
  8698  			TerminationMessagePolicy: "File",
  8699  		}},
  8700  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[1].name", BadValue: "init"}},
  8701  	}, {
  8702  		"duplicate ports",
  8703  		line(),
  8704  		[]core.Container{{
  8705  			Name:  "abc",
  8706  			Image: "image",
  8707  			Ports: []core.ContainerPort{{
  8708  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  8709  			}, {
  8710  				ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
  8711  			}},
  8712  			ImagePullPolicy:          "IfNotPresent",
  8713  			TerminationMessagePolicy: "File",
  8714  		}},
  8715  		field.ErrorList{{Type: field.ErrorTypeDuplicate, Field: "initContainers[0].ports[1].hostPort", BadValue: "TCP//8080"}},
  8716  	}, {
  8717  		"uses disallowed field: Lifecycle",
  8718  		line(),
  8719  		[]core.Container{{
  8720  			Name:                     "debug",
  8721  			Image:                    "image",
  8722  			ImagePullPolicy:          "IfNotPresent",
  8723  			TerminationMessagePolicy: "File",
  8724  			Lifecycle: &core.Lifecycle{
  8725  				PreStop: &core.LifecycleHandler{
  8726  					Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
  8727  				},
  8728  			},
  8729  		}},
  8730  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].lifecycle", BadValue: ""}},
  8731  	}, {
  8732  		"uses disallowed field: LivenessProbe",
  8733  		line(),
  8734  		[]core.Container{{
  8735  			Name:                     "debug",
  8736  			Image:                    "image",
  8737  			ImagePullPolicy:          "IfNotPresent",
  8738  			TerminationMessagePolicy: "File",
  8739  			LivenessProbe: &core.Probe{
  8740  				ProbeHandler: core.ProbeHandler{
  8741  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8742  				},
  8743  				SuccessThreshold: 1,
  8744  			},
  8745  		}},
  8746  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].livenessProbe", BadValue: ""}},
  8747  	}, {
  8748  		"uses disallowed field: ReadinessProbe",
  8749  		line(),
  8750  		[]core.Container{{
  8751  			Name:                     "debug",
  8752  			Image:                    "image",
  8753  			ImagePullPolicy:          "IfNotPresent",
  8754  			TerminationMessagePolicy: "File",
  8755  			ReadinessProbe: &core.Probe{
  8756  				ProbeHandler: core.ProbeHandler{
  8757  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8758  				},
  8759  			},
  8760  		}},
  8761  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].readinessProbe", BadValue: ""}},
  8762  	}, {
  8763  		"Container uses disallowed field: StartupProbe",
  8764  		line(),
  8765  		[]core.Container{{
  8766  			Name:                     "debug",
  8767  			Image:                    "image",
  8768  			ImagePullPolicy:          "IfNotPresent",
  8769  			TerminationMessagePolicy: "File",
  8770  			StartupProbe: &core.Probe{
  8771  				ProbeHandler: core.ProbeHandler{
  8772  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8773  				},
  8774  				SuccessThreshold: 1,
  8775  			},
  8776  		}},
  8777  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  8778  	}, {
  8779  		"Disallowed field with other errors should only return a single Forbidden",
  8780  		line(),
  8781  		[]core.Container{{
  8782  			Name:                     "debug",
  8783  			Image:                    "image",
  8784  			ImagePullPolicy:          "IfNotPresent",
  8785  			TerminationMessagePolicy: "File",
  8786  			StartupProbe: &core.Probe{
  8787  				ProbeHandler: core.ProbeHandler{
  8788  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8789  				},
  8790  				InitialDelaySeconds:           -1,
  8791  				TimeoutSeconds:                -1,
  8792  				PeriodSeconds:                 -1,
  8793  				SuccessThreshold:              -1,
  8794  				FailureThreshold:              -1,
  8795  				TerminationGracePeriodSeconds: utilpointer.Int64(-1),
  8796  			},
  8797  		}},
  8798  		field.ErrorList{{Type: field.ErrorTypeForbidden, Field: "initContainers[0].startupProbe", BadValue: ""}},
  8799  	}, {
  8800  		"Not supported RestartPolicy: OnFailure",
  8801  		line(),
  8802  		[]core.Container{{
  8803  			Name:                     "init",
  8804  			Image:                    "image",
  8805  			ImagePullPolicy:          "IfNotPresent",
  8806  			TerminationMessagePolicy: "File",
  8807  			RestartPolicy:            &containerRestartPolicyOnFailure,
  8808  		}},
  8809  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyOnFailure}},
  8810  	}, {
  8811  		"Not supported RestartPolicy: Never",
  8812  		line(),
  8813  		[]core.Container{{
  8814  			Name:                     "init",
  8815  			Image:                    "image",
  8816  			ImagePullPolicy:          "IfNotPresent",
  8817  			TerminationMessagePolicy: "File",
  8818  			RestartPolicy:            &containerRestartPolicyNever,
  8819  		}},
  8820  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyNever}},
  8821  	}, {
  8822  		"Not supported RestartPolicy: invalid",
  8823  		line(),
  8824  		[]core.Container{{
  8825  			Name:                     "init",
  8826  			Image:                    "image",
  8827  			ImagePullPolicy:          "IfNotPresent",
  8828  			TerminationMessagePolicy: "File",
  8829  			RestartPolicy:            &containerRestartPolicyInvalid,
  8830  		}},
  8831  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyInvalid}},
  8832  	}, {
  8833  		"Not supported RestartPolicy: empty",
  8834  		line(),
  8835  		[]core.Container{{
  8836  			Name:                     "init",
  8837  			Image:                    "image",
  8838  			ImagePullPolicy:          "IfNotPresent",
  8839  			TerminationMessagePolicy: "File",
  8840  			RestartPolicy:            &containerRestartPolicyEmpty,
  8841  		}},
  8842  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].restartPolicy", BadValue: containerRestartPolicyEmpty}},
  8843  	}, {
  8844  		"invalid startup probe in restartable container, successThreshold != 1",
  8845  		line(),
  8846  		[]core.Container{{
  8847  			Name:                     "restartable-init",
  8848  			Image:                    "image",
  8849  			ImagePullPolicy:          "IfNotPresent",
  8850  			TerminationMessagePolicy: "File",
  8851  			RestartPolicy:            &containerRestartPolicyAlways,
  8852  			StartupProbe: &core.Probe{
  8853  				ProbeHandler: core.ProbeHandler{
  8854  					TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt32(80)},
  8855  				},
  8856  				SuccessThreshold: 2,
  8857  			},
  8858  		}},
  8859  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}},
  8860  	}, {
  8861  		"invalid readiness probe, terminationGracePeriodSeconds set.",
  8862  		line(),
  8863  		[]core.Container{{
  8864  			Name:                     "life-123",
  8865  			Image:                    "image",
  8866  			ImagePullPolicy:          "IfNotPresent",
  8867  			TerminationMessagePolicy: "File",
  8868  			RestartPolicy:            &containerRestartPolicyAlways,
  8869  			ReadinessProbe: &core.Probe{
  8870  				ProbeHandler: core.ProbeHandler{
  8871  					TCPSocket: &core.TCPSocketAction{
  8872  						Port: intstr.FromInt32(80),
  8873  					},
  8874  				},
  8875  				TerminationGracePeriodSeconds: utilpointer.Int64(10),
  8876  			},
  8877  		}},
  8878  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}},
  8879  	}, {
  8880  		"invalid liveness probe, successThreshold != 1",
  8881  		line(),
  8882  		[]core.Container{{
  8883  			Name:                     "live-123",
  8884  			Image:                    "image",
  8885  			ImagePullPolicy:          "IfNotPresent",
  8886  			TerminationMessagePolicy: "File",
  8887  			RestartPolicy:            &containerRestartPolicyAlways,
  8888  			LivenessProbe: &core.Probe{
  8889  				ProbeHandler: core.ProbeHandler{
  8890  					TCPSocket: &core.TCPSocketAction{
  8891  						Port: intstr.FromInt32(80),
  8892  					},
  8893  				},
  8894  				SuccessThreshold: 2,
  8895  			},
  8896  		}},
  8897  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}},
  8898  	}, {
  8899  		"invalid lifecycle, no exec command.",
  8900  		line(),
  8901  		[]core.Container{{
  8902  			Name:                     "life-123",
  8903  			Image:                    "image",
  8904  			ImagePullPolicy:          "IfNotPresent",
  8905  			TerminationMessagePolicy: "File",
  8906  			RestartPolicy:            &containerRestartPolicyAlways,
  8907  			Lifecycle: &core.Lifecycle{
  8908  				PreStop: &core.LifecycleHandler{
  8909  					Exec: &core.ExecAction{},
  8910  				},
  8911  			},
  8912  		}},
  8913  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}},
  8914  	}, {
  8915  		"invalid lifecycle, no http path.",
  8916  		line(),
  8917  		[]core.Container{{
  8918  			Name:                     "life-123",
  8919  			Image:                    "image",
  8920  			ImagePullPolicy:          "IfNotPresent",
  8921  			TerminationMessagePolicy: "File",
  8922  			RestartPolicy:            &containerRestartPolicyAlways,
  8923  			Lifecycle: &core.Lifecycle{
  8924  				PreStop: &core.LifecycleHandler{
  8925  					HTTPGet: &core.HTTPGetAction{
  8926  						Port:   intstr.FromInt32(80),
  8927  						Scheme: "HTTP",
  8928  					},
  8929  				},
  8930  			},
  8931  		}},
  8932  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}},
  8933  	}, {
  8934  		"invalid lifecycle, no http port.",
  8935  		line(),
  8936  		[]core.Container{{
  8937  			Name:                     "life-123",
  8938  			Image:                    "image",
  8939  			ImagePullPolicy:          "IfNotPresent",
  8940  			TerminationMessagePolicy: "File",
  8941  			RestartPolicy:            &containerRestartPolicyAlways,
  8942  			Lifecycle: &core.Lifecycle{
  8943  				PreStop: &core.LifecycleHandler{
  8944  					HTTPGet: &core.HTTPGetAction{
  8945  						Path:   "/",
  8946  						Scheme: "HTTP",
  8947  					},
  8948  				},
  8949  			},
  8950  		}},
  8951  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}},
  8952  	}, {
  8953  		"invalid lifecycle, no http scheme.",
  8954  		line(),
  8955  		[]core.Container{{
  8956  			Name:                     "life-123",
  8957  			Image:                    "image",
  8958  			ImagePullPolicy:          "IfNotPresent",
  8959  			TerminationMessagePolicy: "File",
  8960  			RestartPolicy:            &containerRestartPolicyAlways,
  8961  			Lifecycle: &core.Lifecycle{
  8962  				PreStop: &core.LifecycleHandler{
  8963  					HTTPGet: &core.HTTPGetAction{
  8964  						Path: "/",
  8965  						Port: intstr.FromInt32(80),
  8966  					},
  8967  				},
  8968  			},
  8969  		}},
  8970  		field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}},
  8971  	}, {
  8972  		"invalid lifecycle, no tcp socket port.",
  8973  		line(),
  8974  		[]core.Container{{
  8975  			Name:                     "life-123",
  8976  			Image:                    "image",
  8977  			ImagePullPolicy:          "IfNotPresent",
  8978  			TerminationMessagePolicy: "File",
  8979  			RestartPolicy:            &containerRestartPolicyAlways,
  8980  			Lifecycle: &core.Lifecycle{
  8981  				PreStop: &core.LifecycleHandler{
  8982  					TCPSocket: &core.TCPSocketAction{},
  8983  				},
  8984  			},
  8985  		}},
  8986  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  8987  	}, {
  8988  		"invalid lifecycle, zero tcp socket port.",
  8989  		line(),
  8990  		[]core.Container{{
  8991  			Name:                     "life-123",
  8992  			Image:                    "image",
  8993  			ImagePullPolicy:          "IfNotPresent",
  8994  			TerminationMessagePolicy: "File",
  8995  			RestartPolicy:            &containerRestartPolicyAlways,
  8996  			Lifecycle: &core.Lifecycle{
  8997  				PreStop: &core.LifecycleHandler{
  8998  					TCPSocket: &core.TCPSocketAction{
  8999  						Port: intstr.FromInt32(0),
  9000  					},
  9001  				},
  9002  			},
  9003  		}},
  9004  		field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
  9005  	}, {
  9006  		"invalid lifecycle, no action.",
  9007  		line(),
  9008  		[]core.Container{{
  9009  			Name:                     "life-123",
  9010  			Image:                    "image",
  9011  			ImagePullPolicy:          "IfNotPresent",
  9012  			TerminationMessagePolicy: "File",
  9013  			RestartPolicy:            &containerRestartPolicyAlways,
  9014  			Lifecycle: &core.Lifecycle{
  9015  				PreStop: &core.LifecycleHandler{},
  9016  			},
  9017  		}},
  9018  		field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
  9019  	},
  9020  	}
  9021  
  9022  	for _, tc := range errorCases {
  9023  		t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
  9024  			errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("initContainers"), PodValidationOptions{}, &PodRestartPolicy)
  9025  			if len(errs) == 0 {
  9026  				t.Fatal("expected error but received none")
  9027  			}
  9028  
  9029  			if diff := cmp.Diff(tc.expectedErrors, errs, cmpopts.IgnoreFields(field.Error{}, "Detail")); diff != "" {
  9030  				t.Errorf("unexpected diff in errors (-want, +got):\n%s", diff)
  9031  				t.Errorf("INFO: all errors:\n%s", prettyErrorList(errs))
  9032  			}
  9033  		})
  9034  	}
  9035  }
  9036  
  9037  func TestValidateRestartPolicy(t *testing.T) {
  9038  	successCases := []core.RestartPolicy{
  9039  		core.RestartPolicyAlways,
  9040  		core.RestartPolicyOnFailure,
  9041  		core.RestartPolicyNever,
  9042  	}
  9043  	for _, policy := range successCases {
  9044  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9045  			t.Errorf("expected success: %v", errs)
  9046  		}
  9047  	}
  9048  
  9049  	errorCases := []core.RestartPolicy{"", "newpolicy"}
  9050  
  9051  	for k, policy := range errorCases {
  9052  		if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9053  			t.Errorf("expected failure for %d", k)
  9054  		}
  9055  	}
  9056  }
  9057  
  9058  func TestValidateDNSPolicy(t *testing.T) {
  9059  	successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSClusterFirstWithHostNet, core.DNSNone}
  9060  	for _, policy := range successCases {
  9061  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
  9062  			t.Errorf("expected success: %v", errs)
  9063  		}
  9064  	}
  9065  
  9066  	errorCases := []core.DNSPolicy{core.DNSPolicy("invalid"), core.DNSPolicy("")}
  9067  	for _, policy := range errorCases {
  9068  		if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
  9069  			t.Errorf("expected failure for %v", policy)
  9070  		}
  9071  	}
  9072  }
  9073  
  9074  func TestValidatePodDNSConfig(t *testing.T) {
  9075  	generateTestSearchPathFunc := func(numChars int) string {
  9076  		res := ""
  9077  		for i := 0; i < numChars; i++ {
  9078  			res = res + "a"
  9079  		}
  9080  		return res
  9081  	}
  9082  	testOptionValue := "2"
  9083  	testDNSNone := core.DNSNone
  9084  	testDNSClusterFirst := core.DNSClusterFirst
  9085  
  9086  	testCases := []struct {
  9087  		desc          string
  9088  		dnsConfig     *core.PodDNSConfig
  9089  		dnsPolicy     *core.DNSPolicy
  9090  		opts          PodValidationOptions
  9091  		expectedError bool
  9092  	}{{
  9093  		desc:          "valid: empty DNSConfig",
  9094  		dnsConfig:     &core.PodDNSConfig{},
  9095  		expectedError: false,
  9096  	}, {
  9097  		desc: "valid: 1 option",
  9098  		dnsConfig: &core.PodDNSConfig{
  9099  			Options: []core.PodDNSConfigOption{
  9100  				{Name: "ndots", Value: &testOptionValue},
  9101  			},
  9102  		},
  9103  		expectedError: false,
  9104  	}, {
  9105  		desc: "valid: 1 nameserver",
  9106  		dnsConfig: &core.PodDNSConfig{
  9107  			Nameservers: []string{"127.0.0.1"},
  9108  		},
  9109  		expectedError: false,
  9110  	}, {
  9111  		desc: "valid: DNSNone with 1 nameserver",
  9112  		dnsConfig: &core.PodDNSConfig{
  9113  			Nameservers: []string{"127.0.0.1"},
  9114  		},
  9115  		dnsPolicy:     &testDNSNone,
  9116  		expectedError: false,
  9117  	}, {
  9118  		desc: "valid: 1 search path",
  9119  		dnsConfig: &core.PodDNSConfig{
  9120  			Searches: []string{"custom"},
  9121  		},
  9122  		expectedError: false,
  9123  	}, {
  9124  		desc: "valid: 1 search path with trailing period",
  9125  		dnsConfig: &core.PodDNSConfig{
  9126  			Searches: []string{"custom."},
  9127  		},
  9128  		expectedError: false,
  9129  	}, {
  9130  		desc: "valid: 3 nameservers and 6 search paths(legacy)",
  9131  		dnsConfig: &core.PodDNSConfig{
  9132  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9133  			Searches:    []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."},
  9134  		},
  9135  		expectedError: false,
  9136  	}, {
  9137  		desc: "valid: 3 nameservers and 32 search paths",
  9138  		dnsConfig: &core.PodDNSConfig{
  9139  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
  9140  			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"},
  9141  		},
  9142  		expectedError: false,
  9143  	}, {
  9144  		desc: "valid: 256 characters in search path list(legacy)",
  9145  		dnsConfig: &core.PodDNSConfig{
  9146  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9147  			Searches: []string{
  9148  				generateTestSearchPathFunc(1),
  9149  				generateTestSearchPathFunc(50),
  9150  				generateTestSearchPathFunc(50),
  9151  				generateTestSearchPathFunc(50),
  9152  				generateTestSearchPathFunc(50),
  9153  				generateTestSearchPathFunc(50),
  9154  			},
  9155  		},
  9156  		expectedError: false,
  9157  	}, {
  9158  		desc: "valid: 2048 characters in search path list",
  9159  		dnsConfig: &core.PodDNSConfig{
  9160  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9161  			Searches: []string{
  9162  				generateTestSearchPathFunc(64),
  9163  				generateTestSearchPathFunc(63),
  9164  				generateTestSearchPathFunc(63),
  9165  				generateTestSearchPathFunc(63),
  9166  				generateTestSearchPathFunc(63),
  9167  				generateTestSearchPathFunc(63),
  9168  				generateTestSearchPathFunc(63),
  9169  				generateTestSearchPathFunc(63),
  9170  				generateTestSearchPathFunc(63),
  9171  				generateTestSearchPathFunc(63),
  9172  				generateTestSearchPathFunc(63),
  9173  				generateTestSearchPathFunc(63),
  9174  				generateTestSearchPathFunc(63),
  9175  				generateTestSearchPathFunc(63),
  9176  				generateTestSearchPathFunc(63),
  9177  				generateTestSearchPathFunc(63),
  9178  				generateTestSearchPathFunc(63),
  9179  				generateTestSearchPathFunc(63),
  9180  				generateTestSearchPathFunc(63),
  9181  				generateTestSearchPathFunc(63),
  9182  				generateTestSearchPathFunc(63),
  9183  				generateTestSearchPathFunc(63),
  9184  				generateTestSearchPathFunc(63),
  9185  				generateTestSearchPathFunc(63),
  9186  				generateTestSearchPathFunc(63),
  9187  				generateTestSearchPathFunc(63),
  9188  				generateTestSearchPathFunc(63),
  9189  				generateTestSearchPathFunc(63),
  9190  				generateTestSearchPathFunc(63),
  9191  				generateTestSearchPathFunc(63),
  9192  				generateTestSearchPathFunc(63),
  9193  				generateTestSearchPathFunc(63),
  9194  			},
  9195  		},
  9196  		expectedError: false,
  9197  	}, {
  9198  		desc: "valid: ipv6 nameserver",
  9199  		dnsConfig: &core.PodDNSConfig{
  9200  			Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
  9201  		},
  9202  		expectedError: false,
  9203  	}, {
  9204  		desc: "invalid: 4 nameservers",
  9205  		dnsConfig: &core.PodDNSConfig{
  9206  			Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
  9207  		},
  9208  		expectedError: true,
  9209  	}, {
  9210  		desc: "valid: 7 search paths",
  9211  		dnsConfig: &core.PodDNSConfig{
  9212  			Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
  9213  		},
  9214  	}, {
  9215  		desc: "invalid: 33 search paths",
  9216  		dnsConfig: &core.PodDNSConfig{
  9217  			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"},
  9218  		},
  9219  		expectedError: true,
  9220  	}, {
  9221  		desc: "valid: 257 characters in search path list",
  9222  		dnsConfig: &core.PodDNSConfig{
  9223  			// We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
  9224  			Searches: []string{
  9225  				generateTestSearchPathFunc(2),
  9226  				generateTestSearchPathFunc(50),
  9227  				generateTestSearchPathFunc(50),
  9228  				generateTestSearchPathFunc(50),
  9229  				generateTestSearchPathFunc(50),
  9230  				generateTestSearchPathFunc(50),
  9231  			},
  9232  		},
  9233  	}, {
  9234  		desc: "invalid: 2049 characters in search path list",
  9235  		dnsConfig: &core.PodDNSConfig{
  9236  			// We can have 2048 - (32 - 1) = 2017 characters in total for 32 search paths.
  9237  			Searches: []string{
  9238  				generateTestSearchPathFunc(65),
  9239  				generateTestSearchPathFunc(63),
  9240  				generateTestSearchPathFunc(63),
  9241  				generateTestSearchPathFunc(63),
  9242  				generateTestSearchPathFunc(63),
  9243  				generateTestSearchPathFunc(63),
  9244  				generateTestSearchPathFunc(63),
  9245  				generateTestSearchPathFunc(63),
  9246  				generateTestSearchPathFunc(63),
  9247  				generateTestSearchPathFunc(63),
  9248  				generateTestSearchPathFunc(63),
  9249  				generateTestSearchPathFunc(63),
  9250  				generateTestSearchPathFunc(63),
  9251  				generateTestSearchPathFunc(63),
  9252  				generateTestSearchPathFunc(63),
  9253  				generateTestSearchPathFunc(63),
  9254  				generateTestSearchPathFunc(63),
  9255  				generateTestSearchPathFunc(63),
  9256  				generateTestSearchPathFunc(63),
  9257  				generateTestSearchPathFunc(63),
  9258  				generateTestSearchPathFunc(63),
  9259  				generateTestSearchPathFunc(63),
  9260  				generateTestSearchPathFunc(63),
  9261  				generateTestSearchPathFunc(63),
  9262  				generateTestSearchPathFunc(63),
  9263  				generateTestSearchPathFunc(63),
  9264  				generateTestSearchPathFunc(63),
  9265  				generateTestSearchPathFunc(63),
  9266  				generateTestSearchPathFunc(63),
  9267  				generateTestSearchPathFunc(63),
  9268  				generateTestSearchPathFunc(63),
  9269  				generateTestSearchPathFunc(63),
  9270  			},
  9271  		},
  9272  		expectedError: true,
  9273  	}, {
  9274  		desc: "invalid search path",
  9275  		dnsConfig: &core.PodDNSConfig{
  9276  			Searches: []string{"custom?"},
  9277  		},
  9278  		expectedError: true,
  9279  	}, {
  9280  		desc: "invalid nameserver",
  9281  		dnsConfig: &core.PodDNSConfig{
  9282  			Nameservers: []string{"invalid"},
  9283  		},
  9284  		expectedError: true,
  9285  	}, {
  9286  		desc: "invalid empty option name",
  9287  		dnsConfig: &core.PodDNSConfig{
  9288  			Options: []core.PodDNSConfigOption{
  9289  				{Value: &testOptionValue},
  9290  			},
  9291  		},
  9292  		expectedError: true,
  9293  	}, {
  9294  		desc: "invalid: DNSNone with 0 nameserver",
  9295  		dnsConfig: &core.PodDNSConfig{
  9296  			Searches: []string{"custom"},
  9297  		},
  9298  		dnsPolicy:     &testDNSNone,
  9299  		expectedError: true,
  9300  	},
  9301  	}
  9302  
  9303  	for _, tc := range testCases {
  9304  		if tc.dnsPolicy == nil {
  9305  			tc.dnsPolicy = &testDNSClusterFirst
  9306  		}
  9307  
  9308  		errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"), tc.opts)
  9309  		if len(errs) != 0 && !tc.expectedError {
  9310  			t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
  9311  		} else if len(errs) == 0 && tc.expectedError {
  9312  			t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
  9313  		}
  9314  	}
  9315  }
  9316  
  9317  func TestValidatePodReadinessGates(t *testing.T) {
  9318  	successCases := []struct {
  9319  		desc           string
  9320  		readinessGates []core.PodReadinessGate
  9321  	}{{
  9322  		"no gate",
  9323  		[]core.PodReadinessGate{},
  9324  	}, {
  9325  		"one readiness gate",
  9326  		[]core.PodReadinessGate{{
  9327  			ConditionType: core.PodConditionType("example.com/condition"),
  9328  		}},
  9329  	}, {
  9330  		"two readiness gates",
  9331  		[]core.PodReadinessGate{{
  9332  			ConditionType: core.PodConditionType("example.com/condition1"),
  9333  		}, {
  9334  			ConditionType: core.PodConditionType("example.com/condition2"),
  9335  		}},
  9336  	},
  9337  	}
  9338  	for _, tc := range successCases {
  9339  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
  9340  			t.Errorf("expect tc %q to success: %v", tc.desc, errs)
  9341  		}
  9342  	}
  9343  
  9344  	errorCases := []struct {
  9345  		desc           string
  9346  		readinessGates []core.PodReadinessGate
  9347  	}{{
  9348  		"invalid condition type",
  9349  		[]core.PodReadinessGate{{
  9350  			ConditionType: core.PodConditionType("invalid/condition/type"),
  9351  		}},
  9352  	},
  9353  	}
  9354  	for _, tc := range errorCases {
  9355  		if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
  9356  			t.Errorf("expected tc %q to fail", tc.desc)
  9357  		}
  9358  	}
  9359  }
  9360  
  9361  func TestValidatePodConditions(t *testing.T) {
  9362  	successCases := []struct {
  9363  		desc          string
  9364  		podConditions []core.PodCondition
  9365  	}{{
  9366  		"no condition",
  9367  		[]core.PodCondition{},
  9368  	}, {
  9369  		"one system condition",
  9370  		[]core.PodCondition{{
  9371  			Type:   core.PodReady,
  9372  			Status: core.ConditionTrue,
  9373  		}},
  9374  	}, {
  9375  		"one system condition and one custom condition",
  9376  		[]core.PodCondition{{
  9377  			Type:   core.PodReady,
  9378  			Status: core.ConditionTrue,
  9379  		}, {
  9380  			Type:   core.PodConditionType("example.com/condition"),
  9381  			Status: core.ConditionFalse,
  9382  		}},
  9383  	}, {
  9384  		"two custom condition",
  9385  		[]core.PodCondition{{
  9386  			Type:   core.PodConditionType("foobar"),
  9387  			Status: core.ConditionTrue,
  9388  		}, {
  9389  			Type:   core.PodConditionType("example.com/condition"),
  9390  			Status: core.ConditionFalse,
  9391  		}},
  9392  	},
  9393  	}
  9394  
  9395  	for _, tc := range successCases {
  9396  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
  9397  			t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
  9398  		}
  9399  	}
  9400  
  9401  	errorCases := []struct {
  9402  		desc          string
  9403  		podConditions []core.PodCondition
  9404  	}{{
  9405  		"one system condition and a invalid custom condition",
  9406  		[]core.PodCondition{{
  9407  			Type:   core.PodReady,
  9408  			Status: core.ConditionStatus("True"),
  9409  		}, {
  9410  			Type:   core.PodConditionType("invalid/custom/condition"),
  9411  			Status: core.ConditionStatus("True"),
  9412  		}},
  9413  	},
  9414  	}
  9415  	for _, tc := range errorCases {
  9416  		if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
  9417  			t.Errorf("expected tc %q to fail", tc.desc)
  9418  		}
  9419  	}
  9420  }
  9421  
  9422  func TestValidatePodSpec(t *testing.T) {
  9423  	activeDeadlineSeconds := int64(30)
  9424  	activeDeadlineSecondsMax := int64(math.MaxInt32)
  9425  
  9426  	minUserID := int64(0)
  9427  	maxUserID := int64(2147483647)
  9428  	minGroupID := int64(0)
  9429  	maxGroupID := int64(2147483647)
  9430  	goodfsGroupChangePolicy := core.FSGroupChangeAlways
  9431  	badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
  9432  	badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
  9433  
  9434  	successCases := map[string]core.PodSpec{
  9435  		"populate basic fields, leave defaults for most": {
  9436  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9437  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9438  			RestartPolicy: core.RestartPolicyAlways,
  9439  			DNSPolicy:     core.DNSClusterFirst,
  9440  		},
  9441  		"populate all fields": {
  9442  			Volumes: []core.Volume{
  9443  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9444  			},
  9445  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9446  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9447  			RestartPolicy:  core.RestartPolicyAlways,
  9448  			NodeSelector: map[string]string{
  9449  				"key": "value",
  9450  			},
  9451  			NodeName:              "foobar",
  9452  			DNSPolicy:             core.DNSClusterFirst,
  9453  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
  9454  			ServiceAccountName:    "acct",
  9455  		},
  9456  		"populate all fields with larger active deadline": {
  9457  			Volumes: []core.Volume{
  9458  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9459  			},
  9460  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9461  			InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9462  			RestartPolicy:  core.RestartPolicyAlways,
  9463  			NodeSelector: map[string]string{
  9464  				"key": "value",
  9465  			},
  9466  			NodeName:              "foobar",
  9467  			DNSPolicy:             core.DNSClusterFirst,
  9468  			ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
  9469  			ServiceAccountName:    "acct",
  9470  		},
  9471  		"populate HostNetwork": {
  9472  			Containers: []core.Container{
  9473  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
  9474  					Ports: []core.ContainerPort{
  9475  						{HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
  9476  				},
  9477  			},
  9478  			SecurityContext: &core.PodSecurityContext{
  9479  				HostNetwork: true,
  9480  			},
  9481  			RestartPolicy: core.RestartPolicyAlways,
  9482  			DNSPolicy:     core.DNSClusterFirst,
  9483  		},
  9484  		"populate RunAsUser SupplementalGroups FSGroup with minID 0": {
  9485  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9486  			SecurityContext: &core.PodSecurityContext{
  9487  				SupplementalGroups: []int64{minGroupID},
  9488  				RunAsUser:          &minUserID,
  9489  				FSGroup:            &minGroupID,
  9490  			},
  9491  			RestartPolicy: core.RestartPolicyAlways,
  9492  			DNSPolicy:     core.DNSClusterFirst,
  9493  		},
  9494  		"populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
  9495  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9496  			SecurityContext: &core.PodSecurityContext{
  9497  				SupplementalGroups: []int64{maxGroupID},
  9498  				RunAsUser:          &maxUserID,
  9499  				FSGroup:            &maxGroupID,
  9500  			},
  9501  			RestartPolicy: core.RestartPolicyAlways,
  9502  			DNSPolicy:     core.DNSClusterFirst,
  9503  		},
  9504  		"populate HostIPC": {
  9505  			SecurityContext: &core.PodSecurityContext{
  9506  				HostIPC: true,
  9507  			},
  9508  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9509  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9510  			RestartPolicy: core.RestartPolicyAlways,
  9511  			DNSPolicy:     core.DNSClusterFirst,
  9512  		},
  9513  		"populate HostPID": {
  9514  			SecurityContext: &core.PodSecurityContext{
  9515  				HostPID: true,
  9516  			},
  9517  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9518  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9519  			RestartPolicy: core.RestartPolicyAlways,
  9520  			DNSPolicy:     core.DNSClusterFirst,
  9521  		},
  9522  		"populate Affinity": {
  9523  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9524  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9525  			RestartPolicy: core.RestartPolicyAlways,
  9526  			DNSPolicy:     core.DNSClusterFirst,
  9527  		},
  9528  		"populate HostAliases": {
  9529  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
  9530  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9531  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9532  			RestartPolicy: core.RestartPolicyAlways,
  9533  			DNSPolicy:     core.DNSClusterFirst,
  9534  		},
  9535  		"populate HostAliases with `foo.bar` hostnames": {
  9536  			HostAliases:   []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
  9537  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9538  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9539  			RestartPolicy: core.RestartPolicyAlways,
  9540  			DNSPolicy:     core.DNSClusterFirst,
  9541  		},
  9542  		"populate HostAliases with HostNetwork": {
  9543  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
  9544  			Containers:  []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9545  			SecurityContext: &core.PodSecurityContext{
  9546  				HostNetwork: true,
  9547  			},
  9548  			RestartPolicy: core.RestartPolicyAlways,
  9549  			DNSPolicy:     core.DNSClusterFirst,
  9550  		},
  9551  		"populate PriorityClassName": {
  9552  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9553  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9554  			RestartPolicy:     core.RestartPolicyAlways,
  9555  			DNSPolicy:         core.DNSClusterFirst,
  9556  			PriorityClassName: "valid-name",
  9557  		},
  9558  		"populate ShareProcessNamespace": {
  9559  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9560  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9561  			RestartPolicy: core.RestartPolicyAlways,
  9562  			DNSPolicy:     core.DNSClusterFirst,
  9563  			SecurityContext: &core.PodSecurityContext{
  9564  				ShareProcessNamespace: &[]bool{true}[0],
  9565  			},
  9566  		},
  9567  		"populate RuntimeClassName": {
  9568  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9569  			RestartPolicy:    core.RestartPolicyAlways,
  9570  			DNSPolicy:        core.DNSClusterFirst,
  9571  			RuntimeClassName: utilpointer.String("valid-sandbox"),
  9572  		},
  9573  		"populate Overhead": {
  9574  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9575  			RestartPolicy:    core.RestartPolicyAlways,
  9576  			DNSPolicy:        core.DNSClusterFirst,
  9577  			RuntimeClassName: utilpointer.String("valid-sandbox"),
  9578  			Overhead:         core.ResourceList{},
  9579  		},
  9580  		"populate DNSPolicy": {
  9581  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9582  			SecurityContext: &core.PodSecurityContext{
  9583  				FSGroupChangePolicy: &goodfsGroupChangePolicy,
  9584  			},
  9585  			RestartPolicy: core.RestartPolicyAlways,
  9586  			DNSPolicy:     core.DNSClusterFirst,
  9587  		},
  9588  	}
  9589  	for k, v := range successCases {
  9590  		t.Run(k, func(t *testing.T) {
  9591  			opts := PodValidationOptions{
  9592  				ResourceIsPod: true,
  9593  			}
  9594  			if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) != 0 {
  9595  				t.Errorf("expected success: %v", errs)
  9596  			}
  9597  		})
  9598  	}
  9599  
  9600  	activeDeadlineSeconds = int64(0)
  9601  	activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1)
  9602  
  9603  	minUserID = int64(-1)
  9604  	maxUserID = int64(2147483648)
  9605  	minGroupID = int64(-1)
  9606  	maxGroupID = int64(2147483648)
  9607  
  9608  	failureCases := map[string]core.PodSpec{
  9609  		"bad volume": {
  9610  			Volumes:       []core.Volume{{}},
  9611  			RestartPolicy: core.RestartPolicyAlways,
  9612  			DNSPolicy:     core.DNSClusterFirst,
  9613  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9614  		},
  9615  		"no containers": {
  9616  			RestartPolicy: core.RestartPolicyAlways,
  9617  			DNSPolicy:     core.DNSClusterFirst,
  9618  		},
  9619  		"bad container": {
  9620  			Containers:    []core.Container{{}},
  9621  			RestartPolicy: core.RestartPolicyAlways,
  9622  			DNSPolicy:     core.DNSClusterFirst,
  9623  		},
  9624  		"bad init container": {
  9625  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9626  			InitContainers: []core.Container{{}},
  9627  			RestartPolicy:  core.RestartPolicyAlways,
  9628  			DNSPolicy:      core.DNSClusterFirst,
  9629  		},
  9630  		"bad DNS policy": {
  9631  			DNSPolicy:     core.DNSPolicy("invalid"),
  9632  			RestartPolicy: core.RestartPolicyAlways,
  9633  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9634  		},
  9635  		"bad service account name": {
  9636  			Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9637  			RestartPolicy:      core.RestartPolicyAlways,
  9638  			DNSPolicy:          core.DNSClusterFirst,
  9639  			ServiceAccountName: "invalidName",
  9640  		},
  9641  		"bad restart policy": {
  9642  			RestartPolicy: "UnknowPolicy",
  9643  			DNSPolicy:     core.DNSClusterFirst,
  9644  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9645  		},
  9646  		"with hostNetwork hostPort unspecified": {
  9647  			Containers: []core.Container{
  9648  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
  9649  					{HostPort: 0, ContainerPort: 2600, Protocol: "TCP"}},
  9650  				},
  9651  			},
  9652  			SecurityContext: &core.PodSecurityContext{
  9653  				HostNetwork: true,
  9654  			},
  9655  			RestartPolicy: core.RestartPolicyAlways,
  9656  			DNSPolicy:     core.DNSClusterFirst,
  9657  		},
  9658  		"with hostNetwork hostPort not equal to containerPort": {
  9659  			Containers: []core.Container{
  9660  				{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
  9661  					{HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}},
  9662  				},
  9663  			},
  9664  			SecurityContext: &core.PodSecurityContext{
  9665  				HostNetwork: true,
  9666  			},
  9667  			RestartPolicy: core.RestartPolicyAlways,
  9668  			DNSPolicy:     core.DNSClusterFirst,
  9669  		},
  9670  		"with hostAliases with invalid IP": {
  9671  			SecurityContext: &core.PodSecurityContext{
  9672  				HostNetwork: false,
  9673  			},
  9674  			HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}},
  9675  		},
  9676  		"with hostAliases with invalid hostname": {
  9677  			SecurityContext: &core.PodSecurityContext{
  9678  				HostNetwork: false,
  9679  			},
  9680  			HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}},
  9681  		},
  9682  		"bad supplementalGroups large than math.MaxInt32": {
  9683  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9684  			SecurityContext: &core.PodSecurityContext{
  9685  				SupplementalGroups: []int64{maxGroupID, 1234},
  9686  			},
  9687  			RestartPolicy: core.RestartPolicyAlways,
  9688  			DNSPolicy:     core.DNSClusterFirst,
  9689  		},
  9690  		"bad supplementalGroups less than 0": {
  9691  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9692  			SecurityContext: &core.PodSecurityContext{
  9693  				SupplementalGroups: []int64{minGroupID, 1234},
  9694  			},
  9695  			RestartPolicy: core.RestartPolicyAlways,
  9696  			DNSPolicy:     core.DNSClusterFirst,
  9697  		},
  9698  		"bad runAsUser large than math.MaxInt32": {
  9699  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9700  			SecurityContext: &core.PodSecurityContext{
  9701  				RunAsUser: &maxUserID,
  9702  			},
  9703  			RestartPolicy: core.RestartPolicyAlways,
  9704  			DNSPolicy:     core.DNSClusterFirst,
  9705  		},
  9706  		"bad runAsUser less than 0": {
  9707  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9708  			SecurityContext: &core.PodSecurityContext{
  9709  				RunAsUser: &minUserID,
  9710  			},
  9711  			RestartPolicy: core.RestartPolicyAlways,
  9712  			DNSPolicy:     core.DNSClusterFirst,
  9713  		},
  9714  		"bad fsGroup large than math.MaxInt32": {
  9715  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9716  			SecurityContext: &core.PodSecurityContext{
  9717  				FSGroup: &maxGroupID,
  9718  			},
  9719  			RestartPolicy: core.RestartPolicyAlways,
  9720  			DNSPolicy:     core.DNSClusterFirst,
  9721  		},
  9722  		"bad fsGroup less than 0": {
  9723  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9724  			SecurityContext: &core.PodSecurityContext{
  9725  				FSGroup: &minGroupID,
  9726  			},
  9727  			RestartPolicy: core.RestartPolicyAlways,
  9728  			DNSPolicy:     core.DNSClusterFirst,
  9729  		},
  9730  		"bad-active-deadline-seconds": {
  9731  			Volumes: []core.Volume{
  9732  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9733  			},
  9734  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9735  			RestartPolicy: core.RestartPolicyAlways,
  9736  			NodeSelector: map[string]string{
  9737  				"key": "value",
  9738  			},
  9739  			NodeName:              "foobar",
  9740  			DNSPolicy:             core.DNSClusterFirst,
  9741  			ActiveDeadlineSeconds: &activeDeadlineSeconds,
  9742  		},
  9743  		"active-deadline-seconds-too-large": {
  9744  			Volumes: []core.Volume{
  9745  				{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9746  			},
  9747  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9748  			RestartPolicy: core.RestartPolicyAlways,
  9749  			NodeSelector: map[string]string{
  9750  				"key": "value",
  9751  			},
  9752  			NodeName:              "foobar",
  9753  			DNSPolicy:             core.DNSClusterFirst,
  9754  			ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge,
  9755  		},
  9756  		"bad nodeName": {
  9757  			NodeName:      "node name",
  9758  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9759  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9760  			RestartPolicy: core.RestartPolicyAlways,
  9761  			DNSPolicy:     core.DNSClusterFirst,
  9762  		},
  9763  		"bad PriorityClassName": {
  9764  			Volumes:           []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9765  			Containers:        []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9766  			RestartPolicy:     core.RestartPolicyAlways,
  9767  			DNSPolicy:         core.DNSClusterFirst,
  9768  			PriorityClassName: "InvalidName",
  9769  		},
  9770  		"ShareProcessNamespace and HostPID both set": {
  9771  			Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9772  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9773  			RestartPolicy: core.RestartPolicyAlways,
  9774  			DNSPolicy:     core.DNSClusterFirst,
  9775  			SecurityContext: &core.PodSecurityContext{
  9776  				HostPID:               true,
  9777  				ShareProcessNamespace: &[]bool{true}[0],
  9778  			},
  9779  		},
  9780  		"bad RuntimeClassName": {
  9781  			Containers:       []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9782  			RestartPolicy:    core.RestartPolicyAlways,
  9783  			DNSPolicy:        core.DNSClusterFirst,
  9784  			RuntimeClassName: utilpointer.String("invalid/sandbox"),
  9785  		},
  9786  		"bad empty fsGroupchangepolicy": {
  9787  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9788  			SecurityContext: &core.PodSecurityContext{
  9789  				FSGroupChangePolicy: &badfsGroupChangePolicy2,
  9790  			},
  9791  			RestartPolicy: core.RestartPolicyAlways,
  9792  			DNSPolicy:     core.DNSClusterFirst,
  9793  		},
  9794  		"bad invalid fsgroupchangepolicy": {
  9795  			Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9796  			SecurityContext: &core.PodSecurityContext{
  9797  				FSGroupChangePolicy: &badfsGroupChangePolicy1,
  9798  			},
  9799  			RestartPolicy: core.RestartPolicyAlways,
  9800  			DNSPolicy:     core.DNSClusterFirst,
  9801  		},
  9802  		"disallowed resources resize policy for init containers": {
  9803  			InitContainers: []core.Container{{
  9804  				Name:  "initctr",
  9805  				Image: "initimage",
  9806  				ResizePolicy: []core.ContainerResizePolicy{
  9807  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  9808  				},
  9809  				ImagePullPolicy:          "IfNotPresent",
  9810  				TerminationMessagePolicy: "File",
  9811  			}},
  9812  			Containers: []core.Container{{
  9813  				Name:  "ctr",
  9814  				Image: "image",
  9815  				ResizePolicy: []core.ContainerResizePolicy{
  9816  					{ResourceName: "cpu", RestartPolicy: "NotRequired"},
  9817  				},
  9818  				ImagePullPolicy:          "IfNotPresent",
  9819  				TerminationMessagePolicy: "File",
  9820  			}},
  9821  			RestartPolicy: core.RestartPolicyAlways,
  9822  			DNSPolicy:     core.DNSClusterFirst,
  9823  		},
  9824  	}
  9825  	for k, v := range failureCases {
  9826  		opts := PodValidationOptions{
  9827  			ResourceIsPod: true,
  9828  		}
  9829  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), opts); len(errs) == 0 {
  9830  			t.Errorf("expected failure for %q", k)
  9831  		}
  9832  	}
  9833  }
  9834  
  9835  func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
  9836  	var out core.PodSpec
  9837  	out.Containers = in.Containers
  9838  	out.RestartPolicy = in.RestartPolicy
  9839  	out.DNSPolicy = in.DNSPolicy
  9840  	out.Tolerations = tolerations
  9841  	return out
  9842  }
  9843  
  9844  func TestValidatePod(t *testing.T) {
  9845  	validPodSpec := func(affinity *core.Affinity) core.PodSpec {
  9846  		spec := core.PodSpec{
  9847  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9848  			RestartPolicy: core.RestartPolicyAlways,
  9849  			DNSPolicy:     core.DNSClusterFirst,
  9850  		}
  9851  		if affinity != nil {
  9852  			spec.Affinity = affinity
  9853  		}
  9854  		return spec
  9855  	}
  9856  	validPVCSpec := core.PersistentVolumeClaimSpec{
  9857  		AccessModes: []core.PersistentVolumeAccessMode{
  9858  			core.ReadWriteOnce,
  9859  		},
  9860  		Resources: core.VolumeResourceRequirements{
  9861  			Requests: core.ResourceList{
  9862  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
  9863  			},
  9864  		},
  9865  	}
  9866  	validPVCTemplate := core.PersistentVolumeClaimTemplate{
  9867  		Spec: validPVCSpec,
  9868  	}
  9869  	longPodName := strings.Repeat("a", 200)
  9870  	longVolName := strings.Repeat("b", 60)
  9871  
  9872  	successCases := map[string]core.Pod{
  9873  		"basic fields": {
  9874  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
  9875  			Spec: core.PodSpec{
  9876  				Volumes:       []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
  9877  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9878  				RestartPolicy: core.RestartPolicyAlways,
  9879  				DNSPolicy:     core.DNSClusterFirst,
  9880  			},
  9881  		},
  9882  		"just about everything": {
  9883  			ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
  9884  			Spec: core.PodSpec{
  9885  				Volumes: []core.Volume{
  9886  					{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
  9887  				},
  9888  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
  9889  				RestartPolicy: core.RestartPolicyAlways,
  9890  				DNSPolicy:     core.DNSClusterFirst,
  9891  				NodeSelector: map[string]string{
  9892  					"key": "value",
  9893  				},
  9894  				NodeName: "foobar",
  9895  			},
  9896  		},
  9897  		"serialized node affinity requirements": {
  9898  			ObjectMeta: metav1.ObjectMeta{
  9899  				Name:      "123",
  9900  				Namespace: "ns",
  9901  			},
  9902  			Spec: validPodSpec(
  9903  				// TODO: Uncomment and move this block and move inside NodeAffinity once
  9904  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
  9905  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
  9906  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
  9907  				//				{
  9908  				//					MatchExpressions: []core.NodeSelectorRequirement{
  9909  				//						{
  9910  				//							Key: "key1",
  9911  				//							Operator: core.NodeSelectorOpExists
  9912  				//						},
  9913  				//					},
  9914  				//				},
  9915  				//			},
  9916  				//		},
  9917  				&core.Affinity{
  9918  					NodeAffinity: &core.NodeAffinity{
  9919  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
  9920  							NodeSelectorTerms: []core.NodeSelectorTerm{{
  9921  								MatchExpressions: []core.NodeSelectorRequirement{{
  9922  									Key:      "key2",
  9923  									Operator: core.NodeSelectorOpIn,
  9924  									Values:   []string{"value1", "value2"},
  9925  								}},
  9926  								MatchFields: []core.NodeSelectorRequirement{{
  9927  									Key:      "metadata.name",
  9928  									Operator: core.NodeSelectorOpIn,
  9929  									Values:   []string{"host1"},
  9930  								}},
  9931  							}},
  9932  						},
  9933  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
  9934  							Weight: 10,
  9935  							Preference: core.NodeSelectorTerm{
  9936  								MatchExpressions: []core.NodeSelectorRequirement{{
  9937  									Key:      "foo",
  9938  									Operator: core.NodeSelectorOpIn,
  9939  									Values:   []string{"bar"},
  9940  								}},
  9941  							},
  9942  						}},
  9943  					},
  9944  				},
  9945  			),
  9946  		},
  9947  		"serialized node affinity requirements, II": {
  9948  			ObjectMeta: metav1.ObjectMeta{
  9949  				Name:      "123",
  9950  				Namespace: "ns",
  9951  			},
  9952  			Spec: validPodSpec(
  9953  				// TODO: Uncomment and move this block and move inside NodeAffinity once
  9954  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
  9955  				//		RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
  9956  				//			NodeSelectorTerms: []core.NodeSelectorTerm{
  9957  				//				{
  9958  				//					MatchExpressions: []core.NodeSelectorRequirement{
  9959  				//						{
  9960  				//							Key: "key1",
  9961  				//							Operator: core.NodeSelectorOpExists
  9962  				//						},
  9963  				//					},
  9964  				//				},
  9965  				//			},
  9966  				//		},
  9967  				&core.Affinity{
  9968  					NodeAffinity: &core.NodeAffinity{
  9969  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
  9970  							NodeSelectorTerms: []core.NodeSelectorTerm{{
  9971  								MatchExpressions: []core.NodeSelectorRequirement{},
  9972  							}},
  9973  						},
  9974  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
  9975  							Weight: 10,
  9976  							Preference: core.NodeSelectorTerm{
  9977  								MatchExpressions: []core.NodeSelectorRequirement{},
  9978  							},
  9979  						}},
  9980  					},
  9981  				},
  9982  			),
  9983  		},
  9984  		"serialized pod affinity in affinity requirements in annotations": {
  9985  			ObjectMeta: metav1.ObjectMeta{
  9986  				Name:      "123",
  9987  				Namespace: "ns",
  9988  				// TODO: Uncomment and move this block into Annotations map once
  9989  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
  9990  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
  9991  				//			"labelSelector": {
  9992  				//				"matchExpressions": [{
  9993  				//					"key": "key2",
  9994  				//					"operator": "In",
  9995  				//					"values": ["value1", "value2"]
  9996  				//				}]
  9997  				//			},
  9998  				//			"namespaces":["ns"],
  9999  				//			"topologyKey": "zone"
 10000  				//		}]
 10001  			},
 10002  			Spec: validPodSpec(&core.Affinity{
 10003  				PodAffinity: &core.PodAffinity{
 10004  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10005  						LabelSelector: &metav1.LabelSelector{
 10006  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10007  								Key:      "key2",
 10008  								Operator: metav1.LabelSelectorOpIn,
 10009  								Values:   []string{"value1", "value2"},
 10010  							}},
 10011  						},
 10012  						TopologyKey: "zone",
 10013  						Namespaces:  []string{"ns"},
 10014  						NamespaceSelector: &metav1.LabelSelector{
 10015  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10016  								Key:      "key",
 10017  								Operator: metav1.LabelSelectorOpIn,
 10018  								Values:   []string{"value1", "value2"},
 10019  							}},
 10020  						},
 10021  					}},
 10022  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10023  						Weight: 10,
 10024  						PodAffinityTerm: core.PodAffinityTerm{
 10025  							LabelSelector: &metav1.LabelSelector{
 10026  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10027  									Key:      "key2",
 10028  									Operator: metav1.LabelSelectorOpNotIn,
 10029  									Values:   []string{"value1", "value2"},
 10030  								}},
 10031  							},
 10032  							Namespaces:  []string{"ns"},
 10033  							TopologyKey: "region",
 10034  						},
 10035  					}},
 10036  				},
 10037  			}),
 10038  		},
 10039  		"serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
 10040  			ObjectMeta: metav1.ObjectMeta{
 10041  				Name:      "123",
 10042  				Namespace: "ns",
 10043  				// TODO: Uncomment and move this block into Annotations map once
 10044  				// RequiredDuringSchedulingRequiredDuringExecution is implemented
 10045  				//		"requiredDuringSchedulingRequiredDuringExecution": [{
 10046  				//			"labelSelector": {
 10047  				//				"matchExpressions": [{
 10048  				//					"key": "key2",
 10049  				//					"operator": "In",
 10050  				//					"values": ["value1", "value2"]
 10051  				//				}]
 10052  				//			},
 10053  				//			"namespaces":["ns"],
 10054  				//			"topologyKey": "zone"
 10055  				//		}]
 10056  			},
 10057  			Spec: validPodSpec(&core.Affinity{
 10058  				PodAntiAffinity: &core.PodAntiAffinity{
 10059  					RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 10060  						LabelSelector: &metav1.LabelSelector{
 10061  							MatchExpressions: []metav1.LabelSelectorRequirement{{
 10062  								Key:      "key2",
 10063  								Operator: metav1.LabelSelectorOpExists,
 10064  							}},
 10065  						},
 10066  						TopologyKey: "zone",
 10067  						Namespaces:  []string{"ns"},
 10068  					}},
 10069  					PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10070  						Weight: 10,
 10071  						PodAffinityTerm: core.PodAffinityTerm{
 10072  							LabelSelector: &metav1.LabelSelector{
 10073  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 10074  									Key:      "key2",
 10075  									Operator: metav1.LabelSelectorOpDoesNotExist,
 10076  								}},
 10077  							},
 10078  							Namespaces:  []string{"ns"},
 10079  							TopologyKey: "region",
 10080  						},
 10081  					}},
 10082  				},
 10083  			}),
 10084  		},
 10085  		"populate forgiveness tolerations with exists operator in annotations.": {
 10086  			ObjectMeta: metav1.ObjectMeta{
 10087  				Name:      "123",
 10088  				Namespace: "ns",
 10089  			},
 10090  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10091  		},
 10092  		"populate forgiveness tolerations with equal operator in annotations.": {
 10093  			ObjectMeta: metav1.ObjectMeta{
 10094  				Name:      "123",
 10095  				Namespace: "ns",
 10096  			},
 10097  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
 10098  		},
 10099  		"populate tolerations equal operator in annotations.": {
 10100  			ObjectMeta: metav1.ObjectMeta{
 10101  				Name:      "123",
 10102  				Namespace: "ns",
 10103  			},
 10104  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 10105  		},
 10106  		"populate tolerations exists operator in annotations.": {
 10107  			ObjectMeta: metav1.ObjectMeta{
 10108  				Name:      "123",
 10109  				Namespace: "ns",
 10110  			},
 10111  			Spec: validPodSpec(nil),
 10112  		},
 10113  		"empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
 10114  			ObjectMeta: metav1.ObjectMeta{
 10115  				Name:      "123",
 10116  				Namespace: "ns",
 10117  			},
 10118  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
 10119  		},
 10120  		"empty operator is OK for toleration, defaults to Equal.": {
 10121  			ObjectMeta: metav1.ObjectMeta{
 10122  				Name:      "123",
 10123  				Namespace: "ns",
 10124  			},
 10125  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
 10126  		},
 10127  		"empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
 10128  			ObjectMeta: metav1.ObjectMeta{
 10129  				Name:      "123",
 10130  				Namespace: "ns",
 10131  			},
 10132  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
 10133  		},
 10134  		"negative tolerationSeconds is OK for toleration.": {
 10135  			ObjectMeta: metav1.ObjectMeta{
 10136  				Name:      "pod-forgiveness-invalid",
 10137  				Namespace: "ns",
 10138  			},
 10139  			Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
 10140  		},
 10141  		"runtime default seccomp profile": {
 10142  			ObjectMeta: metav1.ObjectMeta{
 10143  				Name:      "123",
 10144  				Namespace: "ns",
 10145  				Annotations: map[string]string{
 10146  					core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault,
 10147  				},
 10148  			},
 10149  			Spec: validPodSpec(nil),
 10150  		},
 10151  		"docker default seccomp profile": {
 10152  			ObjectMeta: metav1.ObjectMeta{
 10153  				Name:      "123",
 10154  				Namespace: "ns",
 10155  				Annotations: map[string]string{
 10156  					core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault,
 10157  				},
 10158  			},
 10159  			Spec: validPodSpec(nil),
 10160  		},
 10161  		"unconfined seccomp profile": {
 10162  			ObjectMeta: metav1.ObjectMeta{
 10163  				Name:      "123",
 10164  				Namespace: "ns",
 10165  				Annotations: map[string]string{
 10166  					core.SeccompPodAnnotationKey: "unconfined",
 10167  				},
 10168  			},
 10169  			Spec: validPodSpec(nil),
 10170  		},
 10171  		"localhost seccomp profile": {
 10172  			ObjectMeta: metav1.ObjectMeta{
 10173  				Name:      "123",
 10174  				Namespace: "ns",
 10175  				Annotations: map[string]string{
 10176  					core.SeccompPodAnnotationKey: "localhost/foo",
 10177  				},
 10178  			},
 10179  			Spec: validPodSpec(nil),
 10180  		},
 10181  		"localhost seccomp profile for a container": {
 10182  			ObjectMeta: metav1.ObjectMeta{
 10183  				Name:      "123",
 10184  				Namespace: "ns",
 10185  				Annotations: map[string]string{
 10186  					core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo",
 10187  				},
 10188  			},
 10189  			Spec: validPodSpec(nil),
 10190  		},
 10191  		"runtime default seccomp profile for a pod": {
 10192  			ObjectMeta: metav1.ObjectMeta{
 10193  				Name:      "123",
 10194  				Namespace: "ns",
 10195  			},
 10196  			Spec: core.PodSpec{
 10197  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10198  				RestartPolicy: core.RestartPolicyAlways,
 10199  				DNSPolicy:     core.DNSDefault,
 10200  				SecurityContext: &core.PodSecurityContext{
 10201  					SeccompProfile: &core.SeccompProfile{
 10202  						Type: core.SeccompProfileTypeRuntimeDefault,
 10203  					},
 10204  				},
 10205  			},
 10206  		},
 10207  		"runtime default seccomp profile for a container": {
 10208  			ObjectMeta: metav1.ObjectMeta{
 10209  				Name:      "123",
 10210  				Namespace: "ns",
 10211  			},
 10212  			Spec: core.PodSpec{
 10213  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10214  					SecurityContext: &core.SecurityContext{
 10215  						SeccompProfile: &core.SeccompProfile{
 10216  							Type: core.SeccompProfileTypeRuntimeDefault,
 10217  						},
 10218  					},
 10219  				}},
 10220  				RestartPolicy: core.RestartPolicyAlways,
 10221  				DNSPolicy:     core.DNSDefault,
 10222  			},
 10223  		},
 10224  		"unconfined seccomp profile for a pod": {
 10225  			ObjectMeta: metav1.ObjectMeta{
 10226  				Name:      "123",
 10227  				Namespace: "ns",
 10228  			},
 10229  			Spec: core.PodSpec{
 10230  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10231  				RestartPolicy: core.RestartPolicyAlways,
 10232  				DNSPolicy:     core.DNSDefault,
 10233  				SecurityContext: &core.PodSecurityContext{
 10234  					SeccompProfile: &core.SeccompProfile{
 10235  						Type: core.SeccompProfileTypeUnconfined,
 10236  					},
 10237  				},
 10238  			},
 10239  		},
 10240  		"unconfined seccomp profile for a container": {
 10241  			ObjectMeta: metav1.ObjectMeta{
 10242  				Name:      "123",
 10243  				Namespace: "ns",
 10244  			},
 10245  			Spec: core.PodSpec{
 10246  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10247  					SecurityContext: &core.SecurityContext{
 10248  						SeccompProfile: &core.SeccompProfile{
 10249  							Type: core.SeccompProfileTypeUnconfined,
 10250  						},
 10251  					},
 10252  				}},
 10253  				RestartPolicy: core.RestartPolicyAlways,
 10254  				DNSPolicy:     core.DNSDefault,
 10255  			},
 10256  		},
 10257  		"localhost seccomp profile for a pod": {
 10258  			ObjectMeta: metav1.ObjectMeta{
 10259  				Name:      "123",
 10260  				Namespace: "ns",
 10261  			},
 10262  			Spec: core.PodSpec{
 10263  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10264  				RestartPolicy: core.RestartPolicyAlways,
 10265  				DNSPolicy:     core.DNSDefault,
 10266  				SecurityContext: &core.PodSecurityContext{
 10267  					SeccompProfile: &core.SeccompProfile{
 10268  						Type:             core.SeccompProfileTypeLocalhost,
 10269  						LocalhostProfile: utilpointer.String("filename.json"),
 10270  					},
 10271  				},
 10272  			},
 10273  		},
 10274  		"localhost seccomp profile for a container, II": {
 10275  			ObjectMeta: metav1.ObjectMeta{
 10276  				Name:      "123",
 10277  				Namespace: "ns",
 10278  			},
 10279  			Spec: core.PodSpec{
 10280  				Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 10281  					SecurityContext: &core.SecurityContext{
 10282  						SeccompProfile: &core.SeccompProfile{
 10283  							Type:             core.SeccompProfileTypeLocalhost,
 10284  							LocalhostProfile: utilpointer.String("filename.json"),
 10285  						},
 10286  					},
 10287  				}},
 10288  				RestartPolicy: core.RestartPolicyAlways,
 10289  				DNSPolicy:     core.DNSDefault,
 10290  			},
 10291  		},
 10292  		"default AppArmor profile for a container": {
 10293  			ObjectMeta: metav1.ObjectMeta{
 10294  				Name:      "123",
 10295  				Namespace: "ns",
 10296  				Annotations: map[string]string{
 10297  					v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileRuntimeDefault,
 10298  				},
 10299  			},
 10300  			Spec: validPodSpec(nil),
 10301  		},
 10302  		"default AppArmor profile for an init container": {
 10303  			ObjectMeta: metav1.ObjectMeta{
 10304  				Name:      "123",
 10305  				Namespace: "ns",
 10306  				Annotations: map[string]string{
 10307  					v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault,
 10308  				},
 10309  			},
 10310  			Spec: core.PodSpec{
 10311  				InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10312  				Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10313  				RestartPolicy:  core.RestartPolicyAlways,
 10314  				DNSPolicy:      core.DNSClusterFirst,
 10315  			},
 10316  		},
 10317  		"localhost AppArmor profile for a container": {
 10318  			ObjectMeta: metav1.ObjectMeta{
 10319  				Name:      "123",
 10320  				Namespace: "ns",
 10321  				Annotations: map[string]string{
 10322  					v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": v1.AppArmorBetaProfileNamePrefix + "foo",
 10323  				},
 10324  			},
 10325  			Spec: validPodSpec(nil),
 10326  		},
 10327  		"syntactically valid sysctls": {
 10328  			ObjectMeta: metav1.ObjectMeta{
 10329  				Name:      "123",
 10330  				Namespace: "ns",
 10331  			},
 10332  			Spec: core.PodSpec{
 10333  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10334  				RestartPolicy: core.RestartPolicyAlways,
 10335  				DNSPolicy:     core.DNSClusterFirst,
 10336  				SecurityContext: &core.PodSecurityContext{
 10337  					Sysctls: []core.Sysctl{{
 10338  						Name:  "kernel.shmmni",
 10339  						Value: "32768",
 10340  					}, {
 10341  						Name:  "kernel.shmmax",
 10342  						Value: "1000000000",
 10343  					}, {
 10344  						Name:  "knet.ipv4.route.min_pmtu",
 10345  						Value: "1000",
 10346  					}},
 10347  				},
 10348  			},
 10349  		},
 10350  		"valid extended resources for init container": {
 10351  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 10352  			Spec: core.PodSpec{
 10353  				InitContainers: []core.Container{{
 10354  					Name:            "valid-extended",
 10355  					Image:           "image",
 10356  					ImagePullPolicy: "IfNotPresent",
 10357  					Resources: core.ResourceRequirements{
 10358  						Requests: core.ResourceList{
 10359  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 10360  						},
 10361  						Limits: core.ResourceList{
 10362  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 10363  						},
 10364  					},
 10365  					TerminationMessagePolicy: "File",
 10366  				}},
 10367  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10368  				RestartPolicy: core.RestartPolicyAlways,
 10369  				DNSPolicy:     core.DNSClusterFirst,
 10370  			},
 10371  		},
 10372  		"valid extended resources for regular container": {
 10373  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 10374  			Spec: core.PodSpec{
 10375  				InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10376  				Containers: []core.Container{{
 10377  					Name:            "valid-extended",
 10378  					Image:           "image",
 10379  					ImagePullPolicy: "IfNotPresent",
 10380  					Resources: core.ResourceRequirements{
 10381  						Requests: core.ResourceList{
 10382  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 10383  						},
 10384  						Limits: core.ResourceList{
 10385  							core.ResourceName("example.com/a"): resource.MustParse("10"),
 10386  						},
 10387  					},
 10388  					TerminationMessagePolicy: "File",
 10389  				}},
 10390  				RestartPolicy: core.RestartPolicyAlways,
 10391  				DNSPolicy:     core.DNSClusterFirst,
 10392  			},
 10393  		},
 10394  		"valid serviceaccount token projected volume with serviceaccount name specified": {
 10395  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 10396  			Spec: core.PodSpec{
 10397  				ServiceAccountName: "some-service-account",
 10398  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10399  				RestartPolicy:      core.RestartPolicyAlways,
 10400  				DNSPolicy:          core.DNSClusterFirst,
 10401  				Volumes: []core.Volume{{
 10402  					Name: "projected-volume",
 10403  					VolumeSource: core.VolumeSource{
 10404  						Projected: &core.ProjectedVolumeSource{
 10405  							Sources: []core.VolumeProjection{{
 10406  								ServiceAccountToken: &core.ServiceAccountTokenProjection{
 10407  									Audience:          "foo-audience",
 10408  									ExpirationSeconds: 6000,
 10409  									Path:              "foo-path",
 10410  								},
 10411  							}},
 10412  						},
 10413  					},
 10414  				}},
 10415  			},
 10416  		},
 10417  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by name": {
 10418  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 10419  			Spec: core.PodSpec{
 10420  				ServiceAccountName: "some-service-account",
 10421  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10422  				RestartPolicy:      core.RestartPolicyAlways,
 10423  				DNSPolicy:          core.DNSClusterFirst,
 10424  				Volumes: []core.Volume{
 10425  					{
 10426  						Name: "projected-volume",
 10427  						VolumeSource: core.VolumeSource{
 10428  							Projected: &core.ProjectedVolumeSource{
 10429  								Sources: []core.VolumeProjection{
 10430  									{
 10431  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 10432  											Path: "foo-path",
 10433  											Name: utilpointer.String("foo"),
 10434  										},
 10435  									},
 10436  								},
 10437  							},
 10438  						},
 10439  					},
 10440  				},
 10441  			},
 10442  		},
 10443  		"valid ClusterTrustBundlePEM projected volume referring to a CTB by signer name": {
 10444  			ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 10445  			Spec: core.PodSpec{
 10446  				ServiceAccountName: "some-service-account",
 10447  				Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10448  				RestartPolicy:      core.RestartPolicyAlways,
 10449  				DNSPolicy:          core.DNSClusterFirst,
 10450  				Volumes: []core.Volume{
 10451  					{
 10452  						Name: "projected-volume",
 10453  						VolumeSource: core.VolumeSource{
 10454  							Projected: &core.ProjectedVolumeSource{
 10455  								Sources: []core.VolumeProjection{
 10456  									{
 10457  										ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 10458  											Path:       "foo-path",
 10459  											SignerName: utilpointer.String("example.com/foo"),
 10460  											LabelSelector: &metav1.LabelSelector{
 10461  												MatchLabels: map[string]string{
 10462  													"version": "live",
 10463  												},
 10464  											},
 10465  										},
 10466  									},
 10467  								},
 10468  							},
 10469  						},
 10470  					},
 10471  				},
 10472  			},
 10473  		},
 10474  		"ephemeral volume + PVC, no conflict between them": {
 10475  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10476  			Spec: core.PodSpec{
 10477  				Volumes: []core.Volume{
 10478  					{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 10479  					{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 10480  				},
 10481  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10482  				RestartPolicy: core.RestartPolicyAlways,
 10483  				DNSPolicy:     core.DNSClusterFirst,
 10484  			},
 10485  		},
 10486  		"negative pod-deletion-cost": {
 10487  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "-100"}},
 10488  			Spec: core.PodSpec{
 10489  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10490  				RestartPolicy: core.RestartPolicyAlways,
 10491  				DNSPolicy:     core.DNSClusterFirst,
 10492  			},
 10493  		},
 10494  		"positive pod-deletion-cost": {
 10495  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "100"}},
 10496  			Spec: core.PodSpec{
 10497  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10498  				RestartPolicy: core.RestartPolicyAlways,
 10499  				DNSPolicy:     core.DNSClusterFirst,
 10500  			},
 10501  		},
 10502  		"MatchLabelKeys/MismatchLabelKeys in required PodAffinity": {
 10503  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10504  			Spec: core.PodSpec{
 10505  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10506  				RestartPolicy: core.RestartPolicyAlways,
 10507  				DNSPolicy:     core.DNSClusterFirst,
 10508  				Affinity: &core.Affinity{
 10509  					PodAffinity: &core.PodAffinity{
 10510  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 10511  							{
 10512  								LabelSelector: &metav1.LabelSelector{
 10513  									MatchExpressions: []metav1.LabelSelectorRequirement{
 10514  										{
 10515  											Key:      "key",
 10516  											Operator: metav1.LabelSelectorOpNotIn,
 10517  											Values:   []string{"value1", "value2"},
 10518  										},
 10519  										{
 10520  											Key:      "key2",
 10521  											Operator: metav1.LabelSelectorOpIn,
 10522  											Values:   []string{"value1"},
 10523  										},
 10524  										{
 10525  											Key:      "key3",
 10526  											Operator: metav1.LabelSelectorOpNotIn,
 10527  											Values:   []string{"value1"},
 10528  										},
 10529  									},
 10530  								},
 10531  								TopologyKey:       "k8s.io/zone",
 10532  								MatchLabelKeys:    []string{"key2"},
 10533  								MismatchLabelKeys: []string{"key3"},
 10534  							},
 10535  						},
 10536  					},
 10537  				},
 10538  			},
 10539  		},
 10540  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAffinity": {
 10541  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10542  			Spec: core.PodSpec{
 10543  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10544  				RestartPolicy: core.RestartPolicyAlways,
 10545  				DNSPolicy:     core.DNSClusterFirst,
 10546  				Affinity: &core.Affinity{
 10547  					PodAffinity: &core.PodAffinity{
 10548  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 10549  							{
 10550  								Weight: 10,
 10551  								PodAffinityTerm: core.PodAffinityTerm{
 10552  									LabelSelector: &metav1.LabelSelector{
 10553  										MatchExpressions: []metav1.LabelSelectorRequirement{
 10554  											{
 10555  												Key:      "key",
 10556  												Operator: metav1.LabelSelectorOpNotIn,
 10557  												Values:   []string{"value1", "value2"},
 10558  											},
 10559  											{
 10560  												Key:      "key2",
 10561  												Operator: metav1.LabelSelectorOpIn,
 10562  												Values:   []string{"value1"},
 10563  											},
 10564  											{
 10565  												Key:      "key3",
 10566  												Operator: metav1.LabelSelectorOpNotIn,
 10567  												Values:   []string{"value1"},
 10568  											},
 10569  										},
 10570  									},
 10571  									TopologyKey:       "k8s.io/zone",
 10572  									MatchLabelKeys:    []string{"key2"},
 10573  									MismatchLabelKeys: []string{"key3"},
 10574  								},
 10575  							},
 10576  						},
 10577  					},
 10578  				},
 10579  			},
 10580  		},
 10581  		"MatchLabelKeys/MismatchLabelKeys in required PodAntiAffinity": {
 10582  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10583  			Spec: core.PodSpec{
 10584  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10585  				RestartPolicy: core.RestartPolicyAlways,
 10586  				DNSPolicy:     core.DNSClusterFirst,
 10587  				Affinity: &core.Affinity{
 10588  					PodAntiAffinity: &core.PodAntiAffinity{
 10589  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 10590  							{
 10591  								LabelSelector: &metav1.LabelSelector{
 10592  									MatchExpressions: []metav1.LabelSelectorRequirement{
 10593  										{
 10594  											Key:      "key",
 10595  											Operator: metav1.LabelSelectorOpNotIn,
 10596  											Values:   []string{"value1", "value2"},
 10597  										},
 10598  										{
 10599  											Key:      "key2",
 10600  											Operator: metav1.LabelSelectorOpIn,
 10601  											Values:   []string{"value1"},
 10602  										},
 10603  										{
 10604  											Key:      "key3",
 10605  											Operator: metav1.LabelSelectorOpNotIn,
 10606  											Values:   []string{"value1"},
 10607  										},
 10608  									},
 10609  								},
 10610  								TopologyKey:       "k8s.io/zone",
 10611  								MatchLabelKeys:    []string{"key2"},
 10612  								MismatchLabelKeys: []string{"key3"},
 10613  							},
 10614  						},
 10615  					},
 10616  				},
 10617  			},
 10618  		},
 10619  		"MatchLabelKeys/MismatchLabelKeys in preferred PodAntiAffinity": {
 10620  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10621  			Spec: core.PodSpec{
 10622  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10623  				RestartPolicy: core.RestartPolicyAlways,
 10624  				DNSPolicy:     core.DNSClusterFirst,
 10625  				Affinity: &core.Affinity{
 10626  					PodAntiAffinity: &core.PodAntiAffinity{
 10627  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 10628  							{
 10629  								Weight: 10,
 10630  								PodAffinityTerm: core.PodAffinityTerm{
 10631  									LabelSelector: &metav1.LabelSelector{
 10632  										MatchExpressions: []metav1.LabelSelectorRequirement{
 10633  											{
 10634  												Key:      "key",
 10635  												Operator: metav1.LabelSelectorOpNotIn,
 10636  												Values:   []string{"value1", "value2"},
 10637  											},
 10638  											{
 10639  												Key:      "key2",
 10640  												Operator: metav1.LabelSelectorOpIn,
 10641  												Values:   []string{"value1"},
 10642  											},
 10643  											{
 10644  												Key:      "key3",
 10645  												Operator: metav1.LabelSelectorOpNotIn,
 10646  												Values:   []string{"value1"},
 10647  											},
 10648  										},
 10649  									},
 10650  									TopologyKey:       "k8s.io/zone",
 10651  									MatchLabelKeys:    []string{"key2"},
 10652  									MismatchLabelKeys: []string{"key3"},
 10653  								},
 10654  							},
 10655  						},
 10656  					},
 10657  				},
 10658  			},
 10659  		},
 10660  		"LabelSelector can have the same key as MismatchLabelKeys": {
 10661  			// Note: On the contrary, in case of matchLabelKeys, keys in matchLabelKeys are not allowed to be specified in labelSelector by users.
 10662  			ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 10663  			Spec: core.PodSpec{
 10664  				Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10665  				RestartPolicy: core.RestartPolicyAlways,
 10666  				DNSPolicy:     core.DNSClusterFirst,
 10667  				Affinity: &core.Affinity{
 10668  					PodAffinity: &core.PodAffinity{
 10669  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 10670  							{
 10671  								LabelSelector: &metav1.LabelSelector{
 10672  									MatchExpressions: []metav1.LabelSelectorRequirement{
 10673  										{
 10674  											Key:      "key",
 10675  											Operator: metav1.LabelSelectorOpNotIn,
 10676  											Values:   []string{"value1", "value2"},
 10677  										},
 10678  										{
 10679  											// This is the same key as in MismatchLabelKeys
 10680  											// but it's allowed.
 10681  											Key:      "key2",
 10682  											Operator: metav1.LabelSelectorOpIn,
 10683  											Values:   []string{"value1"},
 10684  										},
 10685  										{
 10686  											Key:      "key2",
 10687  											Operator: metav1.LabelSelectorOpNotIn,
 10688  											Values:   []string{"value1"},
 10689  										},
 10690  									},
 10691  								},
 10692  								TopologyKey:       "k8s.io/zone",
 10693  								MismatchLabelKeys: []string{"key2"},
 10694  							},
 10695  						},
 10696  					},
 10697  				},
 10698  			},
 10699  		},
 10700  	}
 10701  
 10702  	for k, v := range successCases {
 10703  		t.Run(k, func(t *testing.T) {
 10704  			if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
 10705  				t.Errorf("expected success: %v", errs)
 10706  			}
 10707  		})
 10708  	}
 10709  
 10710  	errorCases := map[string]struct {
 10711  		spec          core.Pod
 10712  		expectedError string
 10713  	}{
 10714  		"bad name": {
 10715  			expectedError: "metadata.name",
 10716  			spec: core.Pod{
 10717  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"},
 10718  				Spec: core.PodSpec{
 10719  					RestartPolicy: core.RestartPolicyAlways,
 10720  					DNSPolicy:     core.DNSClusterFirst,
 10721  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10722  				},
 10723  			},
 10724  		},
 10725  		"image whitespace": {
 10726  			expectedError: "spec.containers[0].image",
 10727  			spec: core.Pod{
 10728  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 10729  				Spec: core.PodSpec{
 10730  					RestartPolicy: core.RestartPolicyAlways,
 10731  					DNSPolicy:     core.DNSClusterFirst,
 10732  					Containers:    []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10733  				},
 10734  			},
 10735  		},
 10736  		"image leading and trailing whitespace": {
 10737  			expectedError: "spec.containers[0].image",
 10738  			spec: core.Pod{
 10739  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 10740  				Spec: core.PodSpec{
 10741  					RestartPolicy: core.RestartPolicyAlways,
 10742  					DNSPolicy:     core.DNSClusterFirst,
 10743  					Containers:    []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10744  				},
 10745  			},
 10746  		},
 10747  		"bad namespace": {
 10748  			expectedError: "metadata.namespace",
 10749  			spec: core.Pod{
 10750  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""},
 10751  				Spec: core.PodSpec{
 10752  					RestartPolicy: core.RestartPolicyAlways,
 10753  					DNSPolicy:     core.DNSClusterFirst,
 10754  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10755  				},
 10756  			},
 10757  		},
 10758  		"bad spec": {
 10759  			expectedError: "spec.containers[0].name",
 10760  			spec: core.Pod{
 10761  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
 10762  				Spec: core.PodSpec{
 10763  					Containers: []core.Container{{}},
 10764  				},
 10765  			},
 10766  		},
 10767  		"bad label": {
 10768  			expectedError: "NoUppercaseOrSpecialCharsLike=Equals",
 10769  			spec: core.Pod{
 10770  				ObjectMeta: metav1.ObjectMeta{
 10771  					Name:      "abc",
 10772  					Namespace: "ns",
 10773  					Labels: map[string]string{
 10774  						"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 10775  					},
 10776  				},
 10777  				Spec: core.PodSpec{
 10778  					RestartPolicy: core.RestartPolicyAlways,
 10779  					DNSPolicy:     core.DNSClusterFirst,
 10780  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 10781  				},
 10782  			},
 10783  		},
 10784  		"invalid node selector requirement in node affinity, operator can't be null": {
 10785  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator",
 10786  			spec: core.Pod{
 10787  				ObjectMeta: metav1.ObjectMeta{
 10788  					Name:      "123",
 10789  					Namespace: "ns",
 10790  				},
 10791  				Spec: validPodSpec(&core.Affinity{
 10792  					NodeAffinity: &core.NodeAffinity{
 10793  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10794  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10795  								MatchExpressions: []core.NodeSelectorRequirement{{
 10796  									Key: "key1",
 10797  								}},
 10798  							}},
 10799  						},
 10800  					},
 10801  				}),
 10802  			},
 10803  		},
 10804  		"invalid node selector requirement in node affinity, key is invalid": {
 10805  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key",
 10806  			spec: core.Pod{
 10807  				ObjectMeta: metav1.ObjectMeta{
 10808  					Name:      "123",
 10809  					Namespace: "ns",
 10810  				},
 10811  				Spec: validPodSpec(&core.Affinity{
 10812  					NodeAffinity: &core.NodeAffinity{
 10813  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10814  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10815  								MatchExpressions: []core.NodeSelectorRequirement{{
 10816  									Key:      "invalid key ___@#",
 10817  									Operator: core.NodeSelectorOpExists,
 10818  								}},
 10819  							}},
 10820  						},
 10821  					},
 10822  				}),
 10823  			},
 10824  		},
 10825  		"invalid node field selector requirement in node affinity, more values for field selector": {
 10826  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values",
 10827  			spec: core.Pod{
 10828  				ObjectMeta: metav1.ObjectMeta{
 10829  					Name:      "123",
 10830  					Namespace: "ns",
 10831  				},
 10832  				Spec: validPodSpec(&core.Affinity{
 10833  					NodeAffinity: &core.NodeAffinity{
 10834  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10835  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10836  								MatchFields: []core.NodeSelectorRequirement{{
 10837  									Key:      "metadata.name",
 10838  									Operator: core.NodeSelectorOpIn,
 10839  									Values:   []string{"host1", "host2"},
 10840  								}},
 10841  							}},
 10842  						},
 10843  					},
 10844  				}),
 10845  			},
 10846  		},
 10847  		"invalid node field selector requirement in node affinity, invalid operator": {
 10848  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator",
 10849  			spec: core.Pod{
 10850  				ObjectMeta: metav1.ObjectMeta{
 10851  					Name:      "123",
 10852  					Namespace: "ns",
 10853  				},
 10854  				Spec: validPodSpec(&core.Affinity{
 10855  					NodeAffinity: &core.NodeAffinity{
 10856  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10857  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10858  								MatchFields: []core.NodeSelectorRequirement{{
 10859  									Key:      "metadata.name",
 10860  									Operator: core.NodeSelectorOpExists,
 10861  								}},
 10862  							}},
 10863  						},
 10864  					},
 10865  				}),
 10866  			},
 10867  		},
 10868  		"invalid node field selector requirement in node affinity, invalid key": {
 10869  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key",
 10870  			spec: core.Pod{
 10871  				ObjectMeta: metav1.ObjectMeta{
 10872  					Name:      "123",
 10873  					Namespace: "ns",
 10874  				},
 10875  				Spec: validPodSpec(&core.Affinity{
 10876  					NodeAffinity: &core.NodeAffinity{
 10877  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10878  							NodeSelectorTerms: []core.NodeSelectorTerm{{
 10879  								MatchFields: []core.NodeSelectorRequirement{{
 10880  									Key:      "metadata.namespace",
 10881  									Operator: core.NodeSelectorOpIn,
 10882  									Values:   []string{"ns1"},
 10883  								}},
 10884  							}},
 10885  						},
 10886  					},
 10887  				}),
 10888  			},
 10889  		},
 10890  		"invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": {
 10891  			expectedError: "must be in the range 1-100",
 10892  			spec: core.Pod{
 10893  				ObjectMeta: metav1.ObjectMeta{
 10894  					Name:      "123",
 10895  					Namespace: "ns",
 10896  				},
 10897  				Spec: validPodSpec(&core.Affinity{
 10898  					NodeAffinity: &core.NodeAffinity{
 10899  						PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 10900  							Weight: 199,
 10901  							Preference: core.NodeSelectorTerm{
 10902  								MatchExpressions: []core.NodeSelectorRequirement{{
 10903  									Key:      "foo",
 10904  									Operator: core.NodeSelectorOpIn,
 10905  									Values:   []string{"bar"},
 10906  								}},
 10907  							},
 10908  						}},
 10909  					},
 10910  				}),
 10911  			},
 10912  		},
 10913  		"invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": {
 10914  			expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms",
 10915  			spec: core.Pod{
 10916  				ObjectMeta: metav1.ObjectMeta{
 10917  					Name:      "123",
 10918  					Namespace: "ns",
 10919  				},
 10920  				Spec: validPodSpec(&core.Affinity{
 10921  					NodeAffinity: &core.NodeAffinity{
 10922  						RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 10923  							NodeSelectorTerms: []core.NodeSelectorTerm{},
 10924  						},
 10925  					},
 10926  				}),
 10927  			},
 10928  		},
 10929  		"invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
 10930  			expectedError: "must be in the range 1-100",
 10931  			spec: core.Pod{
 10932  				ObjectMeta: metav1.ObjectMeta{
 10933  					Name:      "123",
 10934  					Namespace: "ns",
 10935  				},
 10936  				Spec: validPodSpec(&core.Affinity{
 10937  					PodAffinity: &core.PodAffinity{
 10938  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10939  							Weight: 109,
 10940  							PodAffinityTerm: core.PodAffinityTerm{
 10941  								LabelSelector: &metav1.LabelSelector{
 10942  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 10943  										Key:      "key2",
 10944  										Operator: metav1.LabelSelectorOpNotIn,
 10945  										Values:   []string{"value1", "value2"},
 10946  									}},
 10947  								},
 10948  								Namespaces:  []string{"ns"},
 10949  								TopologyKey: "region",
 10950  							},
 10951  						}},
 10952  					},
 10953  				}),
 10954  			},
 10955  		},
 10956  		"invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
 10957  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.labelSelector.matchExpressions[0].values",
 10958  			spec: core.Pod{
 10959  				ObjectMeta: metav1.ObjectMeta{
 10960  					Name:      "123",
 10961  					Namespace: "ns",
 10962  				},
 10963  				Spec: validPodSpec(&core.Affinity{
 10964  					PodAntiAffinity: &core.PodAntiAffinity{
 10965  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10966  							Weight: 10,
 10967  							PodAffinityTerm: core.PodAffinityTerm{
 10968  								LabelSelector: &metav1.LabelSelector{
 10969  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 10970  										Key:      "key2",
 10971  										Operator: metav1.LabelSelectorOpExists,
 10972  										Values:   []string{"value1", "value2"},
 10973  									}},
 10974  								},
 10975  								Namespaces:  []string{"ns"},
 10976  								TopologyKey: "region",
 10977  							},
 10978  						}},
 10979  					},
 10980  				}),
 10981  			},
 10982  		},
 10983  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, In operator must include Values": {
 10984  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 10985  			spec: core.Pod{
 10986  				ObjectMeta: metav1.ObjectMeta{
 10987  					Name:      "123",
 10988  					Namespace: "ns",
 10989  				},
 10990  				Spec: validPodSpec(&core.Affinity{
 10991  					PodAntiAffinity: &core.PodAntiAffinity{
 10992  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 10993  							Weight: 10,
 10994  							PodAffinityTerm: core.PodAffinityTerm{
 10995  								NamespaceSelector: &metav1.LabelSelector{
 10996  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 10997  										Key:      "key2",
 10998  										Operator: metav1.LabelSelectorOpIn,
 10999  									}},
 11000  								},
 11001  								Namespaces:  []string{"ns"},
 11002  								TopologyKey: "region",
 11003  							},
 11004  						}},
 11005  					},
 11006  				}),
 11007  			},
 11008  		},
 11009  		"invalid namespaceSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity, Exists operator can not have values": {
 11010  			expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespaceSelector.matchExpressions[0].values",
 11011  			spec: core.Pod{
 11012  				ObjectMeta: metav1.ObjectMeta{
 11013  					Name:      "123",
 11014  					Namespace: "ns",
 11015  				},
 11016  				Spec: validPodSpec(&core.Affinity{
 11017  					PodAntiAffinity: &core.PodAntiAffinity{
 11018  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11019  							Weight: 10,
 11020  							PodAffinityTerm: core.PodAffinityTerm{
 11021  								NamespaceSelector: &metav1.LabelSelector{
 11022  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11023  										Key:      "key2",
 11024  										Operator: metav1.LabelSelectorOpExists,
 11025  										Values:   []string{"value1", "value2"},
 11026  									}},
 11027  								},
 11028  								Namespaces:  []string{"ns"},
 11029  								TopologyKey: "region",
 11030  							},
 11031  						}},
 11032  					},
 11033  				}),
 11034  			},
 11035  		},
 11036  		"invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, namespace should be valid": {
 11037  			expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace",
 11038  			spec: core.Pod{
 11039  				ObjectMeta: metav1.ObjectMeta{
 11040  					Name:      "123",
 11041  					Namespace: "ns",
 11042  				},
 11043  				Spec: validPodSpec(&core.Affinity{
 11044  					PodAffinity: &core.PodAffinity{
 11045  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11046  							Weight: 10,
 11047  							PodAffinityTerm: core.PodAffinityTerm{
 11048  								LabelSelector: &metav1.LabelSelector{
 11049  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11050  										Key:      "key2",
 11051  										Operator: metav1.LabelSelectorOpExists,
 11052  									}},
 11053  								},
 11054  								Namespaces:  []string{"INVALID_NAMESPACE"},
 11055  								TopologyKey: "region",
 11056  							},
 11057  						}},
 11058  					},
 11059  				}),
 11060  			},
 11061  		},
 11062  		"invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": {
 11063  			expectedError: "can not be empty",
 11064  			spec: core.Pod{
 11065  				ObjectMeta: metav1.ObjectMeta{
 11066  					Name:      "123",
 11067  					Namespace: "ns",
 11068  				},
 11069  				Spec: validPodSpec(&core.Affinity{
 11070  					PodAffinity: &core.PodAffinity{
 11071  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11072  							LabelSelector: &metav1.LabelSelector{
 11073  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11074  									Key:      "key2",
 11075  									Operator: metav1.LabelSelectorOpIn,
 11076  									Values:   []string{"value1", "value2"},
 11077  								}},
 11078  							},
 11079  							Namespaces: []string{"ns"},
 11080  						}},
 11081  					},
 11082  				}),
 11083  			},
 11084  		},
 11085  		"invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
 11086  			expectedError: "can not be empty",
 11087  			spec: core.Pod{
 11088  				ObjectMeta: metav1.ObjectMeta{
 11089  					Name:      "123",
 11090  					Namespace: "ns",
 11091  				},
 11092  				Spec: validPodSpec(&core.Affinity{
 11093  					PodAntiAffinity: &core.PodAntiAffinity{
 11094  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{{
 11095  							LabelSelector: &metav1.LabelSelector{
 11096  								MatchExpressions: []metav1.LabelSelectorRequirement{{
 11097  									Key:      "key2",
 11098  									Operator: metav1.LabelSelectorOpIn,
 11099  									Values:   []string{"value1", "value2"},
 11100  								}},
 11101  							},
 11102  							Namespaces: []string{"ns"},
 11103  						}},
 11104  					},
 11105  				}),
 11106  			},
 11107  		},
 11108  		"invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": {
 11109  			expectedError: "can not be empty",
 11110  			spec: core.Pod{
 11111  				ObjectMeta: metav1.ObjectMeta{
 11112  					Name:      "123",
 11113  					Namespace: "ns",
 11114  				},
 11115  				Spec: validPodSpec(&core.Affinity{
 11116  					PodAffinity: &core.PodAffinity{
 11117  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11118  							Weight: 10,
 11119  							PodAffinityTerm: core.PodAffinityTerm{
 11120  								LabelSelector: &metav1.LabelSelector{
 11121  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11122  										Key:      "key2",
 11123  										Operator: metav1.LabelSelectorOpNotIn,
 11124  										Values:   []string{"value1", "value2"},
 11125  									}},
 11126  								},
 11127  								Namespaces: []string{"ns"},
 11128  							},
 11129  						}},
 11130  					},
 11131  				}),
 11132  			},
 11133  		},
 11134  		"invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": {
 11135  			expectedError: "can not be empty",
 11136  			spec: core.Pod{
 11137  				ObjectMeta: metav1.ObjectMeta{
 11138  					Name:      "123",
 11139  					Namespace: "ns",
 11140  				},
 11141  				Spec: validPodSpec(&core.Affinity{
 11142  					PodAntiAffinity: &core.PodAntiAffinity{
 11143  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{{
 11144  							Weight: 10,
 11145  							PodAffinityTerm: core.PodAffinityTerm{
 11146  								LabelSelector: &metav1.LabelSelector{
 11147  									MatchExpressions: []metav1.LabelSelectorRequirement{{
 11148  										Key:      "key2",
 11149  										Operator: metav1.LabelSelectorOpNotIn,
 11150  										Values:   []string{"value1", "value2"},
 11151  									}},
 11152  								},
 11153  								Namespaces: []string{"ns"},
 11154  							},
 11155  						}},
 11156  					},
 11157  				}),
 11158  			},
 11159  		},
 11160  		"invalid soft pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11161  			expectedError: "prefix part must be non-empty",
 11162  			spec: core.Pod{
 11163  				ObjectMeta: metav1.ObjectMeta{
 11164  					Name:      "123",
 11165  					Namespace: "ns",
 11166  				},
 11167  				Spec: validPodSpec(&core.Affinity{
 11168  					PodAffinity: &core.PodAffinity{
 11169  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11170  							{
 11171  								Weight: 10,
 11172  								PodAffinityTerm: core.PodAffinityTerm{
 11173  									LabelSelector: &metav1.LabelSelector{
 11174  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11175  											{
 11176  												Key:      "key",
 11177  												Operator: metav1.LabelSelectorOpNotIn,
 11178  												Values:   []string{"value1", "value2"},
 11179  											},
 11180  										},
 11181  									},
 11182  									TopologyKey:    "k8s.io/zone",
 11183  									MatchLabelKeys: []string{"/simple"},
 11184  								},
 11185  							},
 11186  						},
 11187  					},
 11188  				}),
 11189  			},
 11190  		},
 11191  		"invalid hard pod affinity, key in MatchLabelKeys isn't correctly defined": {
 11192  			expectedError: "prefix part must be non-empty",
 11193  			spec: core.Pod{
 11194  				ObjectMeta: metav1.ObjectMeta{
 11195  					Name:      "123",
 11196  					Namespace: "ns",
 11197  				},
 11198  				Spec: validPodSpec(&core.Affinity{
 11199  					PodAffinity: &core.PodAffinity{
 11200  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11201  							{
 11202  								LabelSelector: &metav1.LabelSelector{
 11203  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11204  										{
 11205  											Key:      "key",
 11206  											Operator: metav1.LabelSelectorOpNotIn,
 11207  											Values:   []string{"value1", "value2"},
 11208  										},
 11209  									},
 11210  								},
 11211  								TopologyKey:    "k8s.io/zone",
 11212  								MatchLabelKeys: []string{"/simple"},
 11213  							},
 11214  						},
 11215  					},
 11216  				}),
 11217  			},
 11218  		},
 11219  		"invalid soft pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 11220  			expectedError: "prefix part must be non-empty",
 11221  			spec: core.Pod{
 11222  				ObjectMeta: metav1.ObjectMeta{
 11223  					Name:      "123",
 11224  					Namespace: "ns",
 11225  				},
 11226  				Spec: validPodSpec(&core.Affinity{
 11227  					PodAntiAffinity: &core.PodAntiAffinity{
 11228  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11229  							{
 11230  								Weight: 10,
 11231  								PodAffinityTerm: core.PodAffinityTerm{
 11232  									LabelSelector: &metav1.LabelSelector{
 11233  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11234  											{
 11235  												Key:      "key",
 11236  												Operator: metav1.LabelSelectorOpNotIn,
 11237  												Values:   []string{"value1", "value2"},
 11238  											},
 11239  										},
 11240  									},
 11241  									TopologyKey:    "k8s.io/zone",
 11242  									MatchLabelKeys: []string{"/simple"},
 11243  								},
 11244  							},
 11245  						},
 11246  					},
 11247  				}),
 11248  			},
 11249  		},
 11250  		"invalid hard pod anti-affinity, key in MatchLabelKeys isn't correctly defined": {
 11251  			expectedError: "prefix part must be non-empty",
 11252  			spec: core.Pod{
 11253  				ObjectMeta: metav1.ObjectMeta{
 11254  					Name:      "123",
 11255  					Namespace: "ns",
 11256  				},
 11257  				Spec: validPodSpec(&core.Affinity{
 11258  					PodAntiAffinity: &core.PodAntiAffinity{
 11259  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11260  							{
 11261  								LabelSelector: &metav1.LabelSelector{
 11262  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11263  										{
 11264  											Key:      "key",
 11265  											Operator: metav1.LabelSelectorOpNotIn,
 11266  											Values:   []string{"value1", "value2"},
 11267  										},
 11268  									},
 11269  								},
 11270  								TopologyKey:    "k8s.io/zone",
 11271  								MatchLabelKeys: []string{"/simple"},
 11272  							},
 11273  						},
 11274  					},
 11275  				}),
 11276  			},
 11277  		},
 11278  		"invalid soft pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 11279  			expectedError: "prefix part must be non-empty",
 11280  			spec: core.Pod{
 11281  				ObjectMeta: metav1.ObjectMeta{
 11282  					Name:      "123",
 11283  					Namespace: "ns",
 11284  				},
 11285  				Spec: validPodSpec(&core.Affinity{
 11286  					PodAffinity: &core.PodAffinity{
 11287  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11288  							{
 11289  								Weight: 10,
 11290  								PodAffinityTerm: core.PodAffinityTerm{
 11291  									LabelSelector: &metav1.LabelSelector{
 11292  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11293  											{
 11294  												Key:      "key",
 11295  												Operator: metav1.LabelSelectorOpNotIn,
 11296  												Values:   []string{"value1", "value2"},
 11297  											},
 11298  										},
 11299  									},
 11300  									TopologyKey:       "k8s.io/zone",
 11301  									MismatchLabelKeys: []string{"/simple"},
 11302  								},
 11303  							},
 11304  						},
 11305  					},
 11306  				}),
 11307  			},
 11308  		},
 11309  		"invalid hard pod affinity, key in MismatchLabelKeys isn't correctly defined": {
 11310  			expectedError: "prefix part must be non-empty",
 11311  			spec: core.Pod{
 11312  				ObjectMeta: metav1.ObjectMeta{
 11313  					Name:      "123",
 11314  					Namespace: "ns",
 11315  				},
 11316  				Spec: validPodSpec(&core.Affinity{
 11317  					PodAffinity: &core.PodAffinity{
 11318  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11319  							{
 11320  								LabelSelector: &metav1.LabelSelector{
 11321  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11322  										{
 11323  											Key:      "key",
 11324  											Operator: metav1.LabelSelectorOpNotIn,
 11325  											Values:   []string{"value1", "value2"},
 11326  										},
 11327  									},
 11328  								},
 11329  								TopologyKey:       "k8s.io/zone",
 11330  								MismatchLabelKeys: []string{"/simple"},
 11331  							},
 11332  						},
 11333  					},
 11334  				}),
 11335  			},
 11336  		},
 11337  		"invalid soft pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 11338  			expectedError: "prefix part must be non-empty",
 11339  			spec: core.Pod{
 11340  				ObjectMeta: metav1.ObjectMeta{
 11341  					Name:      "123",
 11342  					Namespace: "ns",
 11343  				},
 11344  				Spec: validPodSpec(&core.Affinity{
 11345  					PodAntiAffinity: &core.PodAntiAffinity{
 11346  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11347  							{
 11348  								Weight: 10,
 11349  								PodAffinityTerm: core.PodAffinityTerm{
 11350  									LabelSelector: &metav1.LabelSelector{
 11351  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11352  											{
 11353  												Key:      "key",
 11354  												Operator: metav1.LabelSelectorOpNotIn,
 11355  												Values:   []string{"value1", "value2"},
 11356  											},
 11357  										},
 11358  									},
 11359  									TopologyKey:       "k8s.io/zone",
 11360  									MismatchLabelKeys: []string{"/simple"},
 11361  								},
 11362  							},
 11363  						},
 11364  					},
 11365  				}),
 11366  			},
 11367  		},
 11368  		"invalid hard pod anti-affinity, key in MismatchLabelKeys isn't correctly defined": {
 11369  			expectedError: "prefix part must be non-empty",
 11370  			spec: core.Pod{
 11371  				ObjectMeta: metav1.ObjectMeta{
 11372  					Name:      "123",
 11373  					Namespace: "ns",
 11374  				},
 11375  				Spec: validPodSpec(&core.Affinity{
 11376  					PodAntiAffinity: &core.PodAntiAffinity{
 11377  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11378  							{
 11379  								LabelSelector: &metav1.LabelSelector{
 11380  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11381  										{
 11382  											Key:      "key",
 11383  											Operator: metav1.LabelSelectorOpNotIn,
 11384  											Values:   []string{"value1", "value2"},
 11385  										},
 11386  									},
 11387  								},
 11388  								TopologyKey:       "k8s.io/zone",
 11389  								MismatchLabelKeys: []string{"/simple"},
 11390  							},
 11391  						},
 11392  					},
 11393  				}),
 11394  			},
 11395  		},
 11396  		"invalid soft pod affinity, key exists in both matchLabelKeys and labelSelector": {
 11397  			expectedError: "exists in both matchLabelKeys and labelSelector",
 11398  			spec: core.Pod{
 11399  				ObjectMeta: metav1.ObjectMeta{
 11400  					Name:      "123",
 11401  					Namespace: "ns",
 11402  					Labels:    map[string]string{"key": "value1"},
 11403  				},
 11404  				Spec: validPodSpec(&core.Affinity{
 11405  					PodAffinity: &core.PodAffinity{
 11406  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11407  							{
 11408  								Weight: 10,
 11409  								PodAffinityTerm: core.PodAffinityTerm{
 11410  									LabelSelector: &metav1.LabelSelector{
 11411  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11412  											// This one should be created from MatchLabelKeys.
 11413  											{
 11414  												Key:      "key",
 11415  												Operator: metav1.LabelSelectorOpIn,
 11416  												Values:   []string{"value1"},
 11417  											},
 11418  											{
 11419  												Key:      "key",
 11420  												Operator: metav1.LabelSelectorOpNotIn,
 11421  												Values:   []string{"value2"},
 11422  											},
 11423  										},
 11424  									},
 11425  									TopologyKey:    "k8s.io/zone",
 11426  									MatchLabelKeys: []string{"key"},
 11427  								},
 11428  							},
 11429  						},
 11430  					},
 11431  				}),
 11432  			},
 11433  		},
 11434  		"invalid hard pod affinity, key exists in both matchLabelKeys and labelSelector": {
 11435  			expectedError: "exists in both matchLabelKeys and labelSelector",
 11436  			spec: core.Pod{
 11437  				ObjectMeta: metav1.ObjectMeta{
 11438  					Name:      "123",
 11439  					Namespace: "ns",
 11440  					Labels:    map[string]string{"key": "value1"},
 11441  				},
 11442  				Spec: validPodSpec(&core.Affinity{
 11443  					PodAffinity: &core.PodAffinity{
 11444  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11445  							{
 11446  								LabelSelector: &metav1.LabelSelector{
 11447  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11448  										// This one should be created from MatchLabelKeys.
 11449  										{
 11450  											Key:      "key",
 11451  											Operator: metav1.LabelSelectorOpIn,
 11452  											Values:   []string{"value1"},
 11453  										},
 11454  										{
 11455  											Key:      "key",
 11456  											Operator: metav1.LabelSelectorOpNotIn,
 11457  											Values:   []string{"value2"},
 11458  										},
 11459  									},
 11460  								},
 11461  								TopologyKey:    "k8s.io/zone",
 11462  								MatchLabelKeys: []string{"key"},
 11463  							},
 11464  						},
 11465  					},
 11466  				}),
 11467  			},
 11468  		},
 11469  		"invalid soft pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 11470  			expectedError: "exists in both matchLabelKeys and labelSelector",
 11471  			spec: core.Pod{
 11472  				ObjectMeta: metav1.ObjectMeta{
 11473  					Name:      "123",
 11474  					Namespace: "ns",
 11475  					Labels:    map[string]string{"key": "value1"},
 11476  				},
 11477  				Spec: validPodSpec(&core.Affinity{
 11478  					PodAntiAffinity: &core.PodAntiAffinity{
 11479  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11480  							{
 11481  								Weight: 10,
 11482  								PodAffinityTerm: core.PodAffinityTerm{
 11483  									LabelSelector: &metav1.LabelSelector{
 11484  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11485  											// This one should be created from MatchLabelKeys.
 11486  											{
 11487  												Key:      "key",
 11488  												Operator: metav1.LabelSelectorOpIn,
 11489  												Values:   []string{"value1"},
 11490  											},
 11491  											{
 11492  												Key:      "key",
 11493  												Operator: metav1.LabelSelectorOpNotIn,
 11494  												Values:   []string{"value2"},
 11495  											},
 11496  										},
 11497  									},
 11498  									TopologyKey:    "k8s.io/zone",
 11499  									MatchLabelKeys: []string{"key"},
 11500  								},
 11501  							},
 11502  						},
 11503  					},
 11504  				}),
 11505  			},
 11506  		},
 11507  		"invalid hard pod anti-affinity, key exists in both matchLabelKeys and labelSelector": {
 11508  			expectedError: "exists in both matchLabelKeys and labelSelector",
 11509  			spec: core.Pod{
 11510  				ObjectMeta: metav1.ObjectMeta{
 11511  					Name:      "123",
 11512  					Namespace: "ns",
 11513  					Labels:    map[string]string{"key": "value1"},
 11514  				},
 11515  				Spec: validPodSpec(&core.Affinity{
 11516  					PodAntiAffinity: &core.PodAntiAffinity{
 11517  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11518  							{
 11519  								LabelSelector: &metav1.LabelSelector{
 11520  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11521  										// This one should be created from MatchLabelKeys.
 11522  										{
 11523  											Key:      "key",
 11524  											Operator: metav1.LabelSelectorOpIn,
 11525  											Values:   []string{"value1"},
 11526  										},
 11527  										{
 11528  											Key:      "key",
 11529  											Operator: metav1.LabelSelectorOpNotIn,
 11530  											Values:   []string{"value2"},
 11531  										},
 11532  									},
 11533  								},
 11534  								TopologyKey:    "k8s.io/zone",
 11535  								MatchLabelKeys: []string{"key"},
 11536  							},
 11537  						},
 11538  					},
 11539  				}),
 11540  			},
 11541  		},
 11542  		"invalid soft pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 11543  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 11544  			spec: core.Pod{
 11545  				ObjectMeta: metav1.ObjectMeta{
 11546  					Name:      "123",
 11547  					Namespace: "ns",
 11548  				},
 11549  				Spec: validPodSpec(&core.Affinity{
 11550  					PodAffinity: &core.PodAffinity{
 11551  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11552  							{
 11553  								Weight: 10,
 11554  								PodAffinityTerm: core.PodAffinityTerm{
 11555  									LabelSelector: &metav1.LabelSelector{
 11556  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11557  											{
 11558  												Key:      "key",
 11559  												Operator: metav1.LabelSelectorOpNotIn,
 11560  												Values:   []string{"value1", "value2"},
 11561  											},
 11562  										},
 11563  									},
 11564  									TopologyKey:       "k8s.io/zone",
 11565  									MatchLabelKeys:    []string{"samekey"},
 11566  									MismatchLabelKeys: []string{"samekey"},
 11567  								},
 11568  							},
 11569  						},
 11570  					},
 11571  				}),
 11572  			},
 11573  		},
 11574  		"invalid hard pod affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 11575  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 11576  			spec: core.Pod{
 11577  				ObjectMeta: metav1.ObjectMeta{
 11578  					Name:      "123",
 11579  					Namespace: "ns",
 11580  				},
 11581  				Spec: validPodSpec(&core.Affinity{
 11582  					PodAffinity: &core.PodAffinity{
 11583  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11584  							{
 11585  								LabelSelector: &metav1.LabelSelector{
 11586  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11587  										{
 11588  											Key:      "key",
 11589  											Operator: metav1.LabelSelectorOpNotIn,
 11590  											Values:   []string{"value1", "value2"},
 11591  										},
 11592  									},
 11593  								},
 11594  								TopologyKey:       "k8s.io/zone",
 11595  								MatchLabelKeys:    []string{"samekey"},
 11596  								MismatchLabelKeys: []string{"samekey"},
 11597  							},
 11598  						},
 11599  					},
 11600  				}),
 11601  			},
 11602  		},
 11603  		"invalid soft pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 11604  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 11605  			spec: core.Pod{
 11606  				ObjectMeta: metav1.ObjectMeta{
 11607  					Name:      "123",
 11608  					Namespace: "ns",
 11609  				},
 11610  				Spec: validPodSpec(&core.Affinity{
 11611  					PodAntiAffinity: &core.PodAntiAffinity{
 11612  						PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
 11613  							{
 11614  								Weight: 10,
 11615  								PodAffinityTerm: core.PodAffinityTerm{
 11616  									LabelSelector: &metav1.LabelSelector{
 11617  										MatchExpressions: []metav1.LabelSelectorRequirement{
 11618  											{
 11619  												Key:      "key",
 11620  												Operator: metav1.LabelSelectorOpNotIn,
 11621  												Values:   []string{"value1", "value2"},
 11622  											},
 11623  										},
 11624  									},
 11625  									TopologyKey:       "k8s.io/zone",
 11626  									MatchLabelKeys:    []string{"samekey"},
 11627  									MismatchLabelKeys: []string{"samekey"},
 11628  								},
 11629  							},
 11630  						},
 11631  					},
 11632  				}),
 11633  			},
 11634  		},
 11635  		"invalid hard pod anti-affinity, key exists in both MatchLabelKeys and MismatchLabelKeys": {
 11636  			expectedError: "exists in both matchLabelKeys and mismatchLabelKeys",
 11637  			spec: core.Pod{
 11638  				ObjectMeta: metav1.ObjectMeta{
 11639  					Name:      "123",
 11640  					Namespace: "ns",
 11641  				},
 11642  				Spec: validPodSpec(&core.Affinity{
 11643  					PodAntiAffinity: &core.PodAntiAffinity{
 11644  						RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 11645  							{
 11646  								LabelSelector: &metav1.LabelSelector{
 11647  									MatchExpressions: []metav1.LabelSelectorRequirement{
 11648  										{
 11649  											Key:      "key",
 11650  											Operator: metav1.LabelSelectorOpNotIn,
 11651  											Values:   []string{"value1", "value2"},
 11652  										},
 11653  									},
 11654  								},
 11655  								TopologyKey:       "k8s.io/zone",
 11656  								MatchLabelKeys:    []string{"samekey"},
 11657  								MismatchLabelKeys: []string{"samekey"},
 11658  							},
 11659  						},
 11660  					},
 11661  				}),
 11662  			},
 11663  		},
 11664  		"invalid toleration key": {
 11665  			expectedError: "spec.tolerations[0].key",
 11666  			spec: core.Pod{
 11667  				ObjectMeta: metav1.ObjectMeta{
 11668  					Name:      "123",
 11669  					Namespace: "ns",
 11670  				},
 11671  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 11672  			},
 11673  		},
 11674  		"invalid toleration operator": {
 11675  			expectedError: "spec.tolerations[0].operator",
 11676  			spec: core.Pod{
 11677  				ObjectMeta: metav1.ObjectMeta{
 11678  					Name:      "123",
 11679  					Namespace: "ns",
 11680  				},
 11681  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}),
 11682  			},
 11683  		},
 11684  		"value must be empty when `operator` is 'Exists'": {
 11685  			expectedError: "spec.tolerations[0].operator",
 11686  			spec: core.Pod{
 11687  				ObjectMeta: metav1.ObjectMeta{
 11688  					Name:      "123",
 11689  					Namespace: "ns",
 11690  				},
 11691  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}),
 11692  			},
 11693  		},
 11694  
 11695  		"operator must be 'Exists' when `key` is empty": {
 11696  			expectedError: "spec.tolerations[0].operator",
 11697  			spec: core.Pod{
 11698  				ObjectMeta: metav1.ObjectMeta{
 11699  					Name:      "123",
 11700  					Namespace: "ns",
 11701  				},
 11702  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
 11703  			},
 11704  		},
 11705  		"effect must be 'NoExecute' when `TolerationSeconds` is set": {
 11706  			expectedError: "spec.tolerations[0].effect",
 11707  			spec: core.Pod{
 11708  				ObjectMeta: metav1.ObjectMeta{
 11709  					Name:      "pod-forgiveness-invalid",
 11710  					Namespace: "ns",
 11711  				},
 11712  				Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}),
 11713  			},
 11714  		},
 11715  		"must be a valid pod seccomp profile": {
 11716  			expectedError: "must be a valid seccomp profile",
 11717  			spec: core.Pod{
 11718  				ObjectMeta: metav1.ObjectMeta{
 11719  					Name:      "123",
 11720  					Namespace: "ns",
 11721  					Annotations: map[string]string{
 11722  						core.SeccompPodAnnotationKey: "foo",
 11723  					},
 11724  				},
 11725  				Spec: validPodSpec(nil),
 11726  			},
 11727  		},
 11728  		"must be a valid container seccomp profile": {
 11729  			expectedError: "must be a valid seccomp profile",
 11730  			spec: core.Pod{
 11731  				ObjectMeta: metav1.ObjectMeta{
 11732  					Name:      "123",
 11733  					Namespace: "ns",
 11734  					Annotations: map[string]string{
 11735  						core.SeccompContainerAnnotationKeyPrefix + "foo": "foo",
 11736  					},
 11737  				},
 11738  				Spec: validPodSpec(nil),
 11739  			},
 11740  		},
 11741  		"must be a non-empty container name in seccomp annotation": {
 11742  			expectedError: "name part must be non-empty",
 11743  			spec: core.Pod{
 11744  				ObjectMeta: metav1.ObjectMeta{
 11745  					Name:      "123",
 11746  					Namespace: "ns",
 11747  					Annotations: map[string]string{
 11748  						core.SeccompContainerAnnotationKeyPrefix: "foo",
 11749  					},
 11750  				},
 11751  				Spec: validPodSpec(nil),
 11752  			},
 11753  		},
 11754  		"must be a non-empty container profile in seccomp annotation": {
 11755  			expectedError: "must be a valid seccomp profile",
 11756  			spec: core.Pod{
 11757  				ObjectMeta: metav1.ObjectMeta{
 11758  					Name:      "123",
 11759  					Namespace: "ns",
 11760  					Annotations: map[string]string{
 11761  						core.SeccompContainerAnnotationKeyPrefix + "foo": "",
 11762  					},
 11763  				},
 11764  				Spec: validPodSpec(nil),
 11765  			},
 11766  		},
 11767  		"must match seccomp profile type and pod annotation": {
 11768  			expectedError: "seccomp type in annotation and field must match",
 11769  			spec: core.Pod{
 11770  				ObjectMeta: metav1.ObjectMeta{
 11771  					Name:      "123",
 11772  					Namespace: "ns",
 11773  					Annotations: map[string]string{
 11774  						core.SeccompPodAnnotationKey: "unconfined",
 11775  					},
 11776  				},
 11777  				Spec: core.PodSpec{
 11778  					SecurityContext: &core.PodSecurityContext{
 11779  						SeccompProfile: &core.SeccompProfile{
 11780  							Type: core.SeccompProfileTypeRuntimeDefault,
 11781  						},
 11782  					},
 11783  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11784  					RestartPolicy: core.RestartPolicyAlways,
 11785  					DNSPolicy:     core.DNSClusterFirst,
 11786  				},
 11787  			},
 11788  		},
 11789  		"must match seccomp profile type and container annotation": {
 11790  			expectedError: "seccomp type in annotation and field must match",
 11791  			spec: core.Pod{
 11792  				ObjectMeta: metav1.ObjectMeta{
 11793  					Name:      "123",
 11794  					Namespace: "ns",
 11795  					Annotations: map[string]string{
 11796  						core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined",
 11797  					},
 11798  				},
 11799  				Spec: core.PodSpec{
 11800  					Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 11801  						SecurityContext: &core.SecurityContext{
 11802  							SeccompProfile: &core.SeccompProfile{
 11803  								Type: core.SeccompProfileTypeRuntimeDefault,
 11804  							},
 11805  						}}},
 11806  					RestartPolicy: core.RestartPolicyAlways,
 11807  					DNSPolicy:     core.DNSClusterFirst,
 11808  				},
 11809  			},
 11810  		},
 11811  		"must be a relative path in a node-local seccomp profile annotation": {
 11812  			expectedError: "must be a relative path",
 11813  			spec: core.Pod{
 11814  				ObjectMeta: metav1.ObjectMeta{
 11815  					Name:      "123",
 11816  					Namespace: "ns",
 11817  					Annotations: map[string]string{
 11818  						core.SeccompPodAnnotationKey: "localhost//foo",
 11819  					},
 11820  				},
 11821  				Spec: validPodSpec(nil),
 11822  			},
 11823  		},
 11824  		"must not start with '../'": {
 11825  			expectedError: "must not contain '..'",
 11826  			spec: core.Pod{
 11827  				ObjectMeta: metav1.ObjectMeta{
 11828  					Name:      "123",
 11829  					Namespace: "ns",
 11830  					Annotations: map[string]string{
 11831  						core.SeccompPodAnnotationKey: "localhost/../foo",
 11832  					},
 11833  				},
 11834  				Spec: validPodSpec(nil),
 11835  			},
 11836  		},
 11837  		"AppArmor profile must apply to a container": {
 11838  			expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]",
 11839  			spec: core.Pod{
 11840  				ObjectMeta: metav1.ObjectMeta{
 11841  					Name:      "123",
 11842  					Namespace: "ns",
 11843  					Annotations: map[string]string{
 11844  						v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr":      v1.AppArmorBetaProfileRuntimeDefault,
 11845  						v1.AppArmorBetaContainerAnnotationKeyPrefix + "init-ctr": v1.AppArmorBetaProfileRuntimeDefault,
 11846  						v1.AppArmorBetaContainerAnnotationKeyPrefix + "fake-ctr": v1.AppArmorBetaProfileRuntimeDefault,
 11847  					},
 11848  				},
 11849  				Spec: core.PodSpec{
 11850  					InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11851  					Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11852  					RestartPolicy:  core.RestartPolicyAlways,
 11853  					DNSPolicy:      core.DNSClusterFirst,
 11854  				},
 11855  			},
 11856  		},
 11857  		"AppArmor profile format must be valid": {
 11858  			expectedError: "invalid AppArmor profile name",
 11859  			spec: core.Pod{
 11860  				ObjectMeta: metav1.ObjectMeta{
 11861  					Name:      "123",
 11862  					Namespace: "ns",
 11863  					Annotations: map[string]string{
 11864  						v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "bad-name",
 11865  					},
 11866  				},
 11867  				Spec: validPodSpec(nil),
 11868  			},
 11869  		},
 11870  		"only default AppArmor profile may start with runtime/": {
 11871  			expectedError: "invalid AppArmor profile name",
 11872  			spec: core.Pod{
 11873  				ObjectMeta: metav1.ObjectMeta{
 11874  					Name:      "123",
 11875  					Namespace: "ns",
 11876  					Annotations: map[string]string{
 11877  						v1.AppArmorBetaContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
 11878  					},
 11879  				},
 11880  				Spec: validPodSpec(nil),
 11881  			},
 11882  		},
 11883  		"invalid extended resource name in container request": {
 11884  			expectedError: "must be a standard resource for containers",
 11885  			spec: core.Pod{
 11886  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11887  				Spec: core.PodSpec{
 11888  					Containers: []core.Container{{
 11889  						Name:            "invalid",
 11890  						Image:           "image",
 11891  						ImagePullPolicy: "IfNotPresent",
 11892  						Resources: core.ResourceRequirements{
 11893  							Requests: core.ResourceList{
 11894  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 11895  							},
 11896  							Limits: core.ResourceList{
 11897  								core.ResourceName("invalid-name"): resource.MustParse("2"),
 11898  							},
 11899  						},
 11900  					}},
 11901  					RestartPolicy: core.RestartPolicyAlways,
 11902  					DNSPolicy:     core.DNSClusterFirst,
 11903  				},
 11904  			},
 11905  		},
 11906  		"invalid extended resource requirement: request must be == limit": {
 11907  			expectedError: "must be equal to example.com/a",
 11908  			spec: core.Pod{
 11909  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11910  				Spec: core.PodSpec{
 11911  					Containers: []core.Container{{
 11912  						Name:            "invalid",
 11913  						Image:           "image",
 11914  						ImagePullPolicy: "IfNotPresent",
 11915  						Resources: core.ResourceRequirements{
 11916  							Requests: core.ResourceList{
 11917  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 11918  							},
 11919  							Limits: core.ResourceList{
 11920  								core.ResourceName("example.com/a"): resource.MustParse("1"),
 11921  							},
 11922  						},
 11923  					}},
 11924  					RestartPolicy: core.RestartPolicyAlways,
 11925  					DNSPolicy:     core.DNSClusterFirst,
 11926  				},
 11927  			},
 11928  		},
 11929  		"invalid extended resource requirement without limit": {
 11930  			expectedError: "Limit must be set",
 11931  			spec: core.Pod{
 11932  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11933  				Spec: core.PodSpec{
 11934  					Containers: []core.Container{{
 11935  						Name:            "invalid",
 11936  						Image:           "image",
 11937  						ImagePullPolicy: "IfNotPresent",
 11938  						Resources: core.ResourceRequirements{
 11939  							Requests: core.ResourceList{
 11940  								core.ResourceName("example.com/a"): resource.MustParse("2"),
 11941  							},
 11942  						},
 11943  					}},
 11944  					RestartPolicy: core.RestartPolicyAlways,
 11945  					DNSPolicy:     core.DNSClusterFirst,
 11946  				},
 11947  			},
 11948  		},
 11949  		"invalid fractional extended resource in container request": {
 11950  			expectedError: "must be an integer",
 11951  			spec: core.Pod{
 11952  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11953  				Spec: core.PodSpec{
 11954  					Containers: []core.Container{{
 11955  						Name:            "invalid",
 11956  						Image:           "image",
 11957  						ImagePullPolicy: "IfNotPresent",
 11958  						Resources: core.ResourceRequirements{
 11959  							Requests: core.ResourceList{
 11960  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 11961  							},
 11962  						},
 11963  					}},
 11964  					RestartPolicy: core.RestartPolicyAlways,
 11965  					DNSPolicy:     core.DNSClusterFirst,
 11966  				},
 11967  			},
 11968  		},
 11969  		"invalid fractional extended resource in init container request": {
 11970  			expectedError: "must be an integer",
 11971  			spec: core.Pod{
 11972  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11973  				Spec: core.PodSpec{
 11974  					InitContainers: []core.Container{{
 11975  						Name:            "invalid",
 11976  						Image:           "image",
 11977  						ImagePullPolicy: "IfNotPresent",
 11978  						Resources: core.ResourceRequirements{
 11979  							Requests: core.ResourceList{
 11980  								core.ResourceName("example.com/a"): resource.MustParse("500m"),
 11981  							},
 11982  						},
 11983  					}},
 11984  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 11985  					RestartPolicy: core.RestartPolicyAlways,
 11986  					DNSPolicy:     core.DNSClusterFirst,
 11987  				},
 11988  			},
 11989  		},
 11990  		"invalid fractional extended resource in container limit": {
 11991  			expectedError: "must be an integer",
 11992  			spec: core.Pod{
 11993  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 11994  				Spec: core.PodSpec{
 11995  					Containers: []core.Container{{
 11996  						Name:            "invalid",
 11997  						Image:           "image",
 11998  						ImagePullPolicy: "IfNotPresent",
 11999  						Resources: core.ResourceRequirements{
 12000  							Requests: core.ResourceList{
 12001  								core.ResourceName("example.com/a"): resource.MustParse("5"),
 12002  							},
 12003  							Limits: core.ResourceList{
 12004  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12005  							},
 12006  						},
 12007  					}},
 12008  					RestartPolicy: core.RestartPolicyAlways,
 12009  					DNSPolicy:     core.DNSClusterFirst,
 12010  				},
 12011  			},
 12012  		},
 12013  		"invalid fractional extended resource in init container limit": {
 12014  			expectedError: "must be an integer",
 12015  			spec: core.Pod{
 12016  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12017  				Spec: core.PodSpec{
 12018  					InitContainers: []core.Container{{
 12019  						Name:            "invalid",
 12020  						Image:           "image",
 12021  						ImagePullPolicy: "IfNotPresent",
 12022  						Resources: core.ResourceRequirements{
 12023  							Requests: core.ResourceList{
 12024  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12025  							},
 12026  							Limits: core.ResourceList{
 12027  								core.ResourceName("example.com/a"): resource.MustParse("2.5"),
 12028  							},
 12029  						},
 12030  					}},
 12031  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12032  					RestartPolicy: core.RestartPolicyAlways,
 12033  					DNSPolicy:     core.DNSClusterFirst,
 12034  				},
 12035  			},
 12036  		},
 12037  		"mirror-pod present without nodeName": {
 12038  			expectedError: "mirror",
 12039  			spec: core.Pod{
 12040  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}},
 12041  				Spec: core.PodSpec{
 12042  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12043  					RestartPolicy: core.RestartPolicyAlways,
 12044  					DNSPolicy:     core.DNSClusterFirst,
 12045  				},
 12046  			},
 12047  		},
 12048  		"mirror-pod populated without nodeName": {
 12049  			expectedError: "mirror",
 12050  			spec: core.Pod{
 12051  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}},
 12052  				Spec: core.PodSpec{
 12053  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12054  					RestartPolicy: core.RestartPolicyAlways,
 12055  					DNSPolicy:     core.DNSClusterFirst,
 12056  				},
 12057  			},
 12058  		},
 12059  		"serviceaccount token projected volume with no serviceaccount name specified": {
 12060  			expectedError: "must not be specified when serviceAccountName is not set",
 12061  			spec: core.Pod{
 12062  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12063  				Spec: core.PodSpec{
 12064  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12065  					RestartPolicy: core.RestartPolicyAlways,
 12066  					DNSPolicy:     core.DNSClusterFirst,
 12067  					Volumes: []core.Volume{{
 12068  						Name: "projected-volume",
 12069  						VolumeSource: core.VolumeSource{
 12070  							Projected: &core.ProjectedVolumeSource{
 12071  								Sources: []core.VolumeProjection{{
 12072  									ServiceAccountToken: &core.ServiceAccountTokenProjection{
 12073  										Audience:          "foo-audience",
 12074  										ExpirationSeconds: 6000,
 12075  										Path:              "foo-path",
 12076  									},
 12077  								}},
 12078  							},
 12079  						},
 12080  					}},
 12081  				},
 12082  			},
 12083  		},
 12084  		"ClusterTrustBundlePEM projected volume using both byName and bySigner": {
 12085  			expectedError: "only one of name and signerName may be used",
 12086  			spec: core.Pod{
 12087  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 12088  				Spec: core.PodSpec{
 12089  					ServiceAccountName: "some-service-account",
 12090  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12091  					RestartPolicy:      core.RestartPolicyAlways,
 12092  					DNSPolicy:          core.DNSClusterFirst,
 12093  					Volumes: []core.Volume{
 12094  						{
 12095  							Name: "projected-volume",
 12096  							VolumeSource: core.VolumeSource{
 12097  								Projected: &core.ProjectedVolumeSource{
 12098  									Sources: []core.VolumeProjection{
 12099  										{
 12100  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 12101  												Path:       "foo-path",
 12102  												SignerName: utilpointer.String("example.com/foo"),
 12103  												LabelSelector: &metav1.LabelSelector{
 12104  													MatchLabels: map[string]string{
 12105  														"version": "live",
 12106  													},
 12107  												},
 12108  												Name: utilpointer.String("foo"),
 12109  											},
 12110  										},
 12111  									},
 12112  								},
 12113  							},
 12114  						},
 12115  					},
 12116  				},
 12117  			},
 12118  		},
 12119  		"ClusterTrustBundlePEM projected volume byName with no name": {
 12120  			expectedError: "must be a valid object name",
 12121  			spec: core.Pod{
 12122  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 12123  				Spec: core.PodSpec{
 12124  					ServiceAccountName: "some-service-account",
 12125  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12126  					RestartPolicy:      core.RestartPolicyAlways,
 12127  					DNSPolicy:          core.DNSClusterFirst,
 12128  					Volumes: []core.Volume{
 12129  						{
 12130  							Name: "projected-volume",
 12131  							VolumeSource: core.VolumeSource{
 12132  								Projected: &core.ProjectedVolumeSource{
 12133  									Sources: []core.VolumeProjection{
 12134  										{
 12135  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 12136  												Path: "foo-path",
 12137  												Name: utilpointer.String(""),
 12138  											},
 12139  										},
 12140  									},
 12141  								},
 12142  							},
 12143  						},
 12144  					},
 12145  				},
 12146  			},
 12147  		},
 12148  		"ClusterTrustBundlePEM projected volume bySigner with no signer name": {
 12149  			expectedError: "must be a valid signer name",
 12150  			spec: core.Pod{
 12151  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 12152  				Spec: core.PodSpec{
 12153  					ServiceAccountName: "some-service-account",
 12154  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12155  					RestartPolicy:      core.RestartPolicyAlways,
 12156  					DNSPolicy:          core.DNSClusterFirst,
 12157  					Volumes: []core.Volume{
 12158  						{
 12159  							Name: "projected-volume",
 12160  							VolumeSource: core.VolumeSource{
 12161  								Projected: &core.ProjectedVolumeSource{
 12162  									Sources: []core.VolumeProjection{
 12163  										{
 12164  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 12165  												Path:       "foo-path",
 12166  												SignerName: utilpointer.String(""),
 12167  												LabelSelector: &metav1.LabelSelector{
 12168  													MatchLabels: map[string]string{
 12169  														"foo": "bar",
 12170  													},
 12171  												},
 12172  											},
 12173  										},
 12174  									},
 12175  								},
 12176  							},
 12177  						},
 12178  					},
 12179  				},
 12180  			},
 12181  		},
 12182  		"ClusterTrustBundlePEM projected volume bySigner with invalid signer name": {
 12183  			expectedError: "must be a fully qualified domain and path of the form",
 12184  			spec: core.Pod{
 12185  				ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
 12186  				Spec: core.PodSpec{
 12187  					ServiceAccountName: "some-service-account",
 12188  					Containers:         []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12189  					RestartPolicy:      core.RestartPolicyAlways,
 12190  					DNSPolicy:          core.DNSClusterFirst,
 12191  					Volumes: []core.Volume{
 12192  						{
 12193  							Name: "projected-volume",
 12194  							VolumeSource: core.VolumeSource{
 12195  								Projected: &core.ProjectedVolumeSource{
 12196  									Sources: []core.VolumeProjection{
 12197  										{
 12198  											ClusterTrustBundle: &core.ClusterTrustBundleProjection{
 12199  												Path:       "foo-path",
 12200  												SignerName: utilpointer.String("example.com/foo/invalid"),
 12201  											},
 12202  										},
 12203  									},
 12204  								},
 12205  							},
 12206  						},
 12207  					},
 12208  				},
 12209  			},
 12210  		},
 12211  		"final PVC name for ephemeral volume must be valid": {
 12212  			expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
 12213  			spec: core.Pod{
 12214  				ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
 12215  				Spec: core.PodSpec{
 12216  					Volumes: []core.Volume{
 12217  						{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
 12218  						{Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 12219  					},
 12220  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12221  					RestartPolicy: core.RestartPolicyAlways,
 12222  					DNSPolicy:     core.DNSClusterFirst,
 12223  				},
 12224  			},
 12225  		},
 12226  		"PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
 12227  			expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
 12228  			spec: core.Pod{
 12229  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
 12230  				Spec: core.PodSpec{
 12231  					Volumes: []core.Volume{
 12232  						{Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
 12233  						{Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
 12234  					},
 12235  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12236  					RestartPolicy: core.RestartPolicyAlways,
 12237  					DNSPolicy:     core.DNSClusterFirst,
 12238  				},
 12239  			},
 12240  		},
 12241  		"invalid pod-deletion-cost": {
 12242  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"text\": must be a 32bit integer",
 12243  			spec: core.Pod{
 12244  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "text"}},
 12245  				Spec: core.PodSpec{
 12246  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12247  					RestartPolicy: core.RestartPolicyAlways,
 12248  					DNSPolicy:     core.DNSClusterFirst,
 12249  				},
 12250  			},
 12251  		},
 12252  		"invalid leading zeros pod-deletion-cost": {
 12253  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"008\": must be a 32bit integer",
 12254  			spec: core.Pod{
 12255  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "008"}},
 12256  				Spec: core.PodSpec{
 12257  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12258  					RestartPolicy: core.RestartPolicyAlways,
 12259  					DNSPolicy:     core.DNSClusterFirst,
 12260  				},
 12261  			},
 12262  		},
 12263  		"invalid leading plus sign pod-deletion-cost": {
 12264  			expectedError: "metadata.annotations[controller.kubernetes.io/pod-deletion-cost]: Invalid value: \"+10\": must be a 32bit integer",
 12265  			spec: core.Pod{
 12266  				ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.PodDeletionCost: "+10"}},
 12267  				Spec: core.PodSpec{
 12268  					Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 12269  					RestartPolicy: core.RestartPolicyAlways,
 12270  					DNSPolicy:     core.DNSClusterFirst,
 12271  				},
 12272  			},
 12273  		},
 12274  	}
 12275  	for k, v := range errorCases {
 12276  		t.Run(k, func(t *testing.T) {
 12277  			if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
 12278  				t.Errorf("expected failure")
 12279  			} else if v.expectedError == "" {
 12280  				t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
 12281  			} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
 12282  				t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
 12283  			}
 12284  		})
 12285  	}
 12286  }
 12287  
 12288  func TestValidatePodCreateWithSchedulingGates(t *testing.T) {
 12289  	applyEssentials := func(pod *core.Pod) {
 12290  		pod.Spec.Containers = []core.Container{
 12291  			{Name: "con", Image: "pause", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
 12292  		}
 12293  		pod.Spec.RestartPolicy = core.RestartPolicyAlways
 12294  		pod.Spec.DNSPolicy = core.DNSClusterFirst
 12295  	}
 12296  	fldPath := field.NewPath("spec")
 12297  
 12298  	tests := []struct {
 12299  		name            string
 12300  		pod             *core.Pod
 12301  		featureEnabled  bool
 12302  		wantFieldErrors field.ErrorList
 12303  	}{{
 12304  		name: "create a Pod with nodeName and schedulingGates, feature disabled",
 12305  		pod: &core.Pod{
 12306  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 12307  			Spec: core.PodSpec{
 12308  				NodeName: "node",
 12309  				SchedulingGates: []core.PodSchedulingGate{
 12310  					{Name: "foo"},
 12311  				},
 12312  			},
 12313  		},
 12314  		featureEnabled:  false,
 12315  		wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
 12316  	}, {
 12317  		name: "create a Pod with nodeName and schedulingGates, feature enabled",
 12318  		pod: &core.Pod{
 12319  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 12320  			Spec: core.PodSpec{
 12321  				NodeName: "node",
 12322  				SchedulingGates: []core.PodSchedulingGate{
 12323  					{Name: "foo"},
 12324  				},
 12325  			},
 12326  		},
 12327  		featureEnabled:  true,
 12328  		wantFieldErrors: []*field.Error{field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")},
 12329  	}, {
 12330  		name: "create a Pod with schedulingGates, feature disabled",
 12331  		pod: &core.Pod{
 12332  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 12333  			Spec: core.PodSpec{
 12334  				SchedulingGates: []core.PodSchedulingGate{
 12335  					{Name: "foo"},
 12336  				},
 12337  			},
 12338  		},
 12339  		featureEnabled:  false,
 12340  		wantFieldErrors: nil,
 12341  	}, {
 12342  		name: "create a Pod with schedulingGates, feature enabled",
 12343  		pod: &core.Pod{
 12344  			ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "ns"},
 12345  			Spec: core.PodSpec{
 12346  				SchedulingGates: []core.PodSchedulingGate{
 12347  					{Name: "foo"},
 12348  				},
 12349  			},
 12350  		},
 12351  		featureEnabled:  true,
 12352  		wantFieldErrors: nil,
 12353  	},
 12354  	}
 12355  
 12356  	for _, tt := range tests {
 12357  		t.Run(tt.name, func(t *testing.T) {
 12358  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)()
 12359  
 12360  			applyEssentials(tt.pod)
 12361  			errs := ValidatePodCreate(tt.pod, PodValidationOptions{})
 12362  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 12363  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 12364  			}
 12365  		})
 12366  	}
 12367  }
 12368  
 12369  func TestValidatePodUpdate(t *testing.T) {
 12370  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)()
 12371  	var (
 12372  		activeDeadlineSecondsZero     = int64(0)
 12373  		activeDeadlineSecondsNegative = int64(-30)
 12374  		activeDeadlineSecondsPositive = int64(30)
 12375  		activeDeadlineSecondsLarger   = int64(31)
 12376  		validfsGroupChangePolicy      = core.FSGroupChangeOnRootMismatch
 12377  
 12378  		now    = metav1.Now()
 12379  		grace  = int64(30)
 12380  		grace2 = int64(31)
 12381  	)
 12382  
 12383  	tests := []struct {
 12384  		new  core.Pod
 12385  		old  core.Pod
 12386  		opts PodValidationOptions
 12387  		err  string
 12388  		test string
 12389  	}{
 12390  		{new: core.Pod{}, old: core.Pod{}, err: "", test: "nothing"}, {
 12391  			new: core.Pod{
 12392  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12393  			},
 12394  			old: core.Pod{
 12395  				ObjectMeta: metav1.ObjectMeta{Name: "bar"},
 12396  			},
 12397  			err:  "metadata.name",
 12398  			test: "ids",
 12399  		}, {
 12400  			new: core.Pod{
 12401  				ObjectMeta: metav1.ObjectMeta{
 12402  					Name: "foo",
 12403  					Labels: map[string]string{
 12404  						"foo": "bar",
 12405  					},
 12406  				},
 12407  			},
 12408  			old: core.Pod{
 12409  				ObjectMeta: metav1.ObjectMeta{
 12410  					Name: "foo",
 12411  					Labels: map[string]string{
 12412  						"bar": "foo",
 12413  					},
 12414  				},
 12415  			},
 12416  			err:  "",
 12417  			test: "labels",
 12418  		}, {
 12419  			new: core.Pod{
 12420  				ObjectMeta: metav1.ObjectMeta{
 12421  					Name: "foo",
 12422  					Annotations: map[string]string{
 12423  						"foo": "bar",
 12424  					},
 12425  				},
 12426  			},
 12427  			old: core.Pod{
 12428  				ObjectMeta: metav1.ObjectMeta{
 12429  					Name: "foo",
 12430  					Annotations: map[string]string{
 12431  						"bar": "foo",
 12432  					},
 12433  				},
 12434  			},
 12435  			err:  "",
 12436  			test: "annotations",
 12437  		}, {
 12438  			new: core.Pod{
 12439  				ObjectMeta: metav1.ObjectMeta{
 12440  					Name: "foo",
 12441  				},
 12442  				Spec: core.PodSpec{
 12443  					Containers: []core.Container{{
 12444  						Image: "foo:V1",
 12445  					}},
 12446  				},
 12447  			},
 12448  			old: core.Pod{
 12449  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12450  				Spec: core.PodSpec{
 12451  					Containers: []core.Container{{
 12452  						Image: "foo:V2",
 12453  					}, {
 12454  						Image: "bar:V2",
 12455  					}},
 12456  				},
 12457  			},
 12458  			err:  "may not add or remove containers",
 12459  			test: "less containers",
 12460  		}, {
 12461  			new: core.Pod{
 12462  				ObjectMeta: metav1.ObjectMeta{
 12463  					Name: "foo",
 12464  				},
 12465  				Spec: core.PodSpec{
 12466  					Containers: []core.Container{{
 12467  						Image: "foo:V1",
 12468  					}, {
 12469  						Image: "bar:V2",
 12470  					}},
 12471  				},
 12472  			},
 12473  			old: core.Pod{
 12474  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12475  				Spec: core.PodSpec{
 12476  					Containers: []core.Container{{
 12477  						Image: "foo:V2",
 12478  					}},
 12479  				},
 12480  			},
 12481  			err:  "may not add or remove containers",
 12482  			test: "more containers",
 12483  		}, {
 12484  			new: core.Pod{
 12485  				ObjectMeta: metav1.ObjectMeta{
 12486  					Name: "foo",
 12487  				},
 12488  				Spec: core.PodSpec{
 12489  					InitContainers: []core.Container{{
 12490  						Image: "foo:V1",
 12491  					}},
 12492  				},
 12493  			},
 12494  			old: core.Pod{
 12495  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12496  				Spec: core.PodSpec{
 12497  					InitContainers: []core.Container{{
 12498  						Image: "foo:V2",
 12499  					}, {
 12500  						Image: "bar:V2",
 12501  					}},
 12502  				},
 12503  			},
 12504  			err:  "may not add or remove containers",
 12505  			test: "more init containers",
 12506  		}, {
 12507  			new: core.Pod{
 12508  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12509  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12510  			},
 12511  			old: core.Pod{
 12512  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 12513  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12514  			},
 12515  			err:  "metadata.deletionTimestamp",
 12516  			test: "deletion timestamp removed",
 12517  		}, {
 12518  			new: core.Pod{
 12519  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
 12520  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12521  			},
 12522  			old: core.Pod{
 12523  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12524  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12525  			},
 12526  			err:  "metadata.deletionTimestamp",
 12527  			test: "deletion timestamp added",
 12528  		}, {
 12529  			new: core.Pod{
 12530  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace},
 12531  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12532  			},
 12533  			old: core.Pod{
 12534  				ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2},
 12535  				Spec:       core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
 12536  			},
 12537  			err:  "metadata.deletionGracePeriodSeconds",
 12538  			test: "deletion grace period seconds changed",
 12539  		}, {
 12540  			new: core.Pod{
 12541  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12542  				Spec: core.PodSpec{
 12543  					Containers: []core.Container{{
 12544  						Name:                     "container",
 12545  						Image:                    "foo:V1",
 12546  						TerminationMessagePolicy: "File",
 12547  						ImagePullPolicy:          "Always",
 12548  					}},
 12549  				},
 12550  			},
 12551  			old: core.Pod{
 12552  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12553  				Spec: core.PodSpec{
 12554  					Containers: []core.Container{{
 12555  						Name:                     "container",
 12556  						Image:                    "foo:V2",
 12557  						TerminationMessagePolicy: "File",
 12558  						ImagePullPolicy:          "Always",
 12559  					}},
 12560  				},
 12561  			},
 12562  			err:  "",
 12563  			test: "image change",
 12564  		}, {
 12565  			new: core.Pod{
 12566  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12567  				Spec: core.PodSpec{
 12568  					InitContainers: []core.Container{{
 12569  						Name:                     "container",
 12570  						Image:                    "foo:V1",
 12571  						TerminationMessagePolicy: "File",
 12572  						ImagePullPolicy:          "Always",
 12573  					}},
 12574  				},
 12575  			},
 12576  			old: core.Pod{
 12577  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12578  				Spec: core.PodSpec{
 12579  					InitContainers: []core.Container{{
 12580  						Name:                     "container",
 12581  						Image:                    "foo:V2",
 12582  						TerminationMessagePolicy: "File",
 12583  						ImagePullPolicy:          "Always",
 12584  					}},
 12585  				},
 12586  			},
 12587  			err:  "",
 12588  			test: "init container image change",
 12589  		}, {
 12590  			new: core.Pod{
 12591  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12592  				Spec: core.PodSpec{
 12593  					Containers: []core.Container{{
 12594  						Name:                     "container",
 12595  						TerminationMessagePolicy: "File",
 12596  						ImagePullPolicy:          "Always",
 12597  					}},
 12598  				},
 12599  			},
 12600  			old: core.Pod{
 12601  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12602  				Spec: core.PodSpec{
 12603  					Containers: []core.Container{{
 12604  						Name:                     "container",
 12605  						Image:                    "foo:V2",
 12606  						TerminationMessagePolicy: "File",
 12607  						ImagePullPolicy:          "Always",
 12608  					}},
 12609  				},
 12610  			},
 12611  			err:  "spec.containers[0].image",
 12612  			test: "image change to empty",
 12613  		}, {
 12614  			new: core.Pod{
 12615  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12616  				Spec: core.PodSpec{
 12617  					InitContainers: []core.Container{{
 12618  						Name:                     "container",
 12619  						TerminationMessagePolicy: "File",
 12620  						ImagePullPolicy:          "Always",
 12621  					}},
 12622  				},
 12623  			},
 12624  			old: core.Pod{
 12625  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12626  				Spec: core.PodSpec{
 12627  					InitContainers: []core.Container{{
 12628  						Name:                     "container",
 12629  						Image:                    "foo:V2",
 12630  						TerminationMessagePolicy: "File",
 12631  						ImagePullPolicy:          "Always",
 12632  					}},
 12633  				},
 12634  			},
 12635  			err:  "spec.initContainers[0].image",
 12636  			test: "init container image change to empty",
 12637  		}, {
 12638  			new: core.Pod{
 12639  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12640  				Spec: core.PodSpec{
 12641  					EphemeralContainers: []core.EphemeralContainer{{
 12642  						EphemeralContainerCommon: core.EphemeralContainerCommon{
 12643  							Name:  "ephemeral",
 12644  							Image: "busybox",
 12645  						},
 12646  					}},
 12647  				},
 12648  			},
 12649  			old: core.Pod{
 12650  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 12651  				Spec:       core.PodSpec{},
 12652  			},
 12653  			err:  "Forbidden: pod updates may not change fields other than",
 12654  			test: "ephemeralContainer changes are not allowed via normal pod update",
 12655  		}, {
 12656  			new: core.Pod{
 12657  				Spec: core.PodSpec{},
 12658  			},
 12659  			old: core.Pod{
 12660  				Spec: core.PodSpec{},
 12661  			},
 12662  			err:  "",
 12663  			test: "activeDeadlineSeconds no change, nil",
 12664  		}, {
 12665  			new: core.Pod{
 12666  				Spec: core.PodSpec{
 12667  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12668  				},
 12669  			},
 12670  			old: core.Pod{
 12671  				Spec: core.PodSpec{
 12672  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12673  				},
 12674  			},
 12675  			err:  "",
 12676  			test: "activeDeadlineSeconds no change, set",
 12677  		}, {
 12678  			new: core.Pod{
 12679  				Spec: core.PodSpec{
 12680  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12681  				},
 12682  			},
 12683  			old:  core.Pod{},
 12684  			err:  "",
 12685  			test: "activeDeadlineSeconds change to positive from nil",
 12686  		}, {
 12687  			new: core.Pod{
 12688  				Spec: core.PodSpec{
 12689  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12690  				},
 12691  			},
 12692  			old: core.Pod{
 12693  				Spec: core.PodSpec{
 12694  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 12695  				},
 12696  			},
 12697  			err:  "",
 12698  			test: "activeDeadlineSeconds change to smaller positive",
 12699  		}, {
 12700  			new: core.Pod{
 12701  				Spec: core.PodSpec{
 12702  					ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
 12703  				},
 12704  			},
 12705  			old: core.Pod{
 12706  				Spec: core.PodSpec{
 12707  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12708  				},
 12709  			},
 12710  			err:  "spec.activeDeadlineSeconds",
 12711  			test: "activeDeadlineSeconds change to larger positive",
 12712  		},
 12713  
 12714  		{
 12715  			new: core.Pod{
 12716  				Spec: core.PodSpec{
 12717  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 12718  				},
 12719  			},
 12720  			old:  core.Pod{},
 12721  			err:  "spec.activeDeadlineSeconds",
 12722  			test: "activeDeadlineSeconds change to negative from nil",
 12723  		}, {
 12724  			new: core.Pod{
 12725  				Spec: core.PodSpec{
 12726  					ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
 12727  				},
 12728  			},
 12729  			old: core.Pod{
 12730  				Spec: core.PodSpec{
 12731  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12732  				},
 12733  			},
 12734  			err:  "spec.activeDeadlineSeconds",
 12735  			test: "activeDeadlineSeconds change to negative from positive",
 12736  		}, {
 12737  			new: core.Pod{
 12738  				Spec: core.PodSpec{
 12739  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 12740  				},
 12741  			},
 12742  			old: core.Pod{
 12743  				Spec: core.PodSpec{
 12744  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12745  				},
 12746  			},
 12747  			err:  "spec.activeDeadlineSeconds",
 12748  			test: "activeDeadlineSeconds change to zero from positive",
 12749  		}, {
 12750  			new: core.Pod{
 12751  				Spec: core.PodSpec{
 12752  					ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
 12753  				},
 12754  			},
 12755  			old:  core.Pod{},
 12756  			err:  "spec.activeDeadlineSeconds",
 12757  			test: "activeDeadlineSeconds change to zero from nil",
 12758  		}, {
 12759  			new: core.Pod{},
 12760  			old: core.Pod{
 12761  				Spec: core.PodSpec{
 12762  					ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
 12763  				},
 12764  			},
 12765  			err:  "spec.activeDeadlineSeconds",
 12766  			test: "activeDeadlineSeconds change to nil from positive",
 12767  		}, {
 12768  			new: core.Pod{
 12769  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12770  				Spec: core.PodSpec{
 12771  					Containers: []core.Container{{
 12772  						Name:                     "container",
 12773  						TerminationMessagePolicy: "File",
 12774  						ImagePullPolicy:          "Always",
 12775  						Image:                    "foo:V2",
 12776  						Resources: core.ResourceRequirements{
 12777  							Limits: getResources("200m", "0", "1Gi"),
 12778  						},
 12779  					}},
 12780  				},
 12781  			},
 12782  			old: core.Pod{
 12783  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12784  				Spec: core.PodSpec{
 12785  					Containers: []core.Container{{
 12786  						Name:                     "container",
 12787  						TerminationMessagePolicy: "File",
 12788  						ImagePullPolicy:          "Always",
 12789  						Image:                    "foo:V2",
 12790  						Resources: core.ResourceRequirements{
 12791  							Limits: getResources("100m", "0", "1Gi"),
 12792  						},
 12793  					}},
 12794  				},
 12795  			},
 12796  			err:  "",
 12797  			test: "cpu limit change",
 12798  		}, {
 12799  			new: core.Pod{
 12800  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12801  				Spec: core.PodSpec{
 12802  					Containers: []core.Container{{
 12803  						Name:                     "container",
 12804  						TerminationMessagePolicy: "File",
 12805  						ImagePullPolicy:          "Always",
 12806  						Image:                    "foo:V1",
 12807  						Resources: core.ResourceRequirements{
 12808  							Limits: getResourceLimits("100m", "100Mi"),
 12809  						},
 12810  					}},
 12811  				},
 12812  			},
 12813  			old: core.Pod{
 12814  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12815  				Spec: core.PodSpec{
 12816  					Containers: []core.Container{{
 12817  						Name:                     "container",
 12818  						TerminationMessagePolicy: "File",
 12819  						ImagePullPolicy:          "Always",
 12820  						Image:                    "foo:V2",
 12821  						Resources: core.ResourceRequirements{
 12822  							Limits: getResourceLimits("100m", "200Mi"),
 12823  						},
 12824  					}},
 12825  				},
 12826  			},
 12827  			err:  "",
 12828  			test: "memory limit change",
 12829  		}, {
 12830  			new: core.Pod{
 12831  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12832  				Spec: core.PodSpec{
 12833  					Containers: []core.Container{{
 12834  						Name:                     "container",
 12835  						TerminationMessagePolicy: "File",
 12836  						ImagePullPolicy:          "Always",
 12837  						Image:                    "foo:V1",
 12838  						Resources: core.ResourceRequirements{
 12839  							Limits: getResources("100m", "100Mi", "1Gi"),
 12840  						},
 12841  					}},
 12842  				},
 12843  			},
 12844  			old: core.Pod{
 12845  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12846  				Spec: core.PodSpec{
 12847  					Containers: []core.Container{{
 12848  						Name:                     "container",
 12849  						TerminationMessagePolicy: "File",
 12850  						ImagePullPolicy:          "Always",
 12851  						Image:                    "foo:V2",
 12852  						Resources: core.ResourceRequirements{
 12853  							Limits: getResources("100m", "100Mi", "2Gi"),
 12854  						},
 12855  					}},
 12856  				},
 12857  			},
 12858  			err:  "Forbidden: pod updates may not change fields other than",
 12859  			test: "storage limit change",
 12860  		}, {
 12861  			new: core.Pod{
 12862  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12863  				Spec: core.PodSpec{
 12864  					Containers: []core.Container{{
 12865  						Name:                     "container",
 12866  						TerminationMessagePolicy: "File",
 12867  						ImagePullPolicy:          "Always",
 12868  						Image:                    "foo:V1",
 12869  						Resources: core.ResourceRequirements{
 12870  							Requests: getResourceLimits("100m", "0"),
 12871  						},
 12872  					}},
 12873  				},
 12874  			},
 12875  			old: core.Pod{
 12876  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12877  				Spec: core.PodSpec{
 12878  					Containers: []core.Container{{
 12879  						Name:                     "container",
 12880  						TerminationMessagePolicy: "File",
 12881  						ImagePullPolicy:          "Always",
 12882  						Image:                    "foo:V2",
 12883  						Resources: core.ResourceRequirements{
 12884  							Requests: getResourceLimits("200m", "0"),
 12885  						},
 12886  					}},
 12887  				},
 12888  			},
 12889  			err:  "",
 12890  			test: "cpu request change",
 12891  		}, {
 12892  			new: core.Pod{
 12893  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12894  				Spec: core.PodSpec{
 12895  					Containers: []core.Container{{
 12896  						Name:                     "container",
 12897  						TerminationMessagePolicy: "File",
 12898  						ImagePullPolicy:          "Always",
 12899  						Image:                    "foo:V1",
 12900  						Resources: core.ResourceRequirements{
 12901  							Requests: getResourceLimits("0", "200Mi"),
 12902  						},
 12903  					}},
 12904  				},
 12905  			},
 12906  			old: core.Pod{
 12907  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12908  				Spec: core.PodSpec{
 12909  					Containers: []core.Container{{
 12910  						Name:                     "container",
 12911  						TerminationMessagePolicy: "File",
 12912  						ImagePullPolicy:          "Always",
 12913  						Image:                    "foo:V2",
 12914  						Resources: core.ResourceRequirements{
 12915  							Requests: getResourceLimits("0", "100Mi"),
 12916  						},
 12917  					}},
 12918  				},
 12919  			},
 12920  			err:  "",
 12921  			test: "memory request change",
 12922  		}, {
 12923  			new: core.Pod{
 12924  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12925  				Spec: core.PodSpec{
 12926  					Containers: []core.Container{{
 12927  						Name:                     "container",
 12928  						TerminationMessagePolicy: "File",
 12929  						ImagePullPolicy:          "Always",
 12930  						Image:                    "foo:V1",
 12931  						Resources: core.ResourceRequirements{
 12932  							Requests: getResources("100m", "0", "2Gi"),
 12933  						},
 12934  					}},
 12935  				},
 12936  			},
 12937  			old: core.Pod{
 12938  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12939  				Spec: core.PodSpec{
 12940  					Containers: []core.Container{{
 12941  						Name:                     "container",
 12942  						TerminationMessagePolicy: "File",
 12943  						ImagePullPolicy:          "Always",
 12944  						Image:                    "foo:V2",
 12945  						Resources: core.ResourceRequirements{
 12946  							Requests: getResources("100m", "0", "1Gi"),
 12947  						},
 12948  					}},
 12949  				},
 12950  			},
 12951  			err:  "Forbidden: pod updates may not change fields other than",
 12952  			test: "storage request change",
 12953  		}, {
 12954  			new: core.Pod{
 12955  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12956  				Spec: core.PodSpec{
 12957  					Containers: []core.Container{{
 12958  						Name:                     "container",
 12959  						TerminationMessagePolicy: "File",
 12960  						ImagePullPolicy:          "Always",
 12961  						Image:                    "foo:V1",
 12962  						Resources: core.ResourceRequirements{
 12963  							Limits:   getResources("200m", "400Mi", "1Gi"),
 12964  							Requests: getResources("200m", "400Mi", "1Gi"),
 12965  						},
 12966  					}},
 12967  				},
 12968  			},
 12969  			old: core.Pod{
 12970  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12971  				Spec: core.PodSpec{
 12972  					Containers: []core.Container{{
 12973  						Name:                     "container",
 12974  						TerminationMessagePolicy: "File",
 12975  						ImagePullPolicy:          "Always",
 12976  						Image:                    "foo:V1",
 12977  						Resources: core.ResourceRequirements{
 12978  							Limits:   getResources("100m", "100Mi", "1Gi"),
 12979  							Requests: getResources("100m", "100Mi", "1Gi"),
 12980  						},
 12981  					}},
 12982  				},
 12983  			},
 12984  			err:  "",
 12985  			test: "Pod QoS unchanged, guaranteed -> guaranteed",
 12986  		}, {
 12987  			new: core.Pod{
 12988  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 12989  				Spec: core.PodSpec{
 12990  					Containers: []core.Container{{
 12991  						Name:                     "container",
 12992  						TerminationMessagePolicy: "File",
 12993  						ImagePullPolicy:          "Always",
 12994  						Image:                    "foo:V1",
 12995  						Resources: core.ResourceRequirements{
 12996  							Limits:   getResources("200m", "200Mi", "2Gi"),
 12997  							Requests: getResources("100m", "100Mi", "1Gi"),
 12998  						},
 12999  					}},
 13000  				},
 13001  			},
 13002  			old: core.Pod{
 13003  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13004  				Spec: core.PodSpec{
 13005  					Containers: []core.Container{{
 13006  						Name:                     "container",
 13007  						TerminationMessagePolicy: "File",
 13008  						ImagePullPolicy:          "Always",
 13009  						Image:                    "foo:V1",
 13010  						Resources: core.ResourceRequirements{
 13011  							Limits:   getResources("400m", "400Mi", "2Gi"),
 13012  							Requests: getResources("200m", "200Mi", "1Gi"),
 13013  						},
 13014  					}},
 13015  				},
 13016  			},
 13017  			err:  "",
 13018  			test: "Pod QoS unchanged, burstable -> burstable",
 13019  		}, {
 13020  			new: core.Pod{
 13021  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13022  				Spec: core.PodSpec{
 13023  					Containers: []core.Container{{
 13024  						Name:                     "container",
 13025  						TerminationMessagePolicy: "File",
 13026  						ImagePullPolicy:          "Always",
 13027  						Image:                    "foo:V2",
 13028  						Resources: core.ResourceRequirements{
 13029  							Limits:   getResourceLimits("200m", "200Mi"),
 13030  							Requests: getResourceLimits("100m", "100Mi"),
 13031  						},
 13032  					}},
 13033  				},
 13034  			},
 13035  			old: core.Pod{
 13036  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13037  				Spec: core.PodSpec{
 13038  					Containers: []core.Container{{
 13039  						Name:                     "container",
 13040  						TerminationMessagePolicy: "File",
 13041  						ImagePullPolicy:          "Always",
 13042  						Image:                    "foo:V2",
 13043  						Resources: core.ResourceRequirements{
 13044  							Requests: getResourceLimits("100m", "100Mi"),
 13045  						},
 13046  					}},
 13047  				},
 13048  			},
 13049  			err:  "",
 13050  			test: "Pod QoS unchanged, burstable -> burstable, add limits",
 13051  		}, {
 13052  			new: core.Pod{
 13053  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13054  				Spec: core.PodSpec{
 13055  					Containers: []core.Container{{
 13056  						Name:                     "container",
 13057  						TerminationMessagePolicy: "File",
 13058  						ImagePullPolicy:          "Always",
 13059  						Image:                    "foo:V2",
 13060  						Resources: core.ResourceRequirements{
 13061  							Requests: getResourceLimits("100m", "100Mi"),
 13062  						},
 13063  					}},
 13064  				},
 13065  			},
 13066  			old: core.Pod{
 13067  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13068  				Spec: core.PodSpec{
 13069  					Containers: []core.Container{{
 13070  						Name:                     "container",
 13071  						TerminationMessagePolicy: "File",
 13072  						ImagePullPolicy:          "Always",
 13073  						Image:                    "foo:V2",
 13074  						Resources: core.ResourceRequirements{
 13075  							Limits:   getResourceLimits("200m", "200Mi"),
 13076  							Requests: getResourceLimits("100m", "100Mi"),
 13077  						},
 13078  					}},
 13079  				},
 13080  			},
 13081  			err:  "",
 13082  			test: "Pod QoS unchanged, burstable -> burstable, remove limits",
 13083  		}, {
 13084  			new: core.Pod{
 13085  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13086  				Spec: core.PodSpec{
 13087  					Containers: []core.Container{{
 13088  						Name:                     "container",
 13089  						TerminationMessagePolicy: "File",
 13090  						ImagePullPolicy:          "Always",
 13091  						Image:                    "foo:V2",
 13092  						Resources: core.ResourceRequirements{
 13093  							Limits:   getResources("400m", "", "1Gi"),
 13094  							Requests: getResources("300m", "", "1Gi"),
 13095  						},
 13096  					}},
 13097  				},
 13098  			},
 13099  			old: core.Pod{
 13100  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13101  				Spec: core.PodSpec{
 13102  					Containers: []core.Container{{
 13103  						Name:                     "container",
 13104  						TerminationMessagePolicy: "File",
 13105  						ImagePullPolicy:          "Always",
 13106  						Image:                    "foo:V2",
 13107  						Resources: core.ResourceRequirements{
 13108  							Limits: getResources("200m", "500Mi", "1Gi"),
 13109  						},
 13110  					}},
 13111  				},
 13112  			},
 13113  			err:  "",
 13114  			test: "Pod QoS unchanged, burstable -> burstable, add requests",
 13115  		}, {
 13116  			new: core.Pod{
 13117  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13118  				Spec: core.PodSpec{
 13119  					Containers: []core.Container{{
 13120  						Name:                     "container",
 13121  						TerminationMessagePolicy: "File",
 13122  						ImagePullPolicy:          "Always",
 13123  						Image:                    "foo:V2",
 13124  						Resources: core.ResourceRequirements{
 13125  							Limits: getResources("400m", "500Mi", "2Gi"),
 13126  						},
 13127  					}},
 13128  				},
 13129  			},
 13130  			old: core.Pod{
 13131  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13132  				Spec: core.PodSpec{
 13133  					Containers: []core.Container{{
 13134  						Name:                     "container",
 13135  						TerminationMessagePolicy: "File",
 13136  						ImagePullPolicy:          "Always",
 13137  						Image:                    "foo:V2",
 13138  						Resources: core.ResourceRequirements{
 13139  							Limits:   getResources("200m", "300Mi", "2Gi"),
 13140  							Requests: getResourceLimits("100m", "200Mi"),
 13141  						},
 13142  					}},
 13143  				},
 13144  			},
 13145  			err:  "",
 13146  			test: "Pod QoS unchanged, burstable -> burstable, remove requests",
 13147  		}, {
 13148  			new: core.Pod{
 13149  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13150  				Spec: core.PodSpec{
 13151  					Containers: []core.Container{{
 13152  						Name:                     "container",
 13153  						TerminationMessagePolicy: "File",
 13154  						ImagePullPolicy:          "Always",
 13155  						Image:                    "foo:V2",
 13156  						Resources: core.ResourceRequirements{
 13157  							Limits:   getResourceLimits("200m", "200Mi"),
 13158  							Requests: getResourceLimits("100m", "100Mi"),
 13159  						},
 13160  					}},
 13161  				},
 13162  			},
 13163  			old: core.Pod{
 13164  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13165  				Spec: core.PodSpec{
 13166  					Containers: []core.Container{{
 13167  						Name:                     "container",
 13168  						TerminationMessagePolicy: "File",
 13169  						ImagePullPolicy:          "Always",
 13170  						Image:                    "foo:V2",
 13171  						Resources: core.ResourceRequirements{
 13172  							Limits:   getResourceLimits("100m", "100Mi"),
 13173  							Requests: getResourceLimits("100m", "100Mi"),
 13174  						},
 13175  					}},
 13176  				},
 13177  			},
 13178  			err:  "Pod QoS is immutable",
 13179  			test: "Pod QoS change, guaranteed -> burstable",
 13180  		}, {
 13181  			new: core.Pod{
 13182  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13183  				Spec: core.PodSpec{
 13184  					Containers: []core.Container{{
 13185  						Name:                     "container",
 13186  						TerminationMessagePolicy: "File",
 13187  						ImagePullPolicy:          "Always",
 13188  						Image:                    "foo:V2",
 13189  						Resources: core.ResourceRequirements{
 13190  							Limits:   getResourceLimits("100m", "100Mi"),
 13191  							Requests: getResourceLimits("100m", "100Mi"),
 13192  						},
 13193  					}},
 13194  				},
 13195  			},
 13196  			old: core.Pod{
 13197  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13198  				Spec: core.PodSpec{
 13199  					Containers: []core.Container{{
 13200  						Name:                     "container",
 13201  						TerminationMessagePolicy: "File",
 13202  						ImagePullPolicy:          "Always",
 13203  						Image:                    "foo:V2",
 13204  						Resources: core.ResourceRequirements{
 13205  							Requests: getResourceLimits("100m", "100Mi"),
 13206  						},
 13207  					}},
 13208  				},
 13209  			},
 13210  			err:  "Pod QoS is immutable",
 13211  			test: "Pod QoS change, burstable -> guaranteed",
 13212  		}, {
 13213  			new: core.Pod{
 13214  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13215  				Spec: core.PodSpec{
 13216  					Containers: []core.Container{{
 13217  						Name:                     "container",
 13218  						TerminationMessagePolicy: "File",
 13219  						ImagePullPolicy:          "Always",
 13220  						Image:                    "foo:V2",
 13221  						Resources: core.ResourceRequirements{
 13222  							Limits:   getResourceLimits("200m", "200Mi"),
 13223  							Requests: getResourceLimits("100m", "100Mi"),
 13224  						},
 13225  					}},
 13226  				},
 13227  			},
 13228  			old: core.Pod{
 13229  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13230  				Spec: core.PodSpec{
 13231  					Containers: []core.Container{{
 13232  						Name:                     "container",
 13233  						TerminationMessagePolicy: "File",
 13234  						ImagePullPolicy:          "Always",
 13235  						Image:                    "foo:V2",
 13236  					}},
 13237  				},
 13238  			},
 13239  			err:  "Pod QoS is immutable",
 13240  			test: "Pod QoS change, besteffort -> burstable",
 13241  		}, {
 13242  			new: core.Pod{
 13243  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13244  				Spec: core.PodSpec{
 13245  					Containers: []core.Container{{
 13246  						Name:                     "container",
 13247  						TerminationMessagePolicy: "File",
 13248  						ImagePullPolicy:          "Always",
 13249  						Image:                    "foo:V2",
 13250  					}},
 13251  				},
 13252  			},
 13253  			old: core.Pod{
 13254  				ObjectMeta: metav1.ObjectMeta{Name: "pod"},
 13255  				Spec: core.PodSpec{
 13256  					Containers: []core.Container{{
 13257  						Name:                     "container",
 13258  						TerminationMessagePolicy: "File",
 13259  						ImagePullPolicy:          "Always",
 13260  						Image:                    "foo:V2",
 13261  						Resources: core.ResourceRequirements{
 13262  							Limits:   getResourceLimits("200m", "200Mi"),
 13263  							Requests: getResourceLimits("100m", "100Mi"),
 13264  						},
 13265  					}},
 13266  				},
 13267  			},
 13268  			err:  "Pod QoS is immutable",
 13269  			test: "Pod QoS change, burstable -> besteffort",
 13270  		}, {
 13271  			new: core.Pod{
 13272  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13273  				Spec: core.PodSpec{
 13274  					Containers: []core.Container{{
 13275  						Image: "foo:V1",
 13276  					}},
 13277  					SecurityContext: &core.PodSecurityContext{
 13278  						FSGroupChangePolicy: &validfsGroupChangePolicy,
 13279  					},
 13280  				},
 13281  			},
 13282  			old: core.Pod{
 13283  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13284  				Spec: core.PodSpec{
 13285  					Containers: []core.Container{{
 13286  						Image: "foo:V2",
 13287  					}},
 13288  					SecurityContext: &core.PodSecurityContext{
 13289  						FSGroupChangePolicy: nil,
 13290  					},
 13291  				},
 13292  			},
 13293  			err:  "spec: Forbidden: pod updates may not change fields",
 13294  			test: "fsGroupChangePolicy change",
 13295  		}, {
 13296  			new: core.Pod{
 13297  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13298  				Spec: core.PodSpec{
 13299  					Containers: []core.Container{{
 13300  						Image: "foo:V1",
 13301  						Ports: []core.ContainerPort{
 13302  							{HostPort: 8080, ContainerPort: 80},
 13303  						},
 13304  					}},
 13305  				},
 13306  			},
 13307  			old: core.Pod{
 13308  				ObjectMeta: metav1.ObjectMeta{Name: "foo"},
 13309  				Spec: core.PodSpec{
 13310  					Containers: []core.Container{{
 13311  						Image: "foo:V2",
 13312  						Ports: []core.ContainerPort{
 13313  							{HostPort: 8000, ContainerPort: 80},
 13314  						},
 13315  					}},
 13316  				},
 13317  			},
 13318  			err:  "spec: Forbidden: pod updates may not change fields",
 13319  			test: "port change",
 13320  		}, {
 13321  			new: core.Pod{
 13322  				ObjectMeta: metav1.ObjectMeta{
 13323  					Name: "foo",
 13324  					Labels: map[string]string{
 13325  						"foo": "bar",
 13326  					},
 13327  				},
 13328  			},
 13329  			old: core.Pod{
 13330  				ObjectMeta: metav1.ObjectMeta{
 13331  					Name: "foo",
 13332  					Labels: map[string]string{
 13333  						"Bar": "foo",
 13334  					},
 13335  				},
 13336  			},
 13337  			err:  "",
 13338  			test: "bad label change",
 13339  		}, {
 13340  			new: core.Pod{
 13341  				ObjectMeta: metav1.ObjectMeta{
 13342  					Name: "foo",
 13343  				},
 13344  				Spec: core.PodSpec{
 13345  					NodeName:    "node1",
 13346  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 13347  				},
 13348  			},
 13349  			old: core.Pod{
 13350  				ObjectMeta: metav1.ObjectMeta{
 13351  					Name: "foo",
 13352  				},
 13353  				Spec: core.PodSpec{
 13354  					NodeName:    "node1",
 13355  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 13356  				},
 13357  			},
 13358  			err:  "spec.tolerations: Forbidden",
 13359  			test: "existing toleration value modified in pod spec updates",
 13360  		}, {
 13361  			new: core.Pod{
 13362  				ObjectMeta: metav1.ObjectMeta{
 13363  					Name: "foo",
 13364  				},
 13365  				Spec: core.PodSpec{
 13366  					NodeName:    "node1",
 13367  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}},
 13368  				},
 13369  			},
 13370  			old: core.Pod{
 13371  				ObjectMeta: metav1.ObjectMeta{
 13372  					Name: "foo",
 13373  				},
 13374  				Spec: core.PodSpec{
 13375  					NodeName:    "node1",
 13376  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 13377  				},
 13378  			},
 13379  			err:  "spec.tolerations: Forbidden",
 13380  			test: "existing toleration value modified in pod spec updates with modified tolerationSeconds",
 13381  		}, {
 13382  			new: core.Pod{
 13383  				ObjectMeta: metav1.ObjectMeta{
 13384  					Name: "foo",
 13385  				},
 13386  				Spec: core.PodSpec{
 13387  					NodeName:    "node1",
 13388  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 13389  				},
 13390  			},
 13391  			old: core.Pod{
 13392  				ObjectMeta: metav1.ObjectMeta{
 13393  					Name: "foo",
 13394  				},
 13395  				Spec: core.PodSpec{
 13396  					NodeName:    "node1",
 13397  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}},
 13398  				}},
 13399  			err:  "",
 13400  			test: "modified tolerationSeconds in existing toleration value in pod spec updates",
 13401  		}, {
 13402  			new: core.Pod{
 13403  				ObjectMeta: metav1.ObjectMeta{
 13404  					Name: "foo",
 13405  				},
 13406  				Spec: core.PodSpec{
 13407  					Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
 13408  				},
 13409  			},
 13410  			old: core.Pod{
 13411  				ObjectMeta: metav1.ObjectMeta{
 13412  					Name: "foo",
 13413  				},
 13414  				Spec: core.PodSpec{
 13415  					NodeName:    "",
 13416  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 13417  				},
 13418  			},
 13419  			err:  "spec.tolerations: Forbidden",
 13420  			test: "toleration modified in updates to an unscheduled pod",
 13421  		}, {
 13422  			new: core.Pod{
 13423  				ObjectMeta: metav1.ObjectMeta{
 13424  					Name: "foo",
 13425  				},
 13426  				Spec: core.PodSpec{
 13427  					NodeName:    "node1",
 13428  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 13429  				},
 13430  			},
 13431  			old: core.Pod{
 13432  				ObjectMeta: metav1.ObjectMeta{
 13433  					Name: "foo",
 13434  				},
 13435  				Spec: core.PodSpec{
 13436  					NodeName:    "node1",
 13437  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
 13438  				},
 13439  			},
 13440  			err:  "",
 13441  			test: "tolerations unmodified in updates to a scheduled pod",
 13442  		}, {
 13443  			new: core.Pod{
 13444  				ObjectMeta: metav1.ObjectMeta{
 13445  					Name: "foo",
 13446  				},
 13447  				Spec: core.PodSpec{
 13448  					NodeName: "node1",
 13449  					Tolerations: []core.Toleration{
 13450  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 13451  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]},
 13452  					},
 13453  				}},
 13454  			old: core.Pod{
 13455  				ObjectMeta: metav1.ObjectMeta{
 13456  					Name: "foo",
 13457  				},
 13458  				Spec: core.PodSpec{
 13459  					NodeName:    "node1",
 13460  					Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 13461  				},
 13462  			},
 13463  			err:  "",
 13464  			test: "added valid new toleration to existing tolerations in pod spec updates",
 13465  		}, {
 13466  			new: core.Pod{
 13467  				ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{
 13468  					NodeName: "node1",
 13469  					Tolerations: []core.Toleration{
 13470  						{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
 13471  						{Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]},
 13472  					},
 13473  				}},
 13474  			old: core.Pod{
 13475  				ObjectMeta: metav1.ObjectMeta{
 13476  					Name: "foo",
 13477  				},
 13478  				Spec: core.PodSpec{
 13479  					NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
 13480  				}},
 13481  			err:  "spec.tolerations[1].effect",
 13482  			test: "added invalid new toleration to existing tolerations in pod spec updates",
 13483  		}, {
 13484  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 13485  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
 13486  			err:  "spec: Forbidden: pod updates may not change fields",
 13487  			test: "removed nodeName from pod spec",
 13488  		}, {
 13489  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 13490  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 13491  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 13492  			test: "added mirror pod annotation",
 13493  		}, {
 13494  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
 13495  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
 13496  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 13497  			test: "removed mirror pod annotation",
 13498  		}, {
 13499  			new:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}},
 13500  			old:  core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}},
 13501  			err:  "metadata.annotations[kubernetes.io/config.mirror]",
 13502  			test: "changed mirror pod annotation",
 13503  		}, {
 13504  			new: core.Pod{
 13505  				ObjectMeta: metav1.ObjectMeta{
 13506  					Name: "foo",
 13507  				},
 13508  				Spec: core.PodSpec{
 13509  					NodeName:          "node1",
 13510  					PriorityClassName: "bar-priority",
 13511  				},
 13512  			},
 13513  			old: core.Pod{
 13514  				ObjectMeta: metav1.ObjectMeta{
 13515  					Name: "foo",
 13516  				},
 13517  				Spec: core.PodSpec{
 13518  					NodeName:          "node1",
 13519  					PriorityClassName: "foo-priority",
 13520  				},
 13521  			},
 13522  			err:  "spec: Forbidden: pod updates",
 13523  			test: "changed priority class name",
 13524  		}, {
 13525  			new: core.Pod{
 13526  				ObjectMeta: metav1.ObjectMeta{
 13527  					Name: "foo",
 13528  				},
 13529  				Spec: core.PodSpec{
 13530  					NodeName:          "node1",
 13531  					PriorityClassName: "",
 13532  				},
 13533  			},
 13534  			old: core.Pod{
 13535  				ObjectMeta: metav1.ObjectMeta{
 13536  					Name: "foo",
 13537  				},
 13538  				Spec: core.PodSpec{
 13539  					NodeName:          "node1",
 13540  					PriorityClassName: "foo-priority",
 13541  				},
 13542  			},
 13543  			err:  "spec: Forbidden: pod updates",
 13544  			test: "removed priority class name",
 13545  		}, {
 13546  			new: core.Pod{
 13547  				ObjectMeta: metav1.ObjectMeta{
 13548  					Name: "foo",
 13549  				},
 13550  				Spec: core.PodSpec{
 13551  					TerminationGracePeriodSeconds: utilpointer.Int64(1),
 13552  				},
 13553  			},
 13554  			old: core.Pod{
 13555  				ObjectMeta: metav1.ObjectMeta{
 13556  					Name: "foo",
 13557  				},
 13558  				Spec: core.PodSpec{
 13559  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 13560  				},
 13561  			},
 13562  			err:  "",
 13563  			test: "update termination grace period seconds",
 13564  		}, {
 13565  			new: core.Pod{
 13566  				ObjectMeta: metav1.ObjectMeta{
 13567  					Name: "foo",
 13568  				},
 13569  				Spec: core.PodSpec{
 13570  					TerminationGracePeriodSeconds: utilpointer.Int64(0),
 13571  				},
 13572  			},
 13573  			old: core.Pod{
 13574  				ObjectMeta: metav1.ObjectMeta{
 13575  					Name: "foo",
 13576  				},
 13577  				Spec: core.PodSpec{
 13578  					TerminationGracePeriodSeconds: utilpointer.Int64(-1),
 13579  				},
 13580  			},
 13581  			err:  "spec: Forbidden: pod updates",
 13582  			test: "update termination grace period seconds not 1",
 13583  		}, {
 13584  			new: core.Pod{
 13585  				ObjectMeta: metav1.ObjectMeta{
 13586  					Name: "foo",
 13587  				},
 13588  				Spec: core.PodSpec{
 13589  					OS:              &core.PodOS{Name: core.Windows},
 13590  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 13591  				},
 13592  			},
 13593  			old: core.Pod{
 13594  				ObjectMeta: metav1.ObjectMeta{
 13595  					Name: "foo",
 13596  				},
 13597  				Spec: core.PodSpec{
 13598  					OS:              &core.PodOS{Name: core.Linux},
 13599  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 13600  				},
 13601  			},
 13602  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13603  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set",
 13604  		}, {
 13605  			new: core.Pod{
 13606  				ObjectMeta: metav1.ObjectMeta{
 13607  					Name: "foo",
 13608  				},
 13609  				Spec: core.PodSpec{
 13610  					OS:              &core.PodOS{Name: core.Windows},
 13611  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 13612  				},
 13613  			},
 13614  			old: core.Pod{
 13615  				ObjectMeta: metav1.ObjectMeta{
 13616  					Name: "foo",
 13617  				},
 13618  				Spec: core.PodSpec{
 13619  					OS:              &core.PodOS{Name: core.Linux},
 13620  					SecurityContext: &core.PodSecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}},
 13621  				},
 13622  			},
 13623  			err:  "spec.securityContext.seLinuxOptions: Forbidden",
 13624  			test: "pod OS changing from Linux to Windows, IdentifyPodOS featuregate set, we'd get SELinux errors as well",
 13625  		}, {
 13626  			new: core.Pod{
 13627  				ObjectMeta: metav1.ObjectMeta{
 13628  					Name: "foo",
 13629  				},
 13630  				Spec: core.PodSpec{
 13631  					OS: &core.PodOS{Name: "dummy"},
 13632  				},
 13633  			},
 13634  			old: core.Pod{
 13635  				ObjectMeta: metav1.ObjectMeta{
 13636  					Name: "foo",
 13637  				},
 13638  				Spec: core.PodSpec{},
 13639  			},
 13640  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13641  			test: "invalid PodOS update, IdentifyPodOS featuregate set",
 13642  		}, {
 13643  			new: core.Pod{
 13644  				ObjectMeta: metav1.ObjectMeta{
 13645  					Name: "foo",
 13646  				},
 13647  				Spec: core.PodSpec{
 13648  					OS: &core.PodOS{Name: core.Linux},
 13649  				},
 13650  			},
 13651  			old: core.Pod{
 13652  				ObjectMeta: metav1.ObjectMeta{
 13653  					Name: "foo",
 13654  				},
 13655  				Spec: core.PodSpec{
 13656  					OS: &core.PodOS{Name: core.Windows},
 13657  				},
 13658  			},
 13659  			err:  "Forbidden: pod updates may not change fields other than ",
 13660  			test: "update pod spec OS to a valid value, featuregate disabled",
 13661  		}, {
 13662  			new: core.Pod{
 13663  				Spec: core.PodSpec{
 13664  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 13665  				},
 13666  			},
 13667  			old:  core.Pod{},
 13668  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'foo'",
 13669  			test: "update pod spec schedulingGates: add new scheduling gate",
 13670  		}, {
 13671  			new: core.Pod{
 13672  				Spec: core.PodSpec{
 13673  					SchedulingGates: []core.PodSchedulingGate{{Name: "bar"}},
 13674  				},
 13675  			},
 13676  			old: core.Pod{
 13677  				Spec: core.PodSpec{
 13678  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 13679  				},
 13680  			},
 13681  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'bar'",
 13682  			test: "update pod spec schedulingGates: mutating an existing scheduling gate",
 13683  		}, {
 13684  			new: core.Pod{
 13685  				Spec: core.PodSpec{
 13686  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13687  				},
 13688  			},
 13689  			old: core.Pod{
 13690  				Spec: core.PodSpec{
 13691  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}, {Name: "bar"}},
 13692  				},
 13693  			},
 13694  			err:  "Forbidden: only deletion is allowed, but found new scheduling gate 'baz'",
 13695  			test: "update pod spec schedulingGates: mutating an existing scheduling gate along with deletion",
 13696  		}, {
 13697  			new: core.Pod{},
 13698  			old: core.Pod{
 13699  				Spec: core.PodSpec{
 13700  					SchedulingGates: []core.PodSchedulingGate{{Name: "foo"}},
 13701  				},
 13702  			},
 13703  			err:  "",
 13704  			test: "update pod spec schedulingGates: legal deletion",
 13705  		}, {
 13706  			old: core.Pod{
 13707  				Spec: core.PodSpec{
 13708  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13709  				},
 13710  			},
 13711  			new: core.Pod{
 13712  				Spec: core.PodSpec{
 13713  					NodeSelector: map[string]string{
 13714  						"foo": "bar",
 13715  					},
 13716  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13717  				},
 13718  			},
 13719  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13720  			test: "node selector is immutable when AllowMutableNodeSelector is false",
 13721  		}, {
 13722  			old: core.Pod{
 13723  				Spec: core.PodSpec{
 13724  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13725  				},
 13726  			},
 13727  			new: core.Pod{
 13728  				Spec: core.PodSpec{
 13729  					NodeSelector: map[string]string{
 13730  						"foo": "bar",
 13731  					},
 13732  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13733  				},
 13734  			},
 13735  			opts: PodValidationOptions{
 13736  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13737  			},
 13738  			test: "adding node selector is allowed for gated pods",
 13739  		}, {
 13740  			old: core.Pod{
 13741  				Spec: core.PodSpec{
 13742  					NodeSelector: map[string]string{
 13743  						"foo": "bar",
 13744  					},
 13745  				},
 13746  			},
 13747  			new: core.Pod{
 13748  				Spec: core.PodSpec{
 13749  					NodeSelector: map[string]string{
 13750  						"foo":  "bar",
 13751  						"foo2": "bar2",
 13752  					},
 13753  				},
 13754  			},
 13755  			opts: PodValidationOptions{
 13756  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13757  			},
 13758  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13759  			test: "adding node selector is not allowed for non-gated pods",
 13760  		}, {
 13761  			old: core.Pod{
 13762  				Spec: core.PodSpec{
 13763  					NodeSelector: map[string]string{
 13764  						"foo": "bar",
 13765  					},
 13766  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13767  				},
 13768  			},
 13769  			new: core.Pod{
 13770  				Spec: core.PodSpec{
 13771  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13772  				},
 13773  			},
 13774  			opts: PodValidationOptions{
 13775  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13776  			},
 13777  			err:  "spec.nodeSelector: Invalid value:",
 13778  			test: "removing node selector is not allowed for gated pods",
 13779  		}, {
 13780  			old: core.Pod{
 13781  				Spec: core.PodSpec{
 13782  					NodeSelector: map[string]string{
 13783  						"foo": "bar",
 13784  					},
 13785  				},
 13786  			},
 13787  			new: core.Pod{},
 13788  			opts: PodValidationOptions{
 13789  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13790  			},
 13791  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13792  			test: "removing node selector is not allowed for non-gated pods",
 13793  		}, {
 13794  			old: core.Pod{
 13795  				Spec: core.PodSpec{
 13796  					NodeSelector: map[string]string{
 13797  						"foo": "bar",
 13798  					},
 13799  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13800  				},
 13801  			},
 13802  			new: core.Pod{
 13803  				Spec: core.PodSpec{
 13804  					NodeSelector: map[string]string{
 13805  						"foo":  "bar",
 13806  						"foo2": "bar2",
 13807  					},
 13808  				},
 13809  			},
 13810  			opts: PodValidationOptions{
 13811  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13812  			},
 13813  			test: "old pod spec has scheduling gate, new pod spec does not, and node selector is added",
 13814  		}, {
 13815  			old: core.Pod{
 13816  				Spec: core.PodSpec{
 13817  					NodeSelector: map[string]string{
 13818  						"foo": "bar",
 13819  					},
 13820  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13821  				},
 13822  			},
 13823  			new: core.Pod{
 13824  				Spec: core.PodSpec{
 13825  					NodeSelector: map[string]string{
 13826  						"foo": "new value",
 13827  					},
 13828  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13829  				},
 13830  			},
 13831  			opts: PodValidationOptions{
 13832  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13833  			},
 13834  			err:  "spec.nodeSelector: Invalid value:",
 13835  			test: "modifying value of existing node selector is not allowed",
 13836  		}, {
 13837  			old: core.Pod{
 13838  				Spec: core.PodSpec{
 13839  					Affinity: &core.Affinity{
 13840  						NodeAffinity: &core.NodeAffinity{
 13841  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13842  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13843  									MatchExpressions: []core.NodeSelectorRequirement{{
 13844  										Key:      "expr",
 13845  										Operator: core.NodeSelectorOpIn,
 13846  										Values:   []string{"foo"},
 13847  									}},
 13848  								}},
 13849  							},
 13850  						},
 13851  					},
 13852  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13853  				},
 13854  			},
 13855  			new: core.Pod{
 13856  				Spec: core.PodSpec{
 13857  					Affinity: &core.Affinity{
 13858  						NodeAffinity: &core.NodeAffinity{
 13859  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13860  								// Add 1 MatchExpression and 1 MatchField.
 13861  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13862  									MatchExpressions: []core.NodeSelectorRequirement{{
 13863  										Key:      "expr",
 13864  										Operator: core.NodeSelectorOpIn,
 13865  										Values:   []string{"foo"},
 13866  									}, {
 13867  										Key:      "expr2",
 13868  										Operator: core.NodeSelectorOpIn,
 13869  										Values:   []string{"foo2"},
 13870  									}},
 13871  									MatchFields: []core.NodeSelectorRequirement{{
 13872  										Key:      "metadata.name",
 13873  										Operator: core.NodeSelectorOpIn,
 13874  										Values:   []string{"foo"},
 13875  									}},
 13876  								}},
 13877  							},
 13878  						},
 13879  					},
 13880  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13881  				},
 13882  			},
 13883  			opts: PodValidationOptions{
 13884  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13885  			},
 13886  			test: "addition to nodeAffinity is allowed for gated pods",
 13887  		}, {
 13888  			old: core.Pod{
 13889  				Spec: core.PodSpec{
 13890  					Affinity: &core.Affinity{
 13891  						NodeAffinity: &core.NodeAffinity{
 13892  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13893  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13894  									MatchExpressions: []core.NodeSelectorRequirement{{
 13895  										Key:      "expr",
 13896  										Operator: core.NodeSelectorOpIn,
 13897  										Values:   []string{"foo"},
 13898  									}},
 13899  								}},
 13900  							},
 13901  						},
 13902  					},
 13903  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13904  				},
 13905  			},
 13906  			new: core.Pod{
 13907  				Spec: core.PodSpec{
 13908  					Affinity: &core.Affinity{
 13909  						NodeAffinity: &core.NodeAffinity{},
 13910  					},
 13911  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13912  				},
 13913  			},
 13914  			opts: PodValidationOptions{
 13915  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13916  			},
 13917  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 13918  			test: "old RequiredDuringSchedulingIgnoredDuringExecution is non-nil, new RequiredDuringSchedulingIgnoredDuringExecution is nil, pod is gated",
 13919  		}, {
 13920  			old: core.Pod{
 13921  				Spec: core.PodSpec{
 13922  					Affinity: &core.Affinity{
 13923  						NodeAffinity: &core.NodeAffinity{
 13924  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13925  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13926  									MatchExpressions: []core.NodeSelectorRequirement{{
 13927  										Key:      "expr",
 13928  										Operator: core.NodeSelectorOpIn,
 13929  										Values:   []string{"foo"},
 13930  									}},
 13931  								}},
 13932  							},
 13933  						},
 13934  					},
 13935  				},
 13936  			},
 13937  			new: core.Pod{
 13938  				Spec: core.PodSpec{
 13939  					Affinity: &core.Affinity{
 13940  						NodeAffinity: &core.NodeAffinity{
 13941  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13942  								// Add 1 MatchExpression and 1 MatchField.
 13943  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13944  									MatchExpressions: []core.NodeSelectorRequirement{{
 13945  										Key:      "expr",
 13946  										Operator: core.NodeSelectorOpIn,
 13947  										Values:   []string{"foo"},
 13948  									}, {
 13949  										Key:      "expr2",
 13950  										Operator: core.NodeSelectorOpIn,
 13951  										Values:   []string{"foo2"},
 13952  									}},
 13953  									MatchFields: []core.NodeSelectorRequirement{{
 13954  										Key:      "metadata.name",
 13955  										Operator: core.NodeSelectorOpIn,
 13956  										Values:   []string{"foo"},
 13957  									}},
 13958  								}},
 13959  							},
 13960  						},
 13961  					},
 13962  				},
 13963  			},
 13964  			opts: PodValidationOptions{
 13965  				AllowMutableNodeSelectorAndNodeAffinity: true,
 13966  			},
 13967  			err:  "Forbidden: pod updates may not change fields other than `spec.containers[*].image",
 13968  			test: "addition to nodeAffinity is not allowed for non-gated pods",
 13969  		}, {
 13970  			old: core.Pod{
 13971  				Spec: core.PodSpec{
 13972  					Affinity: &core.Affinity{
 13973  						NodeAffinity: &core.NodeAffinity{
 13974  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13975  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13976  									MatchExpressions: []core.NodeSelectorRequirement{{
 13977  										Key:      "expr",
 13978  										Operator: core.NodeSelectorOpIn,
 13979  										Values:   []string{"foo"},
 13980  									}},
 13981  								}},
 13982  							},
 13983  						},
 13984  					},
 13985  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 13986  				},
 13987  			},
 13988  			new: core.Pod{
 13989  				Spec: core.PodSpec{
 13990  					Affinity: &core.Affinity{
 13991  						NodeAffinity: &core.NodeAffinity{
 13992  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 13993  								// Add 1 MatchExpression and 1 MatchField.
 13994  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 13995  									MatchExpressions: []core.NodeSelectorRequirement{{
 13996  										Key:      "expr",
 13997  										Operator: core.NodeSelectorOpIn,
 13998  										Values:   []string{"foo"},
 13999  									}, {
 14000  										Key:      "expr2",
 14001  										Operator: core.NodeSelectorOpIn,
 14002  										Values:   []string{"foo2"},
 14003  									}},
 14004  									MatchFields: []core.NodeSelectorRequirement{{
 14005  										Key:      "metadata.name",
 14006  										Operator: core.NodeSelectorOpIn,
 14007  										Values:   []string{"foo"},
 14008  									}},
 14009  								}},
 14010  							},
 14011  						},
 14012  					},
 14013  				},
 14014  			},
 14015  			opts: PodValidationOptions{
 14016  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14017  			},
 14018  			test: "old pod spec has scheduling gate, new pod spec does not, and node affinity addition occurs",
 14019  		}, {
 14020  			old: core.Pod{
 14021  				Spec: core.PodSpec{
 14022  					Affinity: &core.Affinity{
 14023  						NodeAffinity: &core.NodeAffinity{
 14024  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14025  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14026  									MatchExpressions: []core.NodeSelectorRequirement{{
 14027  										Key:      "expr",
 14028  										Operator: core.NodeSelectorOpIn,
 14029  										Values:   []string{"foo"},
 14030  									}},
 14031  								}},
 14032  							},
 14033  						},
 14034  					},
 14035  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14036  				},
 14037  			},
 14038  			new: core.Pod{
 14039  				Spec: core.PodSpec{
 14040  					Affinity: &core.Affinity{
 14041  						NodeAffinity: &core.NodeAffinity{
 14042  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14043  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14044  									MatchFields: []core.NodeSelectorRequirement{{
 14045  										Key:      "metadata.name",
 14046  										Operator: core.NodeSelectorOpIn,
 14047  										Values:   []string{"foo"},
 14048  									}},
 14049  								}},
 14050  							},
 14051  						},
 14052  					},
 14053  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14054  				},
 14055  			},
 14056  			opts: PodValidationOptions{
 14057  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14058  			},
 14059  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14060  			test: "nodeAffinity deletion from MatchExpressions not allowed",
 14061  		}, {
 14062  			old: core.Pod{
 14063  				Spec: core.PodSpec{
 14064  					Affinity: &core.Affinity{
 14065  						NodeAffinity: &core.NodeAffinity{
 14066  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14067  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14068  									MatchExpressions: []core.NodeSelectorRequirement{{
 14069  										Key:      "expr",
 14070  										Operator: core.NodeSelectorOpIn,
 14071  										Values:   []string{"foo"},
 14072  									}},
 14073  									MatchFields: []core.NodeSelectorRequirement{{
 14074  										Key:      "metadata.name",
 14075  										Operator: core.NodeSelectorOpIn,
 14076  										Values:   []string{"foo"},
 14077  									}},
 14078  								}},
 14079  							},
 14080  						},
 14081  					},
 14082  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14083  				},
 14084  			},
 14085  			new: core.Pod{
 14086  				Spec: core.PodSpec{
 14087  					Affinity: &core.Affinity{
 14088  						NodeAffinity: &core.NodeAffinity{
 14089  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14090  								// Add 1 MatchExpression and 1 MatchField.
 14091  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14092  									MatchExpressions: []core.NodeSelectorRequirement{{
 14093  										Key:      "expr",
 14094  										Operator: core.NodeSelectorOpIn,
 14095  										Values:   []string{"foo"},
 14096  									}},
 14097  								}},
 14098  							},
 14099  						},
 14100  					},
 14101  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14102  				},
 14103  			},
 14104  			opts: PodValidationOptions{
 14105  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14106  			},
 14107  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14108  			test: "nodeAffinity deletion from MatchFields not allowed",
 14109  		}, {
 14110  			old: core.Pod{
 14111  				Spec: core.PodSpec{
 14112  					Affinity: &core.Affinity{
 14113  						NodeAffinity: &core.NodeAffinity{
 14114  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14115  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14116  									MatchExpressions: []core.NodeSelectorRequirement{{
 14117  										Key:      "expr",
 14118  										Operator: core.NodeSelectorOpIn,
 14119  										Values:   []string{"foo"},
 14120  									}},
 14121  									MatchFields: []core.NodeSelectorRequirement{{
 14122  										Key:      "metadata.name",
 14123  										Operator: core.NodeSelectorOpIn,
 14124  										Values:   []string{"foo"},
 14125  									}},
 14126  								}},
 14127  							},
 14128  						},
 14129  					},
 14130  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14131  				},
 14132  			},
 14133  			new: core.Pod{
 14134  				Spec: core.PodSpec{
 14135  					Affinity: &core.Affinity{
 14136  						NodeAffinity: &core.NodeAffinity{
 14137  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14138  								// Add 1 MatchExpression and 1 MatchField.
 14139  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14140  									MatchExpressions: []core.NodeSelectorRequirement{{
 14141  										Key:      "expr",
 14142  										Operator: core.NodeSelectorOpIn,
 14143  										Values:   []string{"bar"},
 14144  									}},
 14145  									MatchFields: []core.NodeSelectorRequirement{{
 14146  										Key:      "metadata.name",
 14147  										Operator: core.NodeSelectorOpIn,
 14148  										Values:   []string{"foo"},
 14149  									}},
 14150  								}},
 14151  							},
 14152  						},
 14153  					},
 14154  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14155  				},
 14156  			},
 14157  			opts: PodValidationOptions{
 14158  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14159  			},
 14160  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14161  			test: "nodeAffinity modification of item in MatchExpressions not allowed",
 14162  		}, {
 14163  			old: core.Pod{
 14164  				Spec: core.PodSpec{
 14165  					Affinity: &core.Affinity{
 14166  						NodeAffinity: &core.NodeAffinity{
 14167  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14168  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14169  									MatchExpressions: []core.NodeSelectorRequirement{{
 14170  										Key:      "expr",
 14171  										Operator: core.NodeSelectorOpIn,
 14172  										Values:   []string{"foo"},
 14173  									}},
 14174  									MatchFields: []core.NodeSelectorRequirement{{
 14175  										Key:      "metadata.name",
 14176  										Operator: core.NodeSelectorOpIn,
 14177  										Values:   []string{"foo"},
 14178  									}},
 14179  								}},
 14180  							},
 14181  						},
 14182  					},
 14183  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14184  				},
 14185  			},
 14186  			new: core.Pod{
 14187  				Spec: core.PodSpec{
 14188  					Affinity: &core.Affinity{
 14189  						NodeAffinity: &core.NodeAffinity{
 14190  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14191  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14192  									MatchExpressions: []core.NodeSelectorRequirement{{
 14193  										Key:      "expr",
 14194  										Operator: core.NodeSelectorOpIn,
 14195  										Values:   []string{"foo"},
 14196  									}},
 14197  									MatchFields: []core.NodeSelectorRequirement{{
 14198  										Key:      "metadata.name",
 14199  										Operator: core.NodeSelectorOpIn,
 14200  										Values:   []string{"bar"},
 14201  									}},
 14202  								}},
 14203  							},
 14204  						},
 14205  					},
 14206  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14207  				},
 14208  			},
 14209  			opts: PodValidationOptions{
 14210  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14211  			},
 14212  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14213  			test: "nodeAffinity modification of item in MatchFields not allowed",
 14214  		}, {
 14215  			old: core.Pod{
 14216  				Spec: core.PodSpec{
 14217  					Affinity: &core.Affinity{
 14218  						NodeAffinity: &core.NodeAffinity{
 14219  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14220  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14221  									MatchExpressions: []core.NodeSelectorRequirement{{
 14222  										Key:      "expr",
 14223  										Operator: core.NodeSelectorOpIn,
 14224  										Values:   []string{"foo"},
 14225  									}},
 14226  									MatchFields: []core.NodeSelectorRequirement{{
 14227  										Key:      "metadata.name",
 14228  										Operator: core.NodeSelectorOpIn,
 14229  										Values:   []string{"foo"},
 14230  									}},
 14231  								}},
 14232  							},
 14233  						},
 14234  					},
 14235  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14236  				},
 14237  			},
 14238  			new: core.Pod{
 14239  				Spec: core.PodSpec{
 14240  					Affinity: &core.Affinity{
 14241  						NodeAffinity: &core.NodeAffinity{
 14242  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14243  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14244  									MatchExpressions: []core.NodeSelectorRequirement{{
 14245  										Key:      "expr",
 14246  										Operator: core.NodeSelectorOpIn,
 14247  										Values:   []string{"foo"},
 14248  									}},
 14249  									MatchFields: []core.NodeSelectorRequirement{{
 14250  										Key:      "metadata.name",
 14251  										Operator: core.NodeSelectorOpIn,
 14252  										Values:   []string{"bar"},
 14253  									}},
 14254  								}, {
 14255  									MatchExpressions: []core.NodeSelectorRequirement{{
 14256  										Key:      "expr",
 14257  										Operator: core.NodeSelectorOpIn,
 14258  										Values:   []string{"foo2"},
 14259  									}},
 14260  									MatchFields: []core.NodeSelectorRequirement{{
 14261  										Key:      "metadata.name",
 14262  										Operator: core.NodeSelectorOpIn,
 14263  										Values:   []string{"bar2"},
 14264  									}},
 14265  								}},
 14266  							},
 14267  						},
 14268  					},
 14269  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14270  				},
 14271  			},
 14272  			opts: PodValidationOptions{
 14273  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14274  			},
 14275  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 14276  			test: "nodeSelectorTerms addition on gated pod should fail",
 14277  		}, {
 14278  			old: core.Pod{
 14279  				Spec: core.PodSpec{
 14280  					Affinity: &core.Affinity{
 14281  						NodeAffinity: &core.NodeAffinity{
 14282  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14283  								Weight: 1.0,
 14284  								Preference: core.NodeSelectorTerm{
 14285  									MatchExpressions: []core.NodeSelectorRequirement{{
 14286  										Key:      "expr",
 14287  										Operator: core.NodeSelectorOpIn,
 14288  										Values:   []string{"foo"},
 14289  									}},
 14290  								},
 14291  							}},
 14292  						},
 14293  					},
 14294  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14295  				},
 14296  			},
 14297  			new: core.Pod{
 14298  				Spec: core.PodSpec{
 14299  					Affinity: &core.Affinity{
 14300  						NodeAffinity: &core.NodeAffinity{
 14301  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14302  								Weight: 1.0,
 14303  								Preference: core.NodeSelectorTerm{
 14304  									MatchExpressions: []core.NodeSelectorRequirement{{
 14305  										Key:      "expr",
 14306  										Operator: core.NodeSelectorOpIn,
 14307  										Values:   []string{"foo2"},
 14308  									}},
 14309  								},
 14310  							}},
 14311  						},
 14312  					},
 14313  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14314  				},
 14315  			},
 14316  			opts: PodValidationOptions{
 14317  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14318  			},
 14319  			test: "preferredDuringSchedulingIgnoredDuringExecution can modified for gated pods",
 14320  		}, {
 14321  			old: core.Pod{
 14322  				Spec: core.PodSpec{
 14323  					Affinity: &core.Affinity{
 14324  						NodeAffinity: &core.NodeAffinity{
 14325  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14326  								Weight: 1.0,
 14327  								Preference: core.NodeSelectorTerm{
 14328  									MatchExpressions: []core.NodeSelectorRequirement{{
 14329  										Key:      "expr",
 14330  										Operator: core.NodeSelectorOpIn,
 14331  										Values:   []string{"foo"},
 14332  									}},
 14333  								},
 14334  							}},
 14335  						},
 14336  					},
 14337  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14338  				},
 14339  			},
 14340  			new: core.Pod{
 14341  				Spec: core.PodSpec{
 14342  					Affinity: &core.Affinity{
 14343  						NodeAffinity: &core.NodeAffinity{
 14344  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14345  								Weight: 1.0,
 14346  								Preference: core.NodeSelectorTerm{
 14347  									MatchExpressions: []core.NodeSelectorRequirement{{
 14348  										Key:      "expr",
 14349  										Operator: core.NodeSelectorOpIn,
 14350  										Values:   []string{"foo"},
 14351  									}, {
 14352  										Key:      "expr2",
 14353  										Operator: core.NodeSelectorOpIn,
 14354  										Values:   []string{"foo2"},
 14355  									}},
 14356  									MatchFields: []core.NodeSelectorRequirement{{
 14357  										Key:      "metadata.name",
 14358  										Operator: core.NodeSelectorOpIn,
 14359  										Values:   []string{"bar"},
 14360  									}},
 14361  								},
 14362  							}},
 14363  						},
 14364  					},
 14365  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14366  				},
 14367  			},
 14368  			opts: PodValidationOptions{
 14369  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14370  			},
 14371  			test: "preferredDuringSchedulingIgnoredDuringExecution can have additions for gated pods",
 14372  		}, {
 14373  			old: core.Pod{
 14374  				Spec: core.PodSpec{
 14375  					Affinity: &core.Affinity{
 14376  						NodeAffinity: &core.NodeAffinity{
 14377  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14378  								Weight: 1.0,
 14379  								Preference: core.NodeSelectorTerm{
 14380  									MatchExpressions: []core.NodeSelectorRequirement{{
 14381  										Key:      "expr",
 14382  										Operator: core.NodeSelectorOpIn,
 14383  										Values:   []string{"foo"},
 14384  									}},
 14385  								},
 14386  							}},
 14387  						},
 14388  					},
 14389  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14390  				},
 14391  			},
 14392  			new: core.Pod{
 14393  				Spec: core.PodSpec{
 14394  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14395  				},
 14396  			},
 14397  			opts: PodValidationOptions{
 14398  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14399  			},
 14400  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 14401  		}, {
 14402  			old: core.Pod{
 14403  				Spec: core.PodSpec{
 14404  					Affinity: &core.Affinity{
 14405  						NodeAffinity: &core.NodeAffinity{
 14406  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14407  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14408  									MatchExpressions: []core.NodeSelectorRequirement{{
 14409  										Key:      "expr",
 14410  										Operator: core.NodeSelectorOpIn,
 14411  										Values:   []string{"foo"},
 14412  									}},
 14413  								}},
 14414  							},
 14415  						},
 14416  					},
 14417  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14418  				},
 14419  			},
 14420  			new: core.Pod{
 14421  				Spec: core.PodSpec{
 14422  					Affinity:        &core.Affinity{},
 14423  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14424  				},
 14425  			},
 14426  			opts: PodValidationOptions{
 14427  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14428  			},
 14429  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms: Invalid value:",
 14430  			test: "new node affinity is nil",
 14431  		}, {
 14432  			old: core.Pod{
 14433  				Spec: core.PodSpec{
 14434  					Affinity: &core.Affinity{
 14435  						NodeAffinity: &core.NodeAffinity{
 14436  							PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{{
 14437  								Weight: 1.0,
 14438  								Preference: core.NodeSelectorTerm{
 14439  									MatchExpressions: []core.NodeSelectorRequirement{{
 14440  										Key:      "expr",
 14441  										Operator: core.NodeSelectorOpIn,
 14442  										Values:   []string{"foo"},
 14443  									}},
 14444  								},
 14445  							}},
 14446  						},
 14447  					},
 14448  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14449  				},
 14450  			},
 14451  			new: core.Pod{
 14452  				Spec: core.PodSpec{
 14453  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14454  				},
 14455  			},
 14456  			opts: PodValidationOptions{
 14457  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14458  			},
 14459  			test: "preferredDuringSchedulingIgnoredDuringExecution can have removals for gated pods",
 14460  		}, {
 14461  			old: core.Pod{
 14462  				Spec: core.PodSpec{
 14463  					Affinity: &core.Affinity{
 14464  						NodeAffinity: &core.NodeAffinity{
 14465  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14466  								NodeSelectorTerms: []core.NodeSelectorTerm{
 14467  									{},
 14468  								},
 14469  							},
 14470  						},
 14471  					},
 14472  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14473  				},
 14474  			},
 14475  			new: core.Pod{
 14476  				Spec: core.PodSpec{
 14477  					Affinity: &core.Affinity{
 14478  						NodeAffinity: &core.NodeAffinity{
 14479  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14480  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14481  									MatchExpressions: []core.NodeSelectorRequirement{{
 14482  										Key:      "expr",
 14483  										Operator: core.NodeSelectorOpIn,
 14484  										Values:   []string{"foo"},
 14485  									}},
 14486  								}},
 14487  							},
 14488  						},
 14489  					},
 14490  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14491  				},
 14492  			},
 14493  			opts: PodValidationOptions{
 14494  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14495  			},
 14496  			err:  "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0]: Invalid value:",
 14497  			test: "empty NodeSelectorTerm (selects nothing) cannot become populated (selects something)",
 14498  		}, {
 14499  			old: core.Pod{
 14500  				Spec: core.PodSpec{
 14501  					Affinity:        nil,
 14502  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14503  				},
 14504  			},
 14505  			new: core.Pod{
 14506  				Spec: core.PodSpec{
 14507  					Affinity: &core.Affinity{
 14508  						NodeAffinity: &core.NodeAffinity{
 14509  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14510  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14511  									MatchExpressions: []core.NodeSelectorRequirement{{
 14512  										Key:      "expr",
 14513  										Operator: core.NodeSelectorOpIn,
 14514  										Values:   []string{"foo"},
 14515  									}},
 14516  								}},
 14517  							},
 14518  						},
 14519  					},
 14520  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14521  				},
 14522  			},
 14523  			opts: PodValidationOptions{
 14524  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14525  			},
 14526  			test: "nil affinity can be mutated for gated pods",
 14527  		},
 14528  		{
 14529  			old: core.Pod{
 14530  				Spec: core.PodSpec{
 14531  					Affinity:        nil,
 14532  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14533  				},
 14534  			},
 14535  			new: core.Pod{
 14536  				Spec: core.PodSpec{
 14537  					Affinity: &core.Affinity{
 14538  						NodeAffinity: &core.NodeAffinity{
 14539  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14540  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14541  									MatchExpressions: []core.NodeSelectorRequirement{{
 14542  										Key:      "expr",
 14543  										Operator: core.NodeSelectorOpIn,
 14544  										Values:   []string{"foo"},
 14545  									}},
 14546  								}},
 14547  							},
 14548  						},
 14549  						PodAffinity: &core.PodAffinity{
 14550  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 14551  								{
 14552  									TopologyKey: "foo",
 14553  									LabelSelector: &metav1.LabelSelector{
 14554  										MatchLabels: map[string]string{"foo": "bar"},
 14555  									},
 14556  								},
 14557  							},
 14558  						},
 14559  					},
 14560  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14561  				},
 14562  			},
 14563  			opts: PodValidationOptions{
 14564  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14565  			},
 14566  			err:  "pod updates may not change fields other than",
 14567  			test: "the podAffinity cannot be updated on gated pods",
 14568  		},
 14569  		{
 14570  			old: core.Pod{
 14571  				Spec: core.PodSpec{
 14572  					Affinity:        nil,
 14573  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14574  				},
 14575  			},
 14576  			new: core.Pod{
 14577  				Spec: core.PodSpec{
 14578  					Affinity: &core.Affinity{
 14579  						NodeAffinity: &core.NodeAffinity{
 14580  							RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
 14581  								NodeSelectorTerms: []core.NodeSelectorTerm{{
 14582  									MatchExpressions: []core.NodeSelectorRequirement{{
 14583  										Key:      "expr",
 14584  										Operator: core.NodeSelectorOpIn,
 14585  										Values:   []string{"foo"},
 14586  									}},
 14587  								}},
 14588  							},
 14589  						},
 14590  						PodAntiAffinity: &core.PodAntiAffinity{
 14591  							RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
 14592  								{
 14593  									TopologyKey: "foo",
 14594  									LabelSelector: &metav1.LabelSelector{
 14595  										MatchLabels: map[string]string{"foo": "bar"},
 14596  									},
 14597  								},
 14598  							},
 14599  						},
 14600  					},
 14601  					SchedulingGates: []core.PodSchedulingGate{{Name: "baz"}},
 14602  				},
 14603  			},
 14604  			opts: PodValidationOptions{
 14605  				AllowMutableNodeSelectorAndNodeAffinity: true,
 14606  			},
 14607  			err:  "pod updates may not change fields other than",
 14608  			test: "the podAntiAffinity cannot be updated on gated pods",
 14609  		},
 14610  	}
 14611  	for _, test := range tests {
 14612  		test.new.ObjectMeta.ResourceVersion = "1"
 14613  		test.old.ObjectMeta.ResourceVersion = "1"
 14614  
 14615  		// set required fields if old and new match and have no opinion on the value
 14616  		if test.new.Name == "" && test.old.Name == "" {
 14617  			test.new.Name = "name"
 14618  			test.old.Name = "name"
 14619  		}
 14620  		if test.new.Namespace == "" && test.old.Namespace == "" {
 14621  			test.new.Namespace = "namespace"
 14622  			test.old.Namespace = "namespace"
 14623  		}
 14624  		if test.new.Spec.Containers == nil && test.old.Spec.Containers == nil {
 14625  			test.new.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 14626  			test.old.Spec.Containers = []core.Container{{Name: "autoadded", Image: "image", TerminationMessagePolicy: "File", ImagePullPolicy: "Always"}}
 14627  		}
 14628  		if len(test.new.Spec.DNSPolicy) == 0 && len(test.old.Spec.DNSPolicy) == 0 {
 14629  			test.new.Spec.DNSPolicy = core.DNSClusterFirst
 14630  			test.old.Spec.DNSPolicy = core.DNSClusterFirst
 14631  		}
 14632  		if len(test.new.Spec.RestartPolicy) == 0 && len(test.old.Spec.RestartPolicy) == 0 {
 14633  			test.new.Spec.RestartPolicy = "Always"
 14634  			test.old.Spec.RestartPolicy = "Always"
 14635  		}
 14636  
 14637  		errs := ValidatePodUpdate(&test.new, &test.old, test.opts)
 14638  		if test.err == "" {
 14639  			if len(errs) != 0 {
 14640  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 14641  			}
 14642  		} else {
 14643  			if len(errs) == 0 {
 14644  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 14645  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 14646  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 14647  			}
 14648  		}
 14649  	}
 14650  }
 14651  
 14652  func TestValidatePodStatusUpdate(t *testing.T) {
 14653  	tests := []struct {
 14654  		new  core.Pod
 14655  		old  core.Pod
 14656  		err  string
 14657  		test string
 14658  	}{{
 14659  		core.Pod{
 14660  			ObjectMeta: metav1.ObjectMeta{
 14661  				Name: "foo",
 14662  			},
 14663  			Spec: core.PodSpec{
 14664  				NodeName: "node1",
 14665  			},
 14666  			Status: core.PodStatus{
 14667  				NominatedNodeName: "node1",
 14668  			},
 14669  		},
 14670  		core.Pod{
 14671  			ObjectMeta: metav1.ObjectMeta{
 14672  				Name: "foo",
 14673  			},
 14674  			Spec: core.PodSpec{
 14675  				NodeName: "node1",
 14676  			},
 14677  			Status: core.PodStatus{},
 14678  		},
 14679  		"",
 14680  		"removed nominatedNodeName",
 14681  	}, {
 14682  		core.Pod{
 14683  			ObjectMeta: metav1.ObjectMeta{
 14684  				Name: "foo",
 14685  			},
 14686  			Spec: core.PodSpec{
 14687  				NodeName: "node1",
 14688  			},
 14689  		},
 14690  		core.Pod{
 14691  			ObjectMeta: metav1.ObjectMeta{
 14692  				Name: "foo",
 14693  			},
 14694  			Spec: core.PodSpec{
 14695  				NodeName: "node1",
 14696  			},
 14697  			Status: core.PodStatus{
 14698  				NominatedNodeName: "node1",
 14699  			},
 14700  		},
 14701  		"",
 14702  		"add valid nominatedNodeName",
 14703  	}, {
 14704  		core.Pod{
 14705  			ObjectMeta: metav1.ObjectMeta{
 14706  				Name: "foo",
 14707  			},
 14708  			Spec: core.PodSpec{
 14709  				NodeName: "node1",
 14710  			},
 14711  			Status: core.PodStatus{
 14712  				NominatedNodeName: "Node1",
 14713  			},
 14714  		},
 14715  		core.Pod{
 14716  			ObjectMeta: metav1.ObjectMeta{
 14717  				Name: "foo",
 14718  			},
 14719  			Spec: core.PodSpec{
 14720  				NodeName: "node1",
 14721  			},
 14722  		},
 14723  		"nominatedNodeName",
 14724  		"Add invalid nominatedNodeName",
 14725  	}, {
 14726  		core.Pod{
 14727  			ObjectMeta: metav1.ObjectMeta{
 14728  				Name: "foo",
 14729  			},
 14730  			Spec: core.PodSpec{
 14731  				NodeName: "node1",
 14732  			},
 14733  			Status: core.PodStatus{
 14734  				NominatedNodeName: "node1",
 14735  			},
 14736  		},
 14737  		core.Pod{
 14738  			ObjectMeta: metav1.ObjectMeta{
 14739  				Name: "foo",
 14740  			},
 14741  			Spec: core.PodSpec{
 14742  				NodeName: "node1",
 14743  			},
 14744  			Status: core.PodStatus{
 14745  				NominatedNodeName: "node2",
 14746  			},
 14747  		},
 14748  		"",
 14749  		"Update nominatedNodeName",
 14750  	}, {
 14751  		core.Pod{
 14752  			ObjectMeta: metav1.ObjectMeta{
 14753  				Name: "foo",
 14754  			},
 14755  			Status: core.PodStatus{
 14756  				InitContainerStatuses: []core.ContainerStatus{{
 14757  					ContainerID: "docker://numbers",
 14758  					Image:       "alpine",
 14759  					Name:        "init",
 14760  					Ready:       false,
 14761  					Started:     proto.Bool(false),
 14762  					State: core.ContainerState{
 14763  						Waiting: &core.ContainerStateWaiting{
 14764  							Reason: "PodInitializing",
 14765  						},
 14766  					},
 14767  				}},
 14768  				ContainerStatuses: []core.ContainerStatus{{
 14769  					ContainerID: "docker://numbers",
 14770  					Image:       "nginx:alpine",
 14771  					Name:        "main",
 14772  					Ready:       false,
 14773  					Started:     proto.Bool(false),
 14774  					State: core.ContainerState{
 14775  						Waiting: &core.ContainerStateWaiting{
 14776  							Reason: "PodInitializing",
 14777  						},
 14778  					},
 14779  				}},
 14780  			},
 14781  		},
 14782  		core.Pod{
 14783  			ObjectMeta: metav1.ObjectMeta{
 14784  				Name: "foo",
 14785  			},
 14786  		},
 14787  		"",
 14788  		"Container statuses pending",
 14789  	}, {
 14790  		core.Pod{
 14791  			ObjectMeta: metav1.ObjectMeta{
 14792  				Name: "foo",
 14793  			},
 14794  			Status: core.PodStatus{
 14795  				InitContainerStatuses: []core.ContainerStatus{{
 14796  					ContainerID: "docker://numbers",
 14797  					Image:       "alpine",
 14798  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14799  					Name:        "init",
 14800  					Ready:       true,
 14801  					State: core.ContainerState{
 14802  						Terminated: &core.ContainerStateTerminated{
 14803  							ContainerID: "docker://numbers",
 14804  							Reason:      "Completed",
 14805  						},
 14806  					},
 14807  				}},
 14808  				ContainerStatuses: []core.ContainerStatus{{
 14809  					ContainerID: "docker://numbers",
 14810  					Image:       "nginx:alpine",
 14811  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14812  					Name:        "nginx",
 14813  					Ready:       true,
 14814  					Started:     proto.Bool(true),
 14815  					State: core.ContainerState{
 14816  						Running: &core.ContainerStateRunning{
 14817  							StartedAt: metav1.NewTime(time.Now()),
 14818  						},
 14819  					},
 14820  				}},
 14821  			},
 14822  		},
 14823  		core.Pod{
 14824  			ObjectMeta: metav1.ObjectMeta{
 14825  				Name: "foo",
 14826  			},
 14827  			Status: core.PodStatus{
 14828  				InitContainerStatuses: []core.ContainerStatus{{
 14829  					ContainerID: "docker://numbers",
 14830  					Image:       "alpine",
 14831  					Name:        "init",
 14832  					Ready:       false,
 14833  					State: core.ContainerState{
 14834  						Waiting: &core.ContainerStateWaiting{
 14835  							Reason: "PodInitializing",
 14836  						},
 14837  					},
 14838  				}},
 14839  				ContainerStatuses: []core.ContainerStatus{{
 14840  					ContainerID: "docker://numbers",
 14841  					Image:       "nginx:alpine",
 14842  					Name:        "main",
 14843  					Ready:       false,
 14844  					Started:     proto.Bool(false),
 14845  					State: core.ContainerState{
 14846  						Waiting: &core.ContainerStateWaiting{
 14847  							Reason: "PodInitializing",
 14848  						},
 14849  					},
 14850  				}},
 14851  			},
 14852  		},
 14853  		"",
 14854  		"Container statuses running",
 14855  	}, {
 14856  		core.Pod{
 14857  			ObjectMeta: metav1.ObjectMeta{
 14858  				Name: "foo",
 14859  			},
 14860  			Status: core.PodStatus{
 14861  				ContainerStatuses: []core.ContainerStatus{{
 14862  					ContainerID: "docker://numbers",
 14863  					Image:       "nginx:alpine",
 14864  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14865  					Name:        "nginx",
 14866  					Ready:       true,
 14867  					Started:     proto.Bool(true),
 14868  					State: core.ContainerState{
 14869  						Running: &core.ContainerStateRunning{
 14870  							StartedAt: metav1.NewTime(time.Now()),
 14871  						},
 14872  					},
 14873  				}},
 14874  				EphemeralContainerStatuses: []core.ContainerStatus{{
 14875  					ContainerID: "docker://numbers",
 14876  					Image:       "busybox",
 14877  					Name:        "debug",
 14878  					Ready:       false,
 14879  					State: core.ContainerState{
 14880  						Waiting: &core.ContainerStateWaiting{
 14881  							Reason: "PodInitializing",
 14882  						},
 14883  					},
 14884  				}},
 14885  			},
 14886  		},
 14887  		core.Pod{
 14888  			ObjectMeta: metav1.ObjectMeta{
 14889  				Name: "foo",
 14890  			},
 14891  			Status: core.PodStatus{
 14892  				ContainerStatuses: []core.ContainerStatus{{
 14893  					ContainerID: "docker://numbers",
 14894  					Image:       "nginx:alpine",
 14895  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14896  					Name:        "nginx",
 14897  					Ready:       true,
 14898  					Started:     proto.Bool(true),
 14899  					State: core.ContainerState{
 14900  						Running: &core.ContainerStateRunning{
 14901  							StartedAt: metav1.NewTime(time.Now()),
 14902  						},
 14903  					},
 14904  				}},
 14905  			},
 14906  		},
 14907  		"",
 14908  		"Container statuses add ephemeral container",
 14909  	}, {
 14910  		core.Pod{
 14911  			ObjectMeta: metav1.ObjectMeta{
 14912  				Name: "foo",
 14913  			},
 14914  			Status: core.PodStatus{
 14915  				ContainerStatuses: []core.ContainerStatus{{
 14916  					ContainerID: "docker://numbers",
 14917  					Image:       "nginx:alpine",
 14918  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14919  					Name:        "nginx",
 14920  					Ready:       true,
 14921  					Started:     proto.Bool(true),
 14922  					State: core.ContainerState{
 14923  						Running: &core.ContainerStateRunning{
 14924  							StartedAt: metav1.NewTime(time.Now()),
 14925  						},
 14926  					},
 14927  				}},
 14928  				EphemeralContainerStatuses: []core.ContainerStatus{{
 14929  					ContainerID: "docker://numbers",
 14930  					Image:       "busybox",
 14931  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 14932  					Name:        "debug",
 14933  					Ready:       false,
 14934  					State: core.ContainerState{
 14935  						Running: &core.ContainerStateRunning{
 14936  							StartedAt: metav1.NewTime(time.Now()),
 14937  						},
 14938  					},
 14939  				}},
 14940  			},
 14941  		},
 14942  		core.Pod{
 14943  			ObjectMeta: metav1.ObjectMeta{
 14944  				Name: "foo",
 14945  			},
 14946  			Status: core.PodStatus{
 14947  				ContainerStatuses: []core.ContainerStatus{{
 14948  					ContainerID: "docker://numbers",
 14949  					Image:       "nginx:alpine",
 14950  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14951  					Name:        "nginx",
 14952  					Ready:       true,
 14953  					Started:     proto.Bool(true),
 14954  					State: core.ContainerState{
 14955  						Running: &core.ContainerStateRunning{
 14956  							StartedAt: metav1.NewTime(time.Now()),
 14957  						},
 14958  					},
 14959  				}},
 14960  				EphemeralContainerStatuses: []core.ContainerStatus{{
 14961  					ContainerID: "docker://numbers",
 14962  					Image:       "busybox",
 14963  					Name:        "debug",
 14964  					Ready:       false,
 14965  					State: core.ContainerState{
 14966  						Waiting: &core.ContainerStateWaiting{
 14967  							Reason: "PodInitializing",
 14968  						},
 14969  					},
 14970  				}},
 14971  			},
 14972  		},
 14973  		"",
 14974  		"Container statuses ephemeral container running",
 14975  	}, {
 14976  		core.Pod{
 14977  			ObjectMeta: metav1.ObjectMeta{
 14978  				Name: "foo",
 14979  			},
 14980  			Status: core.PodStatus{
 14981  				ContainerStatuses: []core.ContainerStatus{{
 14982  					ContainerID: "docker://numbers",
 14983  					Image:       "nginx:alpine",
 14984  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 14985  					Name:        "nginx",
 14986  					Ready:       true,
 14987  					Started:     proto.Bool(true),
 14988  					State: core.ContainerState{
 14989  						Running: &core.ContainerStateRunning{
 14990  							StartedAt: metav1.NewTime(time.Now()),
 14991  						},
 14992  					},
 14993  				}},
 14994  				EphemeralContainerStatuses: []core.ContainerStatus{{
 14995  					ContainerID: "docker://numbers",
 14996  					Image:       "busybox",
 14997  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 14998  					Name:        "debug",
 14999  					Ready:       false,
 15000  					State: core.ContainerState{
 15001  						Terminated: &core.ContainerStateTerminated{
 15002  							ContainerID: "docker://numbers",
 15003  							Reason:      "Completed",
 15004  							StartedAt:   metav1.NewTime(time.Now()),
 15005  							FinishedAt:  metav1.NewTime(time.Now()),
 15006  						},
 15007  					},
 15008  				}},
 15009  			},
 15010  		},
 15011  		core.Pod{
 15012  			ObjectMeta: metav1.ObjectMeta{
 15013  				Name: "foo",
 15014  			},
 15015  			Status: core.PodStatus{
 15016  				ContainerStatuses: []core.ContainerStatus{{
 15017  					ContainerID: "docker://numbers",
 15018  					Image:       "nginx:alpine",
 15019  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15020  					Name:        "nginx",
 15021  					Ready:       true,
 15022  					Started:     proto.Bool(true),
 15023  					State: core.ContainerState{
 15024  						Running: &core.ContainerStateRunning{
 15025  							StartedAt: metav1.NewTime(time.Now()),
 15026  						},
 15027  					},
 15028  				}},
 15029  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15030  					ContainerID: "docker://numbers",
 15031  					Image:       "busybox",
 15032  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15033  					Name:        "debug",
 15034  					Ready:       false,
 15035  					State: core.ContainerState{
 15036  						Running: &core.ContainerStateRunning{
 15037  							StartedAt: metav1.NewTime(time.Now()),
 15038  						},
 15039  					},
 15040  				}},
 15041  			},
 15042  		},
 15043  		"",
 15044  		"Container statuses ephemeral container exited",
 15045  	}, {
 15046  		core.Pod{
 15047  			ObjectMeta: metav1.ObjectMeta{
 15048  				Name: "foo",
 15049  			},
 15050  			Status: core.PodStatus{
 15051  				InitContainerStatuses: []core.ContainerStatus{{
 15052  					ContainerID: "docker://numbers",
 15053  					Image:       "alpine",
 15054  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15055  					Name:        "init",
 15056  					Ready:       true,
 15057  					State: core.ContainerState{
 15058  						Terminated: &core.ContainerStateTerminated{
 15059  							ContainerID: "docker://numbers",
 15060  							Reason:      "Completed",
 15061  						},
 15062  					},
 15063  				}},
 15064  				ContainerStatuses: []core.ContainerStatus{{
 15065  					ContainerID: "docker://numbers",
 15066  					Image:       "nginx:alpine",
 15067  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15068  					Name:        "nginx",
 15069  					Ready:       true,
 15070  					Started:     proto.Bool(true),
 15071  					State: core.ContainerState{
 15072  						Terminated: &core.ContainerStateTerminated{
 15073  							ContainerID: "docker://numbers",
 15074  							Reason:      "Completed",
 15075  							StartedAt:   metav1.NewTime(time.Now()),
 15076  							FinishedAt:  metav1.NewTime(time.Now()),
 15077  						},
 15078  					},
 15079  				}},
 15080  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15081  					ContainerID: "docker://numbers",
 15082  					Image:       "busybox",
 15083  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15084  					Name:        "debug",
 15085  					Ready:       false,
 15086  					State: core.ContainerState{
 15087  						Terminated: &core.ContainerStateTerminated{
 15088  							ContainerID: "docker://numbers",
 15089  							Reason:      "Completed",
 15090  							StartedAt:   metav1.NewTime(time.Now()),
 15091  							FinishedAt:  metav1.NewTime(time.Now()),
 15092  						},
 15093  					},
 15094  				}},
 15095  			},
 15096  		},
 15097  		core.Pod{
 15098  			ObjectMeta: metav1.ObjectMeta{
 15099  				Name: "foo",
 15100  			},
 15101  			Status: core.PodStatus{
 15102  				InitContainerStatuses: []core.ContainerStatus{{
 15103  					ContainerID: "docker://numbers",
 15104  					Image:       "alpine",
 15105  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15106  					Name:        "init",
 15107  					Ready:       true,
 15108  					State: core.ContainerState{
 15109  						Terminated: &core.ContainerStateTerminated{
 15110  							ContainerID: "docker://numbers",
 15111  							Reason:      "Completed",
 15112  						},
 15113  					},
 15114  				}},
 15115  				ContainerStatuses: []core.ContainerStatus{{
 15116  					ContainerID: "docker://numbers",
 15117  					Image:       "nginx:alpine",
 15118  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15119  					Name:        "nginx",
 15120  					Ready:       true,
 15121  					Started:     proto.Bool(true),
 15122  					State: core.ContainerState{
 15123  						Running: &core.ContainerStateRunning{
 15124  							StartedAt: metav1.NewTime(time.Now()),
 15125  						},
 15126  					},
 15127  				}},
 15128  				EphemeralContainerStatuses: []core.ContainerStatus{{
 15129  					ContainerID: "docker://numbers",
 15130  					Image:       "busybox",
 15131  					ImageID:     "docker-pullable://busybox@sha256:d0gf00d",
 15132  					Name:        "debug",
 15133  					Ready:       false,
 15134  					State: core.ContainerState{
 15135  						Running: &core.ContainerStateRunning{
 15136  							StartedAt: metav1.NewTime(time.Now()),
 15137  						},
 15138  					},
 15139  				}},
 15140  			},
 15141  		},
 15142  		"",
 15143  		"Container statuses all containers terminated",
 15144  	}, {
 15145  		core.Pod{
 15146  			ObjectMeta: metav1.ObjectMeta{
 15147  				Name: "foo",
 15148  			},
 15149  			Status: core.PodStatus{
 15150  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15151  					{Name: "no-such-claim", ResourceClaimName: utilpointer.String("my-claim")},
 15152  				},
 15153  			},
 15154  		},
 15155  		core.Pod{
 15156  			ObjectMeta: metav1.ObjectMeta{
 15157  				Name: "foo",
 15158  			},
 15159  		},
 15160  		"status.resourceClaimStatuses[0].name: Invalid value: \"no-such-claim\": must match the name of an entry in `spec.resourceClaims`",
 15161  		"Non-existent PodResourceClaim",
 15162  	}, {
 15163  		core.Pod{
 15164  			ObjectMeta: metav1.ObjectMeta{
 15165  				Name: "foo",
 15166  			},
 15167  			Spec: core.PodSpec{
 15168  				ResourceClaims: []core.PodResourceClaim{
 15169  					{Name: "my-claim"},
 15170  				},
 15171  			},
 15172  			Status: core.PodStatus{
 15173  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15174  					{Name: "my-claim", ResourceClaimName: utilpointer.String("%$!#")},
 15175  				},
 15176  			},
 15177  		},
 15178  		core.Pod{
 15179  			ObjectMeta: metav1.ObjectMeta{
 15180  				Name: "foo",
 15181  			},
 15182  			Spec: core.PodSpec{
 15183  				ResourceClaims: []core.PodResourceClaim{
 15184  					{Name: "my-claim"},
 15185  				},
 15186  			},
 15187  		},
 15188  		`status.resourceClaimStatuses[0].name: Invalid value: "%$!#": a lowercase RFC 1123 subdomain must consist of`,
 15189  		"Invalid ResourceClaim name",
 15190  	}, {
 15191  		core.Pod{
 15192  			ObjectMeta: metav1.ObjectMeta{
 15193  				Name: "foo",
 15194  			},
 15195  			Spec: core.PodSpec{
 15196  				ResourceClaims: []core.PodResourceClaim{
 15197  					{Name: "my-claim"},
 15198  					{Name: "my-other-claim"},
 15199  				},
 15200  			},
 15201  			Status: core.PodStatus{
 15202  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15203  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 15204  					{Name: "my-other-claim", ResourceClaimName: nil},
 15205  					{Name: "my-other-claim", ResourceClaimName: nil},
 15206  				},
 15207  			},
 15208  		},
 15209  		core.Pod{
 15210  			ObjectMeta: metav1.ObjectMeta{
 15211  				Name: "foo",
 15212  			},
 15213  			Spec: core.PodSpec{
 15214  				ResourceClaims: []core.PodResourceClaim{
 15215  					{Name: "my-claim"},
 15216  				},
 15217  			},
 15218  		},
 15219  		`status.resourceClaimStatuses[2].name: Duplicate value: "my-other-claim"`,
 15220  		"Duplicate ResourceClaimStatuses.Name",
 15221  	}, {
 15222  		core.Pod{
 15223  			ObjectMeta: metav1.ObjectMeta{
 15224  				Name: "foo",
 15225  			},
 15226  			Spec: core.PodSpec{
 15227  				ResourceClaims: []core.PodResourceClaim{
 15228  					{Name: "my-claim"},
 15229  					{Name: "my-other-claim"},
 15230  				},
 15231  			},
 15232  			Status: core.PodStatus{
 15233  				ResourceClaimStatuses: []core.PodResourceClaimStatus{
 15234  					{Name: "my-claim", ResourceClaimName: utilpointer.String("foo-my-claim-12345")},
 15235  					{Name: "my-other-claim", ResourceClaimName: nil},
 15236  				},
 15237  			},
 15238  		},
 15239  		core.Pod{
 15240  			ObjectMeta: metav1.ObjectMeta{
 15241  				Name: "foo",
 15242  			},
 15243  			Spec: core.PodSpec{
 15244  				ResourceClaims: []core.PodResourceClaim{
 15245  					{Name: "my-claim"},
 15246  				},
 15247  			},
 15248  		},
 15249  		"",
 15250  		"ResourceClaimStatuses okay",
 15251  	}, {
 15252  		core.Pod{
 15253  			ObjectMeta: metav1.ObjectMeta{
 15254  				Name: "foo",
 15255  			},
 15256  			Spec: core.PodSpec{
 15257  				InitContainers: []core.Container{
 15258  					{
 15259  						Name: "init",
 15260  					},
 15261  				},
 15262  				Containers: []core.Container{
 15263  					{
 15264  						Name: "nginx",
 15265  					},
 15266  				},
 15267  			},
 15268  			Status: core.PodStatus{
 15269  				InitContainerStatuses: []core.ContainerStatus{{
 15270  					ContainerID: "docker://numbers",
 15271  					Image:       "alpine",
 15272  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15273  					Name:        "init",
 15274  					Ready:       true,
 15275  					State: core.ContainerState{
 15276  						Running: &core.ContainerStateRunning{
 15277  							StartedAt: metav1.NewTime(time.Now()),
 15278  						},
 15279  					},
 15280  				}},
 15281  				ContainerStatuses: []core.ContainerStatus{{
 15282  					ContainerID: "docker://numbers",
 15283  					Image:       "nginx:alpine",
 15284  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15285  					Name:        "nginx",
 15286  					Ready:       true,
 15287  					Started:     proto.Bool(true),
 15288  					State: core.ContainerState{
 15289  						Running: &core.ContainerStateRunning{
 15290  							StartedAt: metav1.NewTime(time.Now()),
 15291  						},
 15292  					},
 15293  				}},
 15294  			},
 15295  		},
 15296  		core.Pod{
 15297  			ObjectMeta: metav1.ObjectMeta{
 15298  				Name: "foo",
 15299  			},
 15300  			Spec: core.PodSpec{
 15301  				InitContainers: []core.Container{
 15302  					{
 15303  						Name: "init",
 15304  					},
 15305  				},
 15306  				Containers: []core.Container{
 15307  					{
 15308  						Name: "nginx",
 15309  					},
 15310  				},
 15311  				RestartPolicy: core.RestartPolicyNever,
 15312  			},
 15313  			Status: core.PodStatus{
 15314  				InitContainerStatuses: []core.ContainerStatus{{
 15315  					ContainerID: "docker://numbers",
 15316  					Image:       "alpine",
 15317  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15318  					Name:        "init",
 15319  					Ready:       false,
 15320  					State: core.ContainerState{
 15321  						Terminated: &core.ContainerStateTerminated{
 15322  							ContainerID: "docker://numbers",
 15323  							Reason:      "Completed",
 15324  						},
 15325  					},
 15326  				}},
 15327  				ContainerStatuses: []core.ContainerStatus{{
 15328  					ContainerID: "docker://numbers",
 15329  					Image:       "nginx:alpine",
 15330  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15331  					Name:        "nginx",
 15332  					Ready:       true,
 15333  					Started:     proto.Bool(true),
 15334  					State: core.ContainerState{
 15335  						Running: &core.ContainerStateRunning{
 15336  							StartedAt: metav1.NewTime(time.Now()),
 15337  						},
 15338  					},
 15339  				}},
 15340  			},
 15341  		},
 15342  		`status.initContainerStatuses[0].state: Forbidden: may not be transitioned to non-terminated state`,
 15343  		"init container cannot restart if RestartPolicyNever",
 15344  	}, {
 15345  		core.Pod{
 15346  			ObjectMeta: metav1.ObjectMeta{
 15347  				Name: "foo",
 15348  			},
 15349  			Spec: core.PodSpec{
 15350  				InitContainers: []core.Container{
 15351  					{
 15352  						Name:          "restartable-init",
 15353  						RestartPolicy: &containerRestartPolicyAlways,
 15354  					},
 15355  				},
 15356  				Containers: []core.Container{
 15357  					{
 15358  						Name: "nginx",
 15359  					},
 15360  				},
 15361  				RestartPolicy: core.RestartPolicyNever,
 15362  			},
 15363  			Status: core.PodStatus{
 15364  				InitContainerStatuses: []core.ContainerStatus{{
 15365  					ContainerID: "docker://numbers",
 15366  					Image:       "alpine",
 15367  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15368  					Name:        "restartable-init",
 15369  					Ready:       true,
 15370  					State: core.ContainerState{
 15371  						Running: &core.ContainerStateRunning{
 15372  							StartedAt: metav1.NewTime(time.Now()),
 15373  						},
 15374  					},
 15375  				}},
 15376  				ContainerStatuses: []core.ContainerStatus{{
 15377  					ContainerID: "docker://numbers",
 15378  					Image:       "nginx:alpine",
 15379  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15380  					Name:        "nginx",
 15381  					Ready:       true,
 15382  					Started:     proto.Bool(true),
 15383  					State: core.ContainerState{
 15384  						Running: &core.ContainerStateRunning{
 15385  							StartedAt: metav1.NewTime(time.Now()),
 15386  						},
 15387  					},
 15388  				}},
 15389  			},
 15390  		},
 15391  		core.Pod{
 15392  			ObjectMeta: metav1.ObjectMeta{
 15393  				Name: "foo",
 15394  			},
 15395  			Spec: core.PodSpec{
 15396  				InitContainers: []core.Container{
 15397  					{
 15398  						Name:          "restartable-init",
 15399  						RestartPolicy: &containerRestartPolicyAlways,
 15400  					},
 15401  				},
 15402  				Containers: []core.Container{
 15403  					{
 15404  						Name: "nginx",
 15405  					},
 15406  				},
 15407  				RestartPolicy: core.RestartPolicyNever,
 15408  			},
 15409  			Status: core.PodStatus{
 15410  				InitContainerStatuses: []core.ContainerStatus{{
 15411  					ContainerID: "docker://numbers",
 15412  					Image:       "alpine",
 15413  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15414  					Name:        "restartable-init",
 15415  					Ready:       false,
 15416  					State: core.ContainerState{
 15417  						Terminated: &core.ContainerStateTerminated{
 15418  							ContainerID: "docker://numbers",
 15419  							Reason:      "Completed",
 15420  						},
 15421  					},
 15422  				}},
 15423  				ContainerStatuses: []core.ContainerStatus{{
 15424  					ContainerID: "docker://numbers",
 15425  					Image:       "nginx:alpine",
 15426  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15427  					Name:        "nginx",
 15428  					Ready:       true,
 15429  					Started:     proto.Bool(true),
 15430  					State: core.ContainerState{
 15431  						Running: &core.ContainerStateRunning{
 15432  							StartedAt: metav1.NewTime(time.Now()),
 15433  						},
 15434  					},
 15435  				}},
 15436  			},
 15437  		},
 15438  		"",
 15439  		"restartable init container can restart if RestartPolicyNever",
 15440  	}, {
 15441  		core.Pod{
 15442  			ObjectMeta: metav1.ObjectMeta{
 15443  				Name: "foo",
 15444  			},
 15445  			Spec: core.PodSpec{
 15446  				InitContainers: []core.Container{
 15447  					{
 15448  						Name:          "restartable-init",
 15449  						RestartPolicy: &containerRestartPolicyAlways,
 15450  					},
 15451  				},
 15452  				Containers: []core.Container{
 15453  					{
 15454  						Name: "nginx",
 15455  					},
 15456  				},
 15457  				RestartPolicy: core.RestartPolicyOnFailure,
 15458  			},
 15459  			Status: core.PodStatus{
 15460  				InitContainerStatuses: []core.ContainerStatus{{
 15461  					ContainerID: "docker://numbers",
 15462  					Image:       "alpine",
 15463  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15464  					Name:        "restartable-init",
 15465  					Ready:       true,
 15466  					State: core.ContainerState{
 15467  						Running: &core.ContainerStateRunning{
 15468  							StartedAt: metav1.NewTime(time.Now()),
 15469  						},
 15470  					},
 15471  				}},
 15472  				ContainerStatuses: []core.ContainerStatus{{
 15473  					ContainerID: "docker://numbers",
 15474  					Image:       "nginx:alpine",
 15475  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15476  					Name:        "nginx",
 15477  					Ready:       true,
 15478  					Started:     proto.Bool(true),
 15479  					State: core.ContainerState{
 15480  						Running: &core.ContainerStateRunning{
 15481  							StartedAt: metav1.NewTime(time.Now()),
 15482  						},
 15483  					},
 15484  				}},
 15485  			},
 15486  		},
 15487  		core.Pod{
 15488  			ObjectMeta: metav1.ObjectMeta{
 15489  				Name: "foo",
 15490  			},
 15491  			Spec: core.PodSpec{
 15492  				InitContainers: []core.Container{
 15493  					{
 15494  						Name:          "restartable-init",
 15495  						RestartPolicy: &containerRestartPolicyAlways,
 15496  					},
 15497  				},
 15498  				Containers: []core.Container{
 15499  					{
 15500  						Name: "nginx",
 15501  					},
 15502  				},
 15503  				RestartPolicy: core.RestartPolicyOnFailure,
 15504  			},
 15505  			Status: core.PodStatus{
 15506  				InitContainerStatuses: []core.ContainerStatus{{
 15507  					ContainerID: "docker://numbers",
 15508  					Image:       "alpine",
 15509  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15510  					Name:        "restartable-init",
 15511  					Ready:       false,
 15512  					State: core.ContainerState{
 15513  						Terminated: &core.ContainerStateTerminated{
 15514  							ContainerID: "docker://numbers",
 15515  							Reason:      "Completed",
 15516  						},
 15517  					},
 15518  				}},
 15519  				ContainerStatuses: []core.ContainerStatus{{
 15520  					ContainerID: "docker://numbers",
 15521  					Image:       "nginx:alpine",
 15522  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15523  					Name:        "nginx",
 15524  					Ready:       true,
 15525  					Started:     proto.Bool(true),
 15526  					State: core.ContainerState{
 15527  						Running: &core.ContainerStateRunning{
 15528  							StartedAt: metav1.NewTime(time.Now()),
 15529  						},
 15530  					},
 15531  				}},
 15532  			},
 15533  		},
 15534  		"",
 15535  		"restartable init container can restart if RestartPolicyOnFailure",
 15536  	}, {
 15537  		core.Pod{
 15538  			ObjectMeta: metav1.ObjectMeta{
 15539  				Name: "foo",
 15540  			},
 15541  			Spec: core.PodSpec{
 15542  				InitContainers: []core.Container{
 15543  					{
 15544  						Name:          "restartable-init",
 15545  						RestartPolicy: &containerRestartPolicyAlways,
 15546  					},
 15547  				},
 15548  				Containers: []core.Container{
 15549  					{
 15550  						Name: "nginx",
 15551  					},
 15552  				},
 15553  				RestartPolicy: core.RestartPolicyAlways,
 15554  			},
 15555  			Status: core.PodStatus{
 15556  				InitContainerStatuses: []core.ContainerStatus{{
 15557  					ContainerID: "docker://numbers",
 15558  					Image:       "alpine",
 15559  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15560  					Name:        "restartable-init",
 15561  					Ready:       true,
 15562  					State: core.ContainerState{
 15563  						Running: &core.ContainerStateRunning{
 15564  							StartedAt: metav1.NewTime(time.Now()),
 15565  						},
 15566  					},
 15567  				}},
 15568  				ContainerStatuses: []core.ContainerStatus{{
 15569  					ContainerID: "docker://numbers",
 15570  					Image:       "nginx:alpine",
 15571  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15572  					Name:        "nginx",
 15573  					Ready:       true,
 15574  					Started:     proto.Bool(true),
 15575  					State: core.ContainerState{
 15576  						Running: &core.ContainerStateRunning{
 15577  							StartedAt: metav1.NewTime(time.Now()),
 15578  						},
 15579  					},
 15580  				}},
 15581  			},
 15582  		},
 15583  		core.Pod{
 15584  			ObjectMeta: metav1.ObjectMeta{
 15585  				Name: "foo",
 15586  			},
 15587  			Spec: core.PodSpec{
 15588  				InitContainers: []core.Container{
 15589  					{
 15590  						Name:          "restartable-init",
 15591  						RestartPolicy: &containerRestartPolicyAlways,
 15592  					},
 15593  				},
 15594  				Containers: []core.Container{
 15595  					{
 15596  						Name: "nginx",
 15597  					},
 15598  				},
 15599  				RestartPolicy: core.RestartPolicyAlways,
 15600  			},
 15601  			Status: core.PodStatus{
 15602  				InitContainerStatuses: []core.ContainerStatus{{
 15603  					ContainerID: "docker://numbers",
 15604  					Image:       "alpine",
 15605  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15606  					Name:        "restartable-init",
 15607  					Ready:       false,
 15608  					State: core.ContainerState{
 15609  						Terminated: &core.ContainerStateTerminated{
 15610  							ContainerID: "docker://numbers",
 15611  							Reason:      "Completed",
 15612  						},
 15613  					},
 15614  				}},
 15615  				ContainerStatuses: []core.ContainerStatus{{
 15616  					ContainerID: "docker://numbers",
 15617  					Image:       "nginx:alpine",
 15618  					ImageID:     "docker-pullable://nginx@sha256:d0gf00d",
 15619  					Name:        "nginx",
 15620  					Ready:       true,
 15621  					Started:     proto.Bool(true),
 15622  					State: core.ContainerState{
 15623  						Running: &core.ContainerStateRunning{
 15624  							StartedAt: metav1.NewTime(time.Now()),
 15625  						},
 15626  					},
 15627  				}},
 15628  			},
 15629  		},
 15630  		"",
 15631  		"restartable init container can restart if RestartPolicyAlways",
 15632  	},
 15633  	}
 15634  
 15635  	for _, test := range tests {
 15636  		test.new.ObjectMeta.ResourceVersion = "1"
 15637  		test.old.ObjectMeta.ResourceVersion = "1"
 15638  		errs := ValidatePodStatusUpdate(&test.new, &test.old, PodValidationOptions{})
 15639  		if test.err == "" {
 15640  			if len(errs) != 0 {
 15641  				t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
 15642  			}
 15643  		} else {
 15644  			if len(errs) == 0 {
 15645  				t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
 15646  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
 15647  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
 15648  			}
 15649  		}
 15650  	}
 15651  }
 15652  
 15653  func makeValidService() core.Service {
 15654  	clusterInternalTrafficPolicy := core.ServiceInternalTrafficPolicyCluster
 15655  	return core.Service{
 15656  		ObjectMeta: metav1.ObjectMeta{
 15657  			Name:            "valid",
 15658  			Namespace:       "valid",
 15659  			Labels:          map[string]string{},
 15660  			Annotations:     map[string]string{},
 15661  			ResourceVersion: "1",
 15662  		},
 15663  		Spec: core.ServiceSpec{
 15664  			Selector:              map[string]string{"key": "val"},
 15665  			SessionAffinity:       "None",
 15666  			Type:                  core.ServiceTypeClusterIP,
 15667  			Ports:                 []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt32(8675)}},
 15668  			InternalTrafficPolicy: &clusterInternalTrafficPolicy,
 15669  		},
 15670  	}
 15671  }
 15672  
 15673  func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
 15674  	makePod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 15675  		return &core.Pod{
 15676  			ObjectMeta: metav1.ObjectMeta{
 15677  				Annotations:     map[string]string{},
 15678  				Labels:          map[string]string{},
 15679  				Name:            "pod",
 15680  				Namespace:       "ns",
 15681  				ResourceVersion: "1",
 15682  			},
 15683  			Spec: core.PodSpec{
 15684  				Containers: []core.Container{{
 15685  					Name:                     "cnt",
 15686  					Image:                    "image",
 15687  					ImagePullPolicy:          "IfNotPresent",
 15688  					TerminationMessagePolicy: "File",
 15689  				}},
 15690  				DNSPolicy:           core.DNSClusterFirst,
 15691  				EphemeralContainers: ephemeralContainers,
 15692  				RestartPolicy:       core.RestartPolicyOnFailure,
 15693  			},
 15694  		}
 15695  	}
 15696  
 15697  	// Some tests use Windows host pods as an example of fields that might
 15698  	// conflict between an ephemeral container and the rest of the pod.
 15699  	capabilities.SetForTests(capabilities.Capabilities{
 15700  		AllowPrivileged: true,
 15701  	})
 15702  	makeWindowsHostPod := func(ephemeralContainers []core.EphemeralContainer) *core.Pod {
 15703  		return &core.Pod{
 15704  			ObjectMeta: metav1.ObjectMeta{
 15705  				Annotations:     map[string]string{},
 15706  				Labels:          map[string]string{},
 15707  				Name:            "pod",
 15708  				Namespace:       "ns",
 15709  				ResourceVersion: "1",
 15710  			},
 15711  			Spec: core.PodSpec{
 15712  				Containers: []core.Container{{
 15713  					Name:            "cnt",
 15714  					Image:           "image",
 15715  					ImagePullPolicy: "IfNotPresent",
 15716  					SecurityContext: &core.SecurityContext{
 15717  						WindowsOptions: &core.WindowsSecurityContextOptions{
 15718  							HostProcess: proto.Bool(true),
 15719  						},
 15720  					},
 15721  					TerminationMessagePolicy: "File",
 15722  				}},
 15723  				DNSPolicy:           core.DNSClusterFirst,
 15724  				EphemeralContainers: ephemeralContainers,
 15725  				RestartPolicy:       core.RestartPolicyOnFailure,
 15726  				SecurityContext: &core.PodSecurityContext{
 15727  					HostNetwork: true,
 15728  					WindowsOptions: &core.WindowsSecurityContextOptions{
 15729  						HostProcess: proto.Bool(true),
 15730  					},
 15731  				},
 15732  			},
 15733  		}
 15734  	}
 15735  
 15736  	tests := []struct {
 15737  		name     string
 15738  		new, old *core.Pod
 15739  		err      string
 15740  	}{{
 15741  		"no ephemeral containers",
 15742  		makePod([]core.EphemeralContainer{}),
 15743  		makePod([]core.EphemeralContainer{}),
 15744  		"",
 15745  	}, {
 15746  		"No change in Ephemeral Containers",
 15747  		makePod([]core.EphemeralContainer{{
 15748  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15749  				Name:                     "debugger",
 15750  				Image:                    "busybox",
 15751  				ImagePullPolicy:          "IfNotPresent",
 15752  				TerminationMessagePolicy: "File",
 15753  			},
 15754  		}, {
 15755  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15756  				Name:                     "debugger2",
 15757  				Image:                    "busybox",
 15758  				ImagePullPolicy:          "IfNotPresent",
 15759  				TerminationMessagePolicy: "File",
 15760  			},
 15761  		}}),
 15762  		makePod([]core.EphemeralContainer{{
 15763  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15764  				Name:                     "debugger",
 15765  				Image:                    "busybox",
 15766  				ImagePullPolicy:          "IfNotPresent",
 15767  				TerminationMessagePolicy: "File",
 15768  			},
 15769  		}, {
 15770  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15771  				Name:                     "debugger2",
 15772  				Image:                    "busybox",
 15773  				ImagePullPolicy:          "IfNotPresent",
 15774  				TerminationMessagePolicy: "File",
 15775  			},
 15776  		}}),
 15777  		"",
 15778  	}, {
 15779  		"Ephemeral Container list order changes",
 15780  		makePod([]core.EphemeralContainer{{
 15781  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15782  				Name:                     "debugger",
 15783  				Image:                    "busybox",
 15784  				ImagePullPolicy:          "IfNotPresent",
 15785  				TerminationMessagePolicy: "File",
 15786  			},
 15787  		}, {
 15788  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15789  				Name:                     "debugger2",
 15790  				Image:                    "busybox",
 15791  				ImagePullPolicy:          "IfNotPresent",
 15792  				TerminationMessagePolicy: "File",
 15793  			},
 15794  		}}),
 15795  		makePod([]core.EphemeralContainer{{
 15796  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15797  				Name:                     "debugger2",
 15798  				Image:                    "busybox",
 15799  				ImagePullPolicy:          "IfNotPresent",
 15800  				TerminationMessagePolicy: "File",
 15801  			},
 15802  		}, {
 15803  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15804  				Name:                     "debugger",
 15805  				Image:                    "busybox",
 15806  				ImagePullPolicy:          "IfNotPresent",
 15807  				TerminationMessagePolicy: "File",
 15808  			},
 15809  		}}),
 15810  		"",
 15811  	}, {
 15812  		"Add an Ephemeral Container",
 15813  		makePod([]core.EphemeralContainer{{
 15814  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15815  				Name:                     "debugger",
 15816  				Image:                    "busybox",
 15817  				ImagePullPolicy:          "IfNotPresent",
 15818  				TerminationMessagePolicy: "File",
 15819  			},
 15820  		}}),
 15821  		makePod([]core.EphemeralContainer{}),
 15822  		"",
 15823  	}, {
 15824  		"Add two Ephemeral Containers",
 15825  		makePod([]core.EphemeralContainer{{
 15826  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15827  				Name:                     "debugger1",
 15828  				Image:                    "busybox",
 15829  				ImagePullPolicy:          "IfNotPresent",
 15830  				TerminationMessagePolicy: "File",
 15831  			},
 15832  		}, {
 15833  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15834  				Name:                     "debugger2",
 15835  				Image:                    "busybox",
 15836  				ImagePullPolicy:          "IfNotPresent",
 15837  				TerminationMessagePolicy: "File",
 15838  			},
 15839  		}}),
 15840  		makePod([]core.EphemeralContainer{}),
 15841  		"",
 15842  	}, {
 15843  		"Add to an existing Ephemeral Containers",
 15844  		makePod([]core.EphemeralContainer{{
 15845  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15846  				Name:                     "debugger",
 15847  				Image:                    "busybox",
 15848  				ImagePullPolicy:          "IfNotPresent",
 15849  				TerminationMessagePolicy: "File",
 15850  			},
 15851  		}, {
 15852  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15853  				Name:                     "debugger2",
 15854  				Image:                    "busybox",
 15855  				ImagePullPolicy:          "IfNotPresent",
 15856  				TerminationMessagePolicy: "File",
 15857  			},
 15858  		}}),
 15859  		makePod([]core.EphemeralContainer{{
 15860  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15861  				Name:                     "debugger",
 15862  				Image:                    "busybox",
 15863  				ImagePullPolicy:          "IfNotPresent",
 15864  				TerminationMessagePolicy: "File",
 15865  			},
 15866  		}}),
 15867  		"",
 15868  	}, {
 15869  		"Add to an existing Ephemeral Containers, list order changes",
 15870  		makePod([]core.EphemeralContainer{{
 15871  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15872  				Name:                     "debugger3",
 15873  				Image:                    "busybox",
 15874  				ImagePullPolicy:          "IfNotPresent",
 15875  				TerminationMessagePolicy: "File",
 15876  			},
 15877  		}, {
 15878  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15879  				Name:                     "debugger2",
 15880  				Image:                    "busybox",
 15881  				ImagePullPolicy:          "IfNotPresent",
 15882  				TerminationMessagePolicy: "File",
 15883  			},
 15884  		}, {
 15885  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15886  				Name:                     "debugger",
 15887  				Image:                    "busybox",
 15888  				ImagePullPolicy:          "IfNotPresent",
 15889  				TerminationMessagePolicy: "File",
 15890  			},
 15891  		}}),
 15892  		makePod([]core.EphemeralContainer{{
 15893  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15894  				Name:                     "debugger",
 15895  				Image:                    "busybox",
 15896  				ImagePullPolicy:          "IfNotPresent",
 15897  				TerminationMessagePolicy: "File",
 15898  			},
 15899  		}, {
 15900  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15901  				Name:                     "debugger2",
 15902  				Image:                    "busybox",
 15903  				ImagePullPolicy:          "IfNotPresent",
 15904  				TerminationMessagePolicy: "File",
 15905  			},
 15906  		}}),
 15907  		"",
 15908  	}, {
 15909  		"Remove an Ephemeral Container",
 15910  		makePod([]core.EphemeralContainer{}),
 15911  		makePod([]core.EphemeralContainer{{
 15912  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15913  				Name:                     "debugger",
 15914  				Image:                    "busybox",
 15915  				ImagePullPolicy:          "IfNotPresent",
 15916  				TerminationMessagePolicy: "File",
 15917  			},
 15918  		}}),
 15919  		"may not be removed",
 15920  	}, {
 15921  		"Replace an Ephemeral Container",
 15922  		makePod([]core.EphemeralContainer{{
 15923  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15924  				Name:                     "firstone",
 15925  				Image:                    "busybox",
 15926  				ImagePullPolicy:          "IfNotPresent",
 15927  				TerminationMessagePolicy: "File",
 15928  			},
 15929  		}}),
 15930  		makePod([]core.EphemeralContainer{{
 15931  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15932  				Name:                     "thentheother",
 15933  				Image:                    "busybox",
 15934  				ImagePullPolicy:          "IfNotPresent",
 15935  				TerminationMessagePolicy: "File",
 15936  			},
 15937  		}}),
 15938  		"may not be removed",
 15939  	}, {
 15940  		"Change an Ephemeral Containers",
 15941  		makePod([]core.EphemeralContainer{{
 15942  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15943  				Name:                     "debugger1",
 15944  				Image:                    "busybox",
 15945  				ImagePullPolicy:          "IfNotPresent",
 15946  				TerminationMessagePolicy: "File",
 15947  			},
 15948  		}, {
 15949  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15950  				Name:                     "debugger2",
 15951  				Image:                    "busybox",
 15952  				ImagePullPolicy:          "IfNotPresent",
 15953  				TerminationMessagePolicy: "File",
 15954  			},
 15955  		}}),
 15956  		makePod([]core.EphemeralContainer{{
 15957  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15958  				Name:                     "debugger1",
 15959  				Image:                    "debian",
 15960  				ImagePullPolicy:          "IfNotPresent",
 15961  				TerminationMessagePolicy: "File",
 15962  			},
 15963  		}, {
 15964  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15965  				Name:                     "debugger2",
 15966  				Image:                    "busybox",
 15967  				ImagePullPolicy:          "IfNotPresent",
 15968  				TerminationMessagePolicy: "File",
 15969  			},
 15970  		}}),
 15971  		"may not be changed",
 15972  	}, {
 15973  		"Ephemeral container with potential conflict with regular containers, but conflict not present",
 15974  		makeWindowsHostPod([]core.EphemeralContainer{{
 15975  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15976  				Name:            "debugger1",
 15977  				Image:           "image",
 15978  				ImagePullPolicy: "IfNotPresent",
 15979  				SecurityContext: &core.SecurityContext{
 15980  					WindowsOptions: &core.WindowsSecurityContextOptions{
 15981  						HostProcess: proto.Bool(true),
 15982  					},
 15983  				},
 15984  				TerminationMessagePolicy: "File",
 15985  			},
 15986  		}}),
 15987  		makeWindowsHostPod(nil),
 15988  		"",
 15989  	}, {
 15990  		"Ephemeral container with potential conflict with regular containers, and conflict is present",
 15991  		makeWindowsHostPod([]core.EphemeralContainer{{
 15992  			EphemeralContainerCommon: core.EphemeralContainerCommon{
 15993  				Name:            "debugger1",
 15994  				Image:           "image",
 15995  				ImagePullPolicy: "IfNotPresent",
 15996  				SecurityContext: &core.SecurityContext{
 15997  					WindowsOptions: &core.WindowsSecurityContextOptions{
 15998  						HostProcess: proto.Bool(false),
 15999  					},
 16000  				},
 16001  				TerminationMessagePolicy: "File",
 16002  			},
 16003  		}}),
 16004  		makeWindowsHostPod(nil),
 16005  		"spec.ephemeralContainers[0].securityContext.windowsOptions.hostProcess: Invalid value: false: pod hostProcess value must be identical",
 16006  	}, {
 16007  		"Add ephemeral container to static pod",
 16008  		func() *core.Pod {
 16009  			p := makePod(nil)
 16010  			p.Spec.NodeName = "some-name"
 16011  			p.ObjectMeta.Annotations = map[string]string{
 16012  				core.MirrorPodAnnotationKey: "foo",
 16013  			}
 16014  			p.Spec.EphemeralContainers = []core.EphemeralContainer{{
 16015  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 16016  					Name:                     "debugger1",
 16017  					Image:                    "debian",
 16018  					ImagePullPolicy:          "IfNotPresent",
 16019  					TerminationMessagePolicy: "File",
 16020  				},
 16021  			}}
 16022  			return p
 16023  		}(),
 16024  		func() *core.Pod {
 16025  			p := makePod(nil)
 16026  			p.Spec.NodeName = "some-name"
 16027  			p.ObjectMeta.Annotations = map[string]string{
 16028  				core.MirrorPodAnnotationKey: "foo",
 16029  			}
 16030  			return p
 16031  		}(),
 16032  		"Forbidden: static pods do not support ephemeral containers",
 16033  	},
 16034  	}
 16035  
 16036  	for _, tc := range tests {
 16037  		errs := ValidatePodEphemeralContainersUpdate(tc.new, tc.old, PodValidationOptions{})
 16038  		if tc.err == "" {
 16039  			if len(errs) != 0 {
 16040  				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))
 16041  			}
 16042  		} else {
 16043  			if len(errs) == 0 {
 16044  				t.Errorf("unexpected valid for test: %s\nLocal diff of test objects (-old +new):\n%s", tc.name, cmp.Diff(tc.old, tc.new))
 16045  			} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, tc.err) {
 16046  				t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", tc.name, tc.err, actualErr)
 16047  			}
 16048  		}
 16049  	}
 16050  }
 16051  
 16052  func TestValidateServiceCreate(t *testing.T) {
 16053  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 16054  	singleStack := core.IPFamilyPolicySingleStack
 16055  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 16056  
 16057  	testCases := []struct {
 16058  		name         string
 16059  		tweakSvc     func(svc *core.Service) // given a basic valid service, each test case can customize it
 16060  		numErrs      int
 16061  		featureGates []featuregate.Feature
 16062  	}{{
 16063  		name: "missing namespace",
 16064  		tweakSvc: func(s *core.Service) {
 16065  			s.Namespace = ""
 16066  		},
 16067  		numErrs: 1,
 16068  	}, {
 16069  		name: "invalid namespace",
 16070  		tweakSvc: func(s *core.Service) {
 16071  			s.Namespace = "-123"
 16072  		},
 16073  		numErrs: 1,
 16074  	}, {
 16075  		name: "missing name",
 16076  		tweakSvc: func(s *core.Service) {
 16077  			s.Name = ""
 16078  		},
 16079  		numErrs: 1,
 16080  	}, {
 16081  		name: "invalid name",
 16082  		tweakSvc: func(s *core.Service) {
 16083  			s.Name = "-123"
 16084  		},
 16085  		numErrs: 1,
 16086  	}, {
 16087  		name: "too long name",
 16088  		tweakSvc: func(s *core.Service) {
 16089  			s.Name = strings.Repeat("a", 64)
 16090  		},
 16091  		numErrs: 1,
 16092  	}, {
 16093  		name: "invalid generateName",
 16094  		tweakSvc: func(s *core.Service) {
 16095  			s.GenerateName = "-123"
 16096  		},
 16097  		numErrs: 1,
 16098  	}, {
 16099  		name: "too long generateName",
 16100  		tweakSvc: func(s *core.Service) {
 16101  			s.GenerateName = strings.Repeat("a", 64)
 16102  		},
 16103  		numErrs: 1,
 16104  	}, {
 16105  		name: "invalid label",
 16106  		tweakSvc: func(s *core.Service) {
 16107  			s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar"
 16108  		},
 16109  		numErrs: 1,
 16110  	}, {
 16111  		name: "invalid annotation",
 16112  		tweakSvc: func(s *core.Service) {
 16113  			s.Annotations["NoSpecialCharsLike=Equals"] = "bar"
 16114  		},
 16115  		numErrs: 1,
 16116  	}, {
 16117  		name: "nil selector",
 16118  		tweakSvc: func(s *core.Service) {
 16119  			s.Spec.Selector = nil
 16120  		},
 16121  		numErrs: 0,
 16122  	}, {
 16123  		name: "invalid selector",
 16124  		tweakSvc: func(s *core.Service) {
 16125  			s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar"
 16126  		},
 16127  		numErrs: 1,
 16128  	}, {
 16129  		name: "missing session affinity",
 16130  		tweakSvc: func(s *core.Service) {
 16131  			s.Spec.SessionAffinity = ""
 16132  		},
 16133  		numErrs: 1,
 16134  	}, {
 16135  		name: "missing type",
 16136  		tweakSvc: func(s *core.Service) {
 16137  			s.Spec.Type = ""
 16138  		},
 16139  		numErrs: 1,
 16140  	}, {
 16141  		name: "missing ports",
 16142  		tweakSvc: func(s *core.Service) {
 16143  			s.Spec.Ports = nil
 16144  		},
 16145  		numErrs: 1,
 16146  	}, {
 16147  		name: "missing ports but headless",
 16148  		tweakSvc: func(s *core.Service) {
 16149  			s.Spec.Ports = nil
 16150  			s.Spec.ClusterIP = core.ClusterIPNone
 16151  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16152  		},
 16153  		numErrs: 0,
 16154  	}, {
 16155  		name: "empty port[0] name",
 16156  		tweakSvc: func(s *core.Service) {
 16157  			s.Spec.Ports[0].Name = ""
 16158  		},
 16159  		numErrs: 0,
 16160  	}, {
 16161  		name: "empty port[1] name",
 16162  		tweakSvc: func(s *core.Service) {
 16163  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 16164  		},
 16165  		numErrs: 1,
 16166  	}, {
 16167  		name: "empty multi-port port[0] name",
 16168  		tweakSvc: func(s *core.Service) {
 16169  			s.Spec.Ports[0].Name = ""
 16170  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt32(12345)})
 16171  		},
 16172  		numErrs: 1,
 16173  	}, {
 16174  		name: "invalid port name",
 16175  		tweakSvc: func(s *core.Service) {
 16176  			s.Spec.Ports[0].Name = "INVALID"
 16177  		},
 16178  		numErrs: 1,
 16179  	}, {
 16180  		name: "missing protocol",
 16181  		tweakSvc: func(s *core.Service) {
 16182  			s.Spec.Ports[0].Protocol = ""
 16183  		},
 16184  		numErrs: 1,
 16185  	}, {
 16186  		name: "invalid protocol",
 16187  		tweakSvc: func(s *core.Service) {
 16188  			s.Spec.Ports[0].Protocol = "INVALID"
 16189  		},
 16190  		numErrs: 1,
 16191  	}, {
 16192  		name: "invalid cluster ip",
 16193  		tweakSvc: func(s *core.Service) {
 16194  			s.Spec.ClusterIP = "invalid"
 16195  			s.Spec.ClusterIPs = []string{"invalid"}
 16196  		},
 16197  		numErrs: 1,
 16198  	}, {
 16199  		name: "missing port",
 16200  		tweakSvc: func(s *core.Service) {
 16201  			s.Spec.Ports[0].Port = 0
 16202  		},
 16203  		numErrs: 1,
 16204  	}, {
 16205  		name: "invalid port",
 16206  		tweakSvc: func(s *core.Service) {
 16207  			s.Spec.Ports[0].Port = 65536
 16208  		},
 16209  		numErrs: 1,
 16210  	}, {
 16211  		name: "invalid TargetPort int",
 16212  		tweakSvc: func(s *core.Service) {
 16213  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(65536)
 16214  		},
 16215  		numErrs: 1,
 16216  	}, {
 16217  		name: "valid port headless",
 16218  		tweakSvc: func(s *core.Service) {
 16219  			s.Spec.Ports[0].Port = 11722
 16220  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11722)
 16221  			s.Spec.ClusterIP = core.ClusterIPNone
 16222  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16223  		},
 16224  		numErrs: 0,
 16225  	}, {
 16226  		name: "invalid port headless 1",
 16227  		tweakSvc: func(s *core.Service) {
 16228  			s.Spec.Ports[0].Port = 11722
 16229  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(11721)
 16230  			s.Spec.ClusterIP = core.ClusterIPNone
 16231  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16232  		},
 16233  		// in the v1 API, targetPorts on headless services were tolerated.
 16234  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 16235  		// numErrs: 1,
 16236  		numErrs: 0,
 16237  	}, {
 16238  		name: "invalid port headless 2",
 16239  		tweakSvc: func(s *core.Service) {
 16240  			s.Spec.Ports[0].Port = 11722
 16241  			s.Spec.Ports[0].TargetPort = intstr.FromString("target")
 16242  			s.Spec.ClusterIP = core.ClusterIPNone
 16243  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16244  		},
 16245  		// in the v1 API, targetPorts on headless services were tolerated.
 16246  		// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
 16247  		// numErrs: 1,
 16248  		numErrs: 0,
 16249  	}, {
 16250  		name: "invalid publicIPs localhost",
 16251  		tweakSvc: func(s *core.Service) {
 16252  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16253  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 16254  		},
 16255  		numErrs: 1,
 16256  	}, {
 16257  		name: "invalid publicIPs unspecified",
 16258  		tweakSvc: func(s *core.Service) {
 16259  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16260  			s.Spec.ExternalIPs = []string{"0.0.0.0"}
 16261  		},
 16262  		numErrs: 1,
 16263  	}, {
 16264  		name: "invalid publicIPs loopback",
 16265  		tweakSvc: func(s *core.Service) {
 16266  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16267  			s.Spec.ExternalIPs = []string{"127.0.0.1"}
 16268  		},
 16269  		numErrs: 1,
 16270  	}, {
 16271  		name: "invalid publicIPs host",
 16272  		tweakSvc: func(s *core.Service) {
 16273  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16274  			s.Spec.ExternalIPs = []string{"myhost.mydomain"}
 16275  		},
 16276  		numErrs: 1,
 16277  	}, {
 16278  		name: "valid publicIPs",
 16279  		tweakSvc: func(s *core.Service) {
 16280  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16281  			s.Spec.ExternalIPs = []string{"1.2.3.4"}
 16282  		},
 16283  		numErrs: 0,
 16284  	}, {
 16285  		name: "dup port name",
 16286  		tweakSvc: func(s *core.Service) {
 16287  			s.Spec.Ports[0].Name = "p"
 16288  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16289  		},
 16290  		numErrs: 1,
 16291  	}, {
 16292  		name: "valid load balancer protocol UDP 1",
 16293  		tweakSvc: func(s *core.Service) {
 16294  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16295  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16296  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16297  			s.Spec.Ports[0].Protocol = "UDP"
 16298  		},
 16299  		numErrs: 0,
 16300  	}, {
 16301  		name: "valid load balancer protocol UDP 2",
 16302  		tweakSvc: func(s *core.Service) {
 16303  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16304  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16305  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16306  			s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)}
 16307  		},
 16308  		numErrs: 0,
 16309  	}, {
 16310  		name: "load balancer with mix protocol",
 16311  		tweakSvc: func(s *core.Service) {
 16312  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16313  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16314  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16315  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(12345)})
 16316  		},
 16317  		numErrs: 0,
 16318  	}, {
 16319  		name: "valid 1",
 16320  		tweakSvc: func(s *core.Service) {
 16321  			// do nothing
 16322  		},
 16323  		numErrs: 0,
 16324  	}, {
 16325  		name: "valid 2",
 16326  		tweakSvc: func(s *core.Service) {
 16327  			s.Spec.Ports[0].Protocol = "UDP"
 16328  			s.Spec.Ports[0].TargetPort = intstr.FromInt32(12345)
 16329  		},
 16330  		numErrs: 0,
 16331  	}, {
 16332  		name: "valid 3",
 16333  		tweakSvc: func(s *core.Service) {
 16334  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 16335  		},
 16336  		numErrs: 0,
 16337  	}, {
 16338  		name: "valid cluster ip - none ",
 16339  		tweakSvc: func(s *core.Service) {
 16340  			s.Spec.ClusterIP = core.ClusterIPNone
 16341  			s.Spec.ClusterIPs = []string{core.ClusterIPNone}
 16342  		},
 16343  		numErrs: 0,
 16344  	}, {
 16345  		name: "valid cluster ip - empty",
 16346  		tweakSvc: func(s *core.Service) {
 16347  			s.Spec.ClusterIPs = nil
 16348  			s.Spec.Ports[0].TargetPort = intstr.FromString("http")
 16349  		},
 16350  		numErrs: 0,
 16351  	}, {
 16352  		name: "valid type - clusterIP",
 16353  		tweakSvc: func(s *core.Service) {
 16354  			s.Spec.Type = core.ServiceTypeClusterIP
 16355  		},
 16356  		numErrs: 0,
 16357  	}, {
 16358  		name: "valid type - loadbalancer",
 16359  		tweakSvc: func(s *core.Service) {
 16360  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16361  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16362  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16363  		},
 16364  		numErrs: 0,
 16365  	}, {
 16366  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=false",
 16367  		tweakSvc: func(s *core.Service) {
 16368  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16369  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16370  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 16371  		},
 16372  		numErrs: 0,
 16373  	}, {
 16374  		name: "invalid type - missing AllocateLoadBalancerNodePorts for loadbalancer type",
 16375  		tweakSvc: func(s *core.Service) {
 16376  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16377  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16378  		},
 16379  		numErrs: 1,
 16380  	}, {
 16381  		name: "valid type loadbalancer 2 ports",
 16382  		tweakSvc: func(s *core.Service) {
 16383  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16384  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16385  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16386  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16387  		},
 16388  		numErrs: 0,
 16389  	}, {
 16390  		name: "valid external load balancer 2 ports",
 16391  		tweakSvc: func(s *core.Service) {
 16392  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16393  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16394  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16395  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16396  		},
 16397  		numErrs: 0,
 16398  	}, {
 16399  		name: "duplicate nodeports",
 16400  		tweakSvc: func(s *core.Service) {
 16401  			s.Spec.Type = core.ServiceTypeNodePort
 16402  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16403  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 16404  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 16405  		},
 16406  		numErrs: 1,
 16407  	}, {
 16408  		name: "duplicate nodeports (different protocols)",
 16409  		tweakSvc: func(s *core.Service) {
 16410  			s.Spec.Type = core.ServiceTypeNodePort
 16411  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16412  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 16413  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 16414  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt32(3)})
 16415  		},
 16416  		numErrs: 0,
 16417  	}, {
 16418  		name: "invalid duplicate ports (with same protocol)",
 16419  		tweakSvc: func(s *core.Service) {
 16420  			s.Spec.Type = core.ServiceTypeClusterIP
 16421  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 16422  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(80)})
 16423  		},
 16424  		numErrs: 1,
 16425  	}, {
 16426  		name: "valid duplicate ports (with different protocols)",
 16427  		tweakSvc: func(s *core.Service) {
 16428  			s.Spec.Type = core.ServiceTypeClusterIP
 16429  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(8080)})
 16430  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt32(80)})
 16431  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt32(8088)})
 16432  		},
 16433  		numErrs: 0,
 16434  	}, {
 16435  		name: "valid type - cluster",
 16436  		tweakSvc: func(s *core.Service) {
 16437  			s.Spec.Type = core.ServiceTypeClusterIP
 16438  		},
 16439  		numErrs: 0,
 16440  	}, {
 16441  		name: "valid type - nodeport",
 16442  		tweakSvc: func(s *core.Service) {
 16443  			s.Spec.Type = core.ServiceTypeNodePort
 16444  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16445  		},
 16446  		numErrs: 0,
 16447  	}, {
 16448  		name: "valid type - loadbalancer with allocateLoadBalancerNodePorts=true",
 16449  		tweakSvc: func(s *core.Service) {
 16450  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16451  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16452  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16453  		},
 16454  		numErrs: 0,
 16455  	}, {
 16456  		name: "valid type loadbalancer 2 ports",
 16457  		tweakSvc: func(s *core.Service) {
 16458  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16459  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16460  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16461  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16462  		},
 16463  		numErrs: 0,
 16464  	}, {
 16465  		name: "valid type loadbalancer with NodePort",
 16466  		tweakSvc: func(s *core.Service) {
 16467  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16468  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16469  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16470  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 16471  		},
 16472  		numErrs: 0,
 16473  	}, {
 16474  		name: "valid type=NodePort service with NodePort",
 16475  		tweakSvc: func(s *core.Service) {
 16476  			s.Spec.Type = core.ServiceTypeNodePort
 16477  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16478  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 16479  		},
 16480  		numErrs: 0,
 16481  	}, {
 16482  		name: "valid type=NodePort service without NodePort",
 16483  		tweakSvc: func(s *core.Service) {
 16484  			s.Spec.Type = core.ServiceTypeNodePort
 16485  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16486  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16487  		},
 16488  		numErrs: 0,
 16489  	}, {
 16490  		name: "valid cluster service without NodePort",
 16491  		tweakSvc: func(s *core.Service) {
 16492  			s.Spec.Type = core.ServiceTypeClusterIP
 16493  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16494  		},
 16495  		numErrs: 0,
 16496  	}, {
 16497  		name: "invalid cluster service with NodePort",
 16498  		tweakSvc: func(s *core.Service) {
 16499  			s.Spec.Type = core.ServiceTypeClusterIP
 16500  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt32(12345)})
 16501  		},
 16502  		numErrs: 1,
 16503  	}, {
 16504  		name: "invalid public service with duplicate NodePort",
 16505  		tweakSvc: func(s *core.Service) {
 16506  			s.Spec.Type = core.ServiceTypeNodePort
 16507  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16508  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 16509  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(2)})
 16510  		},
 16511  		numErrs: 1,
 16512  	}, {
 16513  		name: "valid type=LoadBalancer",
 16514  		tweakSvc: func(s *core.Service) {
 16515  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16516  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16517  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16518  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16519  		},
 16520  		numErrs: 0,
 16521  	}, {
 16522  		// For now we open firewalls, and its insecure if we open 10250, remove this
 16523  		// when we have better protections in place.
 16524  		name: "invalid port type=LoadBalancer",
 16525  		tweakSvc: func(s *core.Service) {
 16526  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16527  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16528  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16529  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt32(12345)})
 16530  		},
 16531  		numErrs: 1,
 16532  	}, {
 16533  		name: "valid LoadBalancer source range annotation",
 16534  		tweakSvc: func(s *core.Service) {
 16535  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16536  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16537  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16538  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8,  5.6.7.8/16"
 16539  		},
 16540  		numErrs: 0,
 16541  	}, {
 16542  		name: "empty LoadBalancer source range annotation",
 16543  		tweakSvc: func(s *core.Service) {
 16544  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16545  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16546  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16547  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
 16548  		},
 16549  		numErrs: 0,
 16550  	}, {
 16551  		name: "invalid LoadBalancer source range annotation (hostname)",
 16552  		tweakSvc: func(s *core.Service) {
 16553  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
 16554  		},
 16555  		numErrs: 2,
 16556  	}, {
 16557  		name: "invalid LoadBalancer source range annotation (invalid CIDR)",
 16558  		tweakSvc: func(s *core.Service) {
 16559  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16560  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16561  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16562  			s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33"
 16563  		},
 16564  		numErrs: 1,
 16565  	}, {
 16566  		name: "invalid source range for non LoadBalancer type service",
 16567  		tweakSvc: func(s *core.Service) {
 16568  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"}
 16569  		},
 16570  		numErrs: 1,
 16571  	}, {
 16572  		name: "valid LoadBalancer source range",
 16573  		tweakSvc: func(s *core.Service) {
 16574  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16575  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16576  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16577  			s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"}
 16578  		},
 16579  		numErrs: 0,
 16580  	}, {
 16581  		name: "empty LoadBalancer source range",
 16582  		tweakSvc: func(s *core.Service) {
 16583  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16584  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16585  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16586  			s.Spec.LoadBalancerSourceRanges = []string{"   "}
 16587  		},
 16588  		numErrs: 1,
 16589  	}, {
 16590  		name: "invalid LoadBalancer source range",
 16591  		tweakSvc: func(s *core.Service) {
 16592  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16593  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16594  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16595  			s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"}
 16596  		},
 16597  		numErrs: 1,
 16598  	}, {
 16599  		name: "valid ExternalName",
 16600  		tweakSvc: func(s *core.Service) {
 16601  			s.Spec.Type = core.ServiceTypeExternalName
 16602  			s.Spec.ExternalName = "foo.bar.example.com"
 16603  		},
 16604  		numErrs: 0,
 16605  	}, {
 16606  		name: "valid ExternalName (trailing dot)",
 16607  		tweakSvc: func(s *core.Service) {
 16608  			s.Spec.Type = core.ServiceTypeExternalName
 16609  			s.Spec.ExternalName = "foo.bar.example.com."
 16610  		},
 16611  		numErrs: 0,
 16612  	}, {
 16613  		name: "invalid ExternalName clusterIP (valid IP)",
 16614  		tweakSvc: func(s *core.Service) {
 16615  			s.Spec.Type = core.ServiceTypeExternalName
 16616  			s.Spec.ClusterIP = "1.2.3.4"
 16617  			s.Spec.ClusterIPs = []string{"1.2.3.4"}
 16618  			s.Spec.ExternalName = "foo.bar.example.com"
 16619  		},
 16620  		numErrs: 1,
 16621  	}, {
 16622  		name: "invalid ExternalName clusterIP (None)",
 16623  		tweakSvc: func(s *core.Service) {
 16624  			s.Spec.Type = core.ServiceTypeExternalName
 16625  			s.Spec.ClusterIP = "None"
 16626  			s.Spec.ClusterIPs = []string{"None"}
 16627  			s.Spec.ExternalName = "foo.bar.example.com"
 16628  		},
 16629  		numErrs: 1,
 16630  	}, {
 16631  		name: "invalid ExternalName (not a DNS name)",
 16632  		tweakSvc: func(s *core.Service) {
 16633  			s.Spec.Type = core.ServiceTypeExternalName
 16634  			s.Spec.ExternalName = "-123"
 16635  		},
 16636  		numErrs: 1,
 16637  	}, {
 16638  		name: "LoadBalancer type cannot have None ClusterIP",
 16639  		tweakSvc: func(s *core.Service) {
 16640  			s.Spec.ClusterIP = "None"
 16641  			s.Spec.ClusterIPs = []string{"None"}
 16642  			s.Spec.Type = core.ServiceTypeLoadBalancer
 16643  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16644  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16645  		},
 16646  		numErrs: 1,
 16647  	}, {
 16648  		name: "invalid node port with clusterIP None",
 16649  		tweakSvc: func(s *core.Service) {
 16650  			s.Spec.Type = core.ServiceTypeNodePort
 16651  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16652  			s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 16653  			s.Spec.ClusterIP = "None"
 16654  			s.Spec.ClusterIPs = []string{"None"}
 16655  		},
 16656  		numErrs: 1,
 16657  	},
 16658  		// ESIPP section begins.
 16659  		{
 16660  			name: "invalid externalTraffic field",
 16661  			tweakSvc: func(s *core.Service) {
 16662  				s.Spec.Type = core.ServiceTypeLoadBalancer
 16663  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16664  				s.Spec.ExternalTrafficPolicy = "invalid"
 16665  			},
 16666  			numErrs: 1,
 16667  		}, {
 16668  			name: "nil internalTraffic field when feature gate is on",
 16669  			tweakSvc: func(s *core.Service) {
 16670  				s.Spec.InternalTrafficPolicy = nil
 16671  			},
 16672  			numErrs: 1,
 16673  		}, {
 16674  			name: "internalTrafficPolicy field nil when type is ExternalName",
 16675  			tweakSvc: func(s *core.Service) {
 16676  				s.Spec.InternalTrafficPolicy = nil
 16677  				s.Spec.Type = core.ServiceTypeExternalName
 16678  				s.Spec.ExternalName = "foo.bar.com"
 16679  			},
 16680  			numErrs: 0,
 16681  		}, {
 16682  			// Typically this should fail validation, but in v1.22 we have existing clusters
 16683  			// that may have allowed internalTrafficPolicy when Type=ExternalName.
 16684  			// This test case ensures we don't break compatibility for internalTrafficPolicy
 16685  			// when Type=ExternalName
 16686  			name: "internalTrafficPolicy field is set when type is ExternalName",
 16687  			tweakSvc: func(s *core.Service) {
 16688  				cluster := core.ServiceInternalTrafficPolicyCluster
 16689  				s.Spec.InternalTrafficPolicy = &cluster
 16690  				s.Spec.Type = core.ServiceTypeExternalName
 16691  				s.Spec.ExternalName = "foo.bar.com"
 16692  			},
 16693  			numErrs: 0,
 16694  		}, {
 16695  			name: "invalid internalTraffic field",
 16696  			tweakSvc: func(s *core.Service) {
 16697  				invalid := core.ServiceInternalTrafficPolicy("invalid")
 16698  				s.Spec.InternalTrafficPolicy = &invalid
 16699  			},
 16700  			numErrs: 1,
 16701  		}, {
 16702  			name: "internalTrafficPolicy field set to Cluster",
 16703  			tweakSvc: func(s *core.Service) {
 16704  				cluster := core.ServiceInternalTrafficPolicyCluster
 16705  				s.Spec.InternalTrafficPolicy = &cluster
 16706  			},
 16707  			numErrs: 0,
 16708  		}, {
 16709  			name: "internalTrafficPolicy field set to Local",
 16710  			tweakSvc: func(s *core.Service) {
 16711  				local := core.ServiceInternalTrafficPolicyLocal
 16712  				s.Spec.InternalTrafficPolicy = &local
 16713  			},
 16714  			numErrs: 0,
 16715  		}, {
 16716  			name: "negative healthCheckNodePort field",
 16717  			tweakSvc: func(s *core.Service) {
 16718  				s.Spec.Type = core.ServiceTypeLoadBalancer
 16719  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16720  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 16721  				s.Spec.HealthCheckNodePort = -1
 16722  			},
 16723  			numErrs: 1,
 16724  		}, {
 16725  			name: "negative healthCheckNodePort field",
 16726  			tweakSvc: func(s *core.Service) {
 16727  				s.Spec.Type = core.ServiceTypeLoadBalancer
 16728  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16729  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 16730  				s.Spec.HealthCheckNodePort = 31100
 16731  			},
 16732  			numErrs: 0,
 16733  		},
 16734  		// ESIPP section ends.
 16735  		{
 16736  			name: "invalid timeoutSeconds field",
 16737  			tweakSvc: func(s *core.Service) {
 16738  				s.Spec.Type = core.ServiceTypeClusterIP
 16739  				s.Spec.SessionAffinity = core.ServiceAffinityClientIP
 16740  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 16741  					ClientIP: &core.ClientIPConfig{
 16742  						TimeoutSeconds: utilpointer.Int32(-1),
 16743  					},
 16744  				}
 16745  			},
 16746  			numErrs: 1,
 16747  		}, {
 16748  			name: "sessionAffinityConfig can't be set when session affinity is None",
 16749  			tweakSvc: func(s *core.Service) {
 16750  				s.Spec.Type = core.ServiceTypeLoadBalancer
 16751  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 16752  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 16753  				s.Spec.SessionAffinity = core.ServiceAffinityNone
 16754  				s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 16755  					ClientIP: &core.ClientIPConfig{
 16756  						TimeoutSeconds: utilpointer.Int32(90),
 16757  					},
 16758  				}
 16759  			},
 16760  			numErrs: 1,
 16761  		},
 16762  		/* ip families validation */
 16763  		{
 16764  			name: "invalid, service with invalid ipFamilies",
 16765  			tweakSvc: func(s *core.Service) {
 16766  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 16767  				s.Spec.IPFamilies = []core.IPFamily{invalidServiceIPFamily}
 16768  			},
 16769  			numErrs: 1,
 16770  		}, {
 16771  			name: "invalid, service with invalid ipFamilies (2nd)",
 16772  			tweakSvc: func(s *core.Service) {
 16773  				invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
 16774  				s.Spec.IPFamilyPolicy = &requireDualStack
 16775  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, invalidServiceIPFamily}
 16776  			},
 16777  			numErrs: 1,
 16778  		}, {
 16779  			name: "IPFamilyPolicy(singleStack) is set for two families",
 16780  			tweakSvc: func(s *core.Service) {
 16781  				s.Spec.IPFamilyPolicy = &singleStack
 16782  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16783  			},
 16784  			numErrs: 0, // this validated in alloc code.
 16785  		}, {
 16786  			name: "valid, IPFamilyPolicy(preferDualStack) is set for two families (note: alloc sets families)",
 16787  			tweakSvc: func(s *core.Service) {
 16788  				s.Spec.IPFamilyPolicy = &preferDualStack
 16789  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16790  			},
 16791  			numErrs: 0,
 16792  		},
 16793  
 16794  		{
 16795  			name: "invalid, service with 2+ ipFamilies",
 16796  			tweakSvc: func(s *core.Service) {
 16797  				s.Spec.IPFamilyPolicy = &requireDualStack
 16798  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol, core.IPv4Protocol}
 16799  			},
 16800  			numErrs: 1,
 16801  		}, {
 16802  			name: "invalid, service with same ip families",
 16803  			tweakSvc: func(s *core.Service) {
 16804  				s.Spec.IPFamilyPolicy = &requireDualStack
 16805  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv6Protocol}
 16806  			},
 16807  			numErrs: 1,
 16808  		}, {
 16809  			name: "valid, nil service ipFamilies",
 16810  			tweakSvc: func(s *core.Service) {
 16811  				s.Spec.IPFamilies = nil
 16812  			},
 16813  			numErrs: 0,
 16814  		}, {
 16815  			name: "valid, service with valid ipFamilies (v4)",
 16816  			tweakSvc: func(s *core.Service) {
 16817  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 16818  			},
 16819  			numErrs: 0,
 16820  		}, {
 16821  			name: "valid, service with valid ipFamilies (v6)",
 16822  			tweakSvc: func(s *core.Service) {
 16823  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 16824  			},
 16825  			numErrs: 0,
 16826  		}, {
 16827  			name: "valid, service with valid ipFamilies(v4,v6)",
 16828  			tweakSvc: func(s *core.Service) {
 16829  				s.Spec.IPFamilyPolicy = &requireDualStack
 16830  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16831  			},
 16832  			numErrs: 0,
 16833  		}, {
 16834  			name: "valid, service with valid ipFamilies(v6,v4)",
 16835  			tweakSvc: func(s *core.Service) {
 16836  				s.Spec.IPFamilyPolicy = &requireDualStack
 16837  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 16838  			},
 16839  			numErrs: 0,
 16840  		}, {
 16841  			name: "valid, service preferred dual stack with single family",
 16842  			tweakSvc: func(s *core.Service) {
 16843  				s.Spec.IPFamilyPolicy = &preferDualStack
 16844  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 16845  			},
 16846  			numErrs: 0,
 16847  		},
 16848  		/* cluster IPs. some tests are redundant */
 16849  		{
 16850  			name: "invalid, garbage single ip",
 16851  			tweakSvc: func(s *core.Service) {
 16852  				s.Spec.ClusterIP = "garbage-ip"
 16853  				s.Spec.ClusterIPs = []string{"garbage-ip"}
 16854  			},
 16855  			numErrs: 1,
 16856  		}, {
 16857  			name: "invalid, garbage ips",
 16858  			tweakSvc: func(s *core.Service) {
 16859  				s.Spec.IPFamilyPolicy = &requireDualStack
 16860  				s.Spec.ClusterIP = "garbage-ip"
 16861  				s.Spec.ClusterIPs = []string{"garbage-ip", "garbage-second-ip"}
 16862  			},
 16863  			numErrs: 2,
 16864  		}, {
 16865  			name: "invalid, garbage first ip",
 16866  			tweakSvc: func(s *core.Service) {
 16867  				s.Spec.IPFamilyPolicy = &requireDualStack
 16868  				s.Spec.ClusterIP = "garbage-ip"
 16869  				s.Spec.ClusterIPs = []string{"garbage-ip", "2001::1"}
 16870  			},
 16871  			numErrs: 1,
 16872  		}, {
 16873  			name: "invalid, garbage second ip",
 16874  			tweakSvc: func(s *core.Service) {
 16875  				s.Spec.IPFamilyPolicy = &requireDualStack
 16876  				s.Spec.ClusterIP = "2001::1"
 16877  				s.Spec.ClusterIPs = []string{"2001::1", "garbage-ip"}
 16878  			},
 16879  			numErrs: 1,
 16880  		}, {
 16881  			name: "invalid, NONE + IP",
 16882  			tweakSvc: func(s *core.Service) {
 16883  				s.Spec.IPFamilyPolicy = &requireDualStack
 16884  				s.Spec.ClusterIP = "None"
 16885  				s.Spec.ClusterIPs = []string{"None", "2001::1"}
 16886  			},
 16887  			numErrs: 1,
 16888  		}, {
 16889  			name: "invalid, IP + NONE",
 16890  			tweakSvc: func(s *core.Service) {
 16891  				s.Spec.IPFamilyPolicy = &requireDualStack
 16892  				s.Spec.ClusterIP = "2001::1"
 16893  				s.Spec.ClusterIPs = []string{"2001::1", "None"}
 16894  			},
 16895  			numErrs: 1,
 16896  		}, {
 16897  			name: "invalid, EMPTY STRING + IP",
 16898  			tweakSvc: func(s *core.Service) {
 16899  				s.Spec.IPFamilyPolicy = &requireDualStack
 16900  				s.Spec.ClusterIP = ""
 16901  				s.Spec.ClusterIPs = []string{"", "2001::1"}
 16902  			},
 16903  			numErrs: 2,
 16904  		}, {
 16905  			name: "invalid, IP + EMPTY STRING",
 16906  			tweakSvc: func(s *core.Service) {
 16907  				s.Spec.IPFamilyPolicy = &requireDualStack
 16908  				s.Spec.ClusterIP = "2001::1"
 16909  				s.Spec.ClusterIPs = []string{"2001::1", ""}
 16910  			},
 16911  			numErrs: 1,
 16912  		}, {
 16913  			name: "invalid, same ip family (v6)",
 16914  			tweakSvc: func(s *core.Service) {
 16915  				s.Spec.IPFamilyPolicy = &requireDualStack
 16916  				s.Spec.ClusterIP = "2001::1"
 16917  				s.Spec.ClusterIPs = []string{"2001::1", "2001::4"}
 16918  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16919  			},
 16920  			numErrs: 2,
 16921  		}, {
 16922  			name: "invalid, same ip family (v4)",
 16923  			tweakSvc: func(s *core.Service) {
 16924  				s.Spec.IPFamilyPolicy = &requireDualStack
 16925  				s.Spec.ClusterIP = "10.0.0.1"
 16926  				s.Spec.ClusterIPs = []string{"10.0.0.1", "10.0.0.10"}
 16927  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16928  
 16929  			},
 16930  			numErrs: 2,
 16931  		}, {
 16932  			name: "invalid, more than two ips",
 16933  			tweakSvc: func(s *core.Service) {
 16934  				s.Spec.IPFamilyPolicy = &requireDualStack
 16935  				s.Spec.ClusterIP = "10.0.0.1"
 16936  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1", "10.0.0.10"}
 16937  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16938  			},
 16939  			numErrs: 1,
 16940  		}, {
 16941  			name: " multi ip, dualstack not set (request for downgrade)",
 16942  			tweakSvc: func(s *core.Service) {
 16943  				s.Spec.IPFamilyPolicy = &singleStack
 16944  				s.Spec.ClusterIP = "10.0.0.1"
 16945  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 16946  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16947  			},
 16948  			numErrs: 0,
 16949  		}, {
 16950  			name: "valid, headless-no-selector + multi family + gate off",
 16951  			tweakSvc: func(s *core.Service) {
 16952  				s.Spec.IPFamilyPolicy = &requireDualStack
 16953  				s.Spec.ClusterIP = "None"
 16954  				s.Spec.ClusterIPs = []string{"None"}
 16955  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 16956  				s.Spec.Selector = nil
 16957  			},
 16958  			numErrs: 0,
 16959  		}, {
 16960  			name: "valid, multi ip, single ipfamilies preferDualStack",
 16961  			tweakSvc: func(s *core.Service) {
 16962  				s.Spec.IPFamilyPolicy = &preferDualStack
 16963  				s.Spec.ClusterIP = "10.0.0.1"
 16964  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 16965  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 16966  			},
 16967  			numErrs: 0,
 16968  		},
 16969  
 16970  		{
 16971  			name: "valid, multi ip, single ipfamilies (must match when provided) + requireDualStack",
 16972  			tweakSvc: func(s *core.Service) {
 16973  				s.Spec.IPFamilyPolicy = &requireDualStack
 16974  				s.Spec.ClusterIP = "10.0.0.1"
 16975  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 16976  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 16977  			},
 16978  			numErrs: 0,
 16979  		}, {
 16980  			name: "invalid, families don't match (v4=>v6)",
 16981  			tweakSvc: func(s *core.Service) {
 16982  				s.Spec.ClusterIP = "10.0.0.1"
 16983  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 16984  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 16985  			},
 16986  			numErrs: 1,
 16987  		}, {
 16988  			name: "invalid, families don't match (v6=>v4)",
 16989  			tweakSvc: func(s *core.Service) {
 16990  				s.Spec.ClusterIP = "2001::1"
 16991  				s.Spec.ClusterIPs = []string{"2001::1"}
 16992  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 16993  			},
 16994  			numErrs: 1,
 16995  		}, {
 16996  			name: "valid. no field set",
 16997  			tweakSvc: func(s *core.Service) {
 16998  			},
 16999  			numErrs: 0,
 17000  		},
 17001  
 17002  		{
 17003  			name: "valid, single ip",
 17004  			tweakSvc: func(s *core.Service) {
 17005  				s.Spec.IPFamilyPolicy = &singleStack
 17006  				s.Spec.ClusterIP = "10.0.0.1"
 17007  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17008  			},
 17009  			numErrs: 0,
 17010  		}, {
 17011  			name: "valid, single family",
 17012  			tweakSvc: func(s *core.Service) {
 17013  				s.Spec.IPFamilyPolicy = &singleStack
 17014  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17015  
 17016  			},
 17017  			numErrs: 0,
 17018  		}, {
 17019  			name: "valid, single ip + single family",
 17020  			tweakSvc: func(s *core.Service) {
 17021  				s.Spec.IPFamilyPolicy = &singleStack
 17022  				s.Spec.ClusterIP = "2001::1"
 17023  				s.Spec.ClusterIPs = []string{"2001::1"}
 17024  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17025  
 17026  			},
 17027  			numErrs: 0,
 17028  		}, {
 17029  			name: "valid, single ip + single family (dual stack requested)",
 17030  			tweakSvc: func(s *core.Service) {
 17031  				s.Spec.IPFamilyPolicy = &preferDualStack
 17032  				s.Spec.ClusterIP = "2001::1"
 17033  				s.Spec.ClusterIPs = []string{"2001::1"}
 17034  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 17035  
 17036  			},
 17037  			numErrs: 0,
 17038  		}, {
 17039  			name: "valid, single ip, multi ipfamilies",
 17040  			tweakSvc: func(s *core.Service) {
 17041  				s.Spec.IPFamilyPolicy = &requireDualStack
 17042  				s.Spec.ClusterIP = "10.0.0.1"
 17043  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17044  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17045  			},
 17046  			numErrs: 0,
 17047  		}, {
 17048  			name: "valid, multi ips, multi ipfamilies (4,6)",
 17049  			tweakSvc: func(s *core.Service) {
 17050  				s.Spec.IPFamilyPolicy = &requireDualStack
 17051  				s.Spec.ClusterIP = "10.0.0.1"
 17052  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17053  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17054  			},
 17055  			numErrs: 0,
 17056  		}, {
 17057  			name: "valid, ips, multi ipfamilies (6,4)",
 17058  			tweakSvc: func(s *core.Service) {
 17059  				s.Spec.IPFamilyPolicy = &requireDualStack
 17060  				s.Spec.ClusterIP = "2001::1"
 17061  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17062  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17063  			},
 17064  			numErrs: 0,
 17065  		}, {
 17066  			name: "valid, multi ips (6,4)",
 17067  			tweakSvc: func(s *core.Service) {
 17068  				s.Spec.IPFamilyPolicy = &requireDualStack
 17069  				s.Spec.ClusterIP = "2001::1"
 17070  				s.Spec.ClusterIPs = []string{"2001::1", "10.0.0.1"}
 17071  			},
 17072  			numErrs: 0,
 17073  		}, {
 17074  			name: "valid, multi ipfamilies (6,4)",
 17075  			tweakSvc: func(s *core.Service) {
 17076  				s.Spec.IPFamilyPolicy = &requireDualStack
 17077  				s.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 17078  			},
 17079  			numErrs: 0,
 17080  		}, {
 17081  			name: "valid, multi ips (4,6)",
 17082  			tweakSvc: func(s *core.Service) {
 17083  				s.Spec.IPFamilyPolicy = &requireDualStack
 17084  				s.Spec.ClusterIP = "10.0.0.1"
 17085  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17086  			},
 17087  			numErrs: 0,
 17088  		}, {
 17089  			name: "valid,  multi ipfamilies (4,6)",
 17090  			tweakSvc: func(s *core.Service) {
 17091  				s.Spec.IPFamilyPolicy = &requireDualStack
 17092  				s.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 17093  			},
 17094  			numErrs: 0,
 17095  		}, {
 17096  			name: "valid, dual stack",
 17097  			tweakSvc: func(s *core.Service) {
 17098  				s.Spec.IPFamilyPolicy = &requireDualStack
 17099  			},
 17100  			numErrs: 0,
 17101  		},
 17102  
 17103  		{
 17104  			name: `valid appProtocol`,
 17105  			tweakSvc: func(s *core.Service) {
 17106  				s.Spec.Ports = []core.ServicePort{{
 17107  					Port:        12345,
 17108  					TargetPort:  intstr.FromInt32(12345),
 17109  					Protocol:    "TCP",
 17110  					AppProtocol: utilpointer.String("HTTP"),
 17111  				}}
 17112  			},
 17113  			numErrs: 0,
 17114  		}, {
 17115  			name: `valid custom appProtocol`,
 17116  			tweakSvc: func(s *core.Service) {
 17117  				s.Spec.Ports = []core.ServicePort{{
 17118  					Port:        12345,
 17119  					TargetPort:  intstr.FromInt32(12345),
 17120  					Protocol:    "TCP",
 17121  					AppProtocol: utilpointer.String("example.com/protocol"),
 17122  				}}
 17123  			},
 17124  			numErrs: 0,
 17125  		}, {
 17126  			name: `invalid appProtocol`,
 17127  			tweakSvc: func(s *core.Service) {
 17128  				s.Spec.Ports = []core.ServicePort{{
 17129  					Port:        12345,
 17130  					TargetPort:  intstr.FromInt32(12345),
 17131  					Protocol:    "TCP",
 17132  					AppProtocol: utilpointer.String("example.com/protocol_with{invalid}[characters]"),
 17133  				}}
 17134  			},
 17135  			numErrs: 1,
 17136  		},
 17137  
 17138  		{
 17139  			name: "invalid cluster ip != clusterIP in multi ip service",
 17140  			tweakSvc: func(s *core.Service) {
 17141  				s.Spec.IPFamilyPolicy = &requireDualStack
 17142  				s.Spec.ClusterIP = "10.0.0.10"
 17143  				s.Spec.ClusterIPs = []string{"10.0.0.1", "2001::1"}
 17144  			},
 17145  			numErrs: 1,
 17146  		}, {
 17147  			name: "invalid cluster ip != clusterIP in single ip service",
 17148  			tweakSvc: func(s *core.Service) {
 17149  				s.Spec.ClusterIP = "10.0.0.10"
 17150  				s.Spec.ClusterIPs = []string{"10.0.0.1"}
 17151  			},
 17152  			numErrs: 1,
 17153  		}, {
 17154  			name: "Use AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 17155  			tweakSvc: func(s *core.Service) {
 17156  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17157  			},
 17158  			numErrs: 1,
 17159  		}, {
 17160  			name: "valid LoadBalancerClass when type is LoadBalancer",
 17161  			tweakSvc: func(s *core.Service) {
 17162  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17163  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17164  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17165  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 17166  			},
 17167  			numErrs: 0,
 17168  		}, {
 17169  			name: "invalid LoadBalancerClass",
 17170  			tweakSvc: func(s *core.Service) {
 17171  				s.Spec.Type = core.ServiceTypeLoadBalancer
 17172  				s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17173  				s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17174  				s.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerClass")
 17175  			},
 17176  			numErrs: 1,
 17177  		}, {
 17178  			name: "invalid: set LoadBalancerClass when type is not LoadBalancer",
 17179  			tweakSvc: func(s *core.Service) {
 17180  				s.Spec.Type = core.ServiceTypeClusterIP
 17181  				s.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 17182  			},
 17183  			numErrs: 1,
 17184  		}, {
 17185  			name: "topology annotations are mismatched",
 17186  			tweakSvc: func(s *core.Service) {
 17187  				s.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 17188  				s.Annotations[core.AnnotationTopologyMode] = "different"
 17189  			},
 17190  			numErrs: 1,
 17191  		},
 17192  	}
 17193  
 17194  	for _, tc := range testCases {
 17195  		t.Run(tc.name, func(t *testing.T) {
 17196  			for i := range tc.featureGates {
 17197  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tc.featureGates[i], true)()
 17198  			}
 17199  			svc := makeValidService()
 17200  			tc.tweakSvc(&svc)
 17201  			errs := ValidateServiceCreate(&svc)
 17202  			if len(errs) != tc.numErrs {
 17203  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 17204  			}
 17205  		})
 17206  	}
 17207  }
 17208  
 17209  func TestValidateServiceExternalTrafficPolicy(t *testing.T) {
 17210  	testCases := []struct {
 17211  		name     string
 17212  		tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it.
 17213  		numErrs  int
 17214  	}{{
 17215  		name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
 17216  		tweakSvc: func(s *core.Service) {
 17217  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17218  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17219  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17220  			s.Spec.HealthCheckNodePort = 34567
 17221  		},
 17222  		numErrs: 0,
 17223  	}, {
 17224  		name: "valid nodePort service with externalTrafficPolicy set",
 17225  		tweakSvc: func(s *core.Service) {
 17226  			s.Spec.Type = core.ServiceTypeNodePort
 17227  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17228  		},
 17229  		numErrs: 0,
 17230  	}, {
 17231  		name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
 17232  		tweakSvc: func(s *core.Service) {
 17233  			s.Spec.Type = core.ServiceTypeClusterIP
 17234  		},
 17235  		numErrs: 0,
 17236  	}, {
 17237  		name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
 17238  		tweakSvc: func(s *core.Service) {
 17239  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17240  			s.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 17241  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 17242  			s.Spec.HealthCheckNodePort = 34567
 17243  		},
 17244  		numErrs: 1,
 17245  	}, {
 17246  		name: "cannot set healthCheckNodePort field on nodePort service",
 17247  		tweakSvc: func(s *core.Service) {
 17248  			s.Spec.Type = core.ServiceTypeNodePort
 17249  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17250  			s.Spec.HealthCheckNodePort = 34567
 17251  		},
 17252  		numErrs: 1,
 17253  	}, {
 17254  		name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
 17255  		tweakSvc: func(s *core.Service) {
 17256  			s.Spec.Type = core.ServiceTypeClusterIP
 17257  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17258  			s.Spec.HealthCheckNodePort = 34567
 17259  		},
 17260  		numErrs: 2,
 17261  	}, {
 17262  		name: "cannot set externalTrafficPolicy field on ExternalName service",
 17263  		tweakSvc: func(s *core.Service) {
 17264  			s.Spec.Type = core.ServiceTypeExternalName
 17265  			s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyLocal
 17266  		},
 17267  		numErrs: 1,
 17268  	}, {
 17269  		name: "externalTrafficPolicy is required on NodePort service",
 17270  		tweakSvc: func(s *core.Service) {
 17271  			s.Spec.Type = core.ServiceTypeNodePort
 17272  		},
 17273  		numErrs: 1,
 17274  	}, {
 17275  		name: "externalTrafficPolicy is required on LoadBalancer service",
 17276  		tweakSvc: func(s *core.Service) {
 17277  			s.Spec.Type = core.ServiceTypeLoadBalancer
 17278  		},
 17279  		numErrs: 1,
 17280  	}, {
 17281  		name: "externalTrafficPolicy is required on ClusterIP service with externalIPs",
 17282  		tweakSvc: func(s *core.Service) {
 17283  			s.Spec.Type = core.ServiceTypeClusterIP
 17284  			s.Spec.ExternalIPs = []string{"1.2.3,4"}
 17285  		},
 17286  		numErrs: 1,
 17287  	},
 17288  	}
 17289  
 17290  	for _, tc := range testCases {
 17291  		svc := makeValidService()
 17292  		tc.tweakSvc(&svc)
 17293  		errs := validateServiceExternalTrafficPolicy(&svc)
 17294  		if len(errs) != tc.numErrs {
 17295  			t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
 17296  		}
 17297  	}
 17298  }
 17299  
 17300  func TestValidateReplicationControllerStatus(t *testing.T) {
 17301  	tests := []struct {
 17302  		name string
 17303  
 17304  		replicas             int32
 17305  		fullyLabeledReplicas int32
 17306  		readyReplicas        int32
 17307  		availableReplicas    int32
 17308  		observedGeneration   int64
 17309  
 17310  		expectedErr bool
 17311  	}{{
 17312  		name:                 "valid status",
 17313  		replicas:             3,
 17314  		fullyLabeledReplicas: 3,
 17315  		readyReplicas:        2,
 17316  		availableReplicas:    1,
 17317  		observedGeneration:   2,
 17318  		expectedErr:          false,
 17319  	}, {
 17320  		name:                 "invalid replicas",
 17321  		replicas:             -1,
 17322  		fullyLabeledReplicas: 3,
 17323  		readyReplicas:        2,
 17324  		availableReplicas:    1,
 17325  		observedGeneration:   2,
 17326  		expectedErr:          true,
 17327  	}, {
 17328  		name:                 "invalid fullyLabeledReplicas",
 17329  		replicas:             3,
 17330  		fullyLabeledReplicas: -1,
 17331  		readyReplicas:        2,
 17332  		availableReplicas:    1,
 17333  		observedGeneration:   2,
 17334  		expectedErr:          true,
 17335  	}, {
 17336  		name:                 "invalid readyReplicas",
 17337  		replicas:             3,
 17338  		fullyLabeledReplicas: 3,
 17339  		readyReplicas:        -1,
 17340  		availableReplicas:    1,
 17341  		observedGeneration:   2,
 17342  		expectedErr:          true,
 17343  	}, {
 17344  		name:                 "invalid availableReplicas",
 17345  		replicas:             3,
 17346  		fullyLabeledReplicas: 3,
 17347  		readyReplicas:        3,
 17348  		availableReplicas:    -1,
 17349  		observedGeneration:   2,
 17350  		expectedErr:          true,
 17351  	}, {
 17352  		name:                 "invalid observedGeneration",
 17353  		replicas:             3,
 17354  		fullyLabeledReplicas: 3,
 17355  		readyReplicas:        3,
 17356  		availableReplicas:    3,
 17357  		observedGeneration:   -1,
 17358  		expectedErr:          true,
 17359  	}, {
 17360  		name:                 "fullyLabeledReplicas greater than replicas",
 17361  		replicas:             3,
 17362  		fullyLabeledReplicas: 4,
 17363  		readyReplicas:        3,
 17364  		availableReplicas:    3,
 17365  		observedGeneration:   1,
 17366  		expectedErr:          true,
 17367  	}, {
 17368  		name:                 "readyReplicas greater than replicas",
 17369  		replicas:             3,
 17370  		fullyLabeledReplicas: 3,
 17371  		readyReplicas:        4,
 17372  		availableReplicas:    3,
 17373  		observedGeneration:   1,
 17374  		expectedErr:          true,
 17375  	}, {
 17376  		name:                 "availableReplicas greater than replicas",
 17377  		replicas:             3,
 17378  		fullyLabeledReplicas: 3,
 17379  		readyReplicas:        3,
 17380  		availableReplicas:    4,
 17381  		observedGeneration:   1,
 17382  		expectedErr:          true,
 17383  	}, {
 17384  		name:                 "availableReplicas greater than readyReplicas",
 17385  		replicas:             3,
 17386  		fullyLabeledReplicas: 3,
 17387  		readyReplicas:        2,
 17388  		availableReplicas:    3,
 17389  		observedGeneration:   1,
 17390  		expectedErr:          true,
 17391  	},
 17392  	}
 17393  
 17394  	for _, test := range tests {
 17395  		status := core.ReplicationControllerStatus{
 17396  			Replicas:             test.replicas,
 17397  			FullyLabeledReplicas: test.fullyLabeledReplicas,
 17398  			ReadyReplicas:        test.readyReplicas,
 17399  			AvailableReplicas:    test.availableReplicas,
 17400  			ObservedGeneration:   test.observedGeneration,
 17401  		}
 17402  
 17403  		if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
 17404  			t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
 17405  		}
 17406  	}
 17407  }
 17408  
 17409  func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
 17410  	validSelector := map[string]string{"a": "b"}
 17411  	validPodTemplate := core.PodTemplate{
 17412  		Template: core.PodTemplateSpec{
 17413  			ObjectMeta: metav1.ObjectMeta{
 17414  				Labels: validSelector,
 17415  			},
 17416  			Spec: core.PodSpec{
 17417  				RestartPolicy: core.RestartPolicyAlways,
 17418  				DNSPolicy:     core.DNSClusterFirst,
 17419  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17420  			},
 17421  		},
 17422  	}
 17423  	type rcUpdateTest struct {
 17424  		old    core.ReplicationController
 17425  		update core.ReplicationController
 17426  	}
 17427  	successCases := []rcUpdateTest{{
 17428  		old: core.ReplicationController{
 17429  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17430  			Spec: core.ReplicationControllerSpec{
 17431  				Selector: validSelector,
 17432  				Template: &validPodTemplate.Template,
 17433  			},
 17434  			Status: core.ReplicationControllerStatus{
 17435  				Replicas: 2,
 17436  			},
 17437  		},
 17438  		update: core.ReplicationController{
 17439  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17440  			Spec: core.ReplicationControllerSpec{
 17441  				Replicas: 3,
 17442  				Selector: validSelector,
 17443  				Template: &validPodTemplate.Template,
 17444  			},
 17445  			Status: core.ReplicationControllerStatus{
 17446  				Replicas: 4,
 17447  			},
 17448  		},
 17449  	},
 17450  	}
 17451  	for _, successCase := range successCases {
 17452  		successCase.old.ObjectMeta.ResourceVersion = "1"
 17453  		successCase.update.ObjectMeta.ResourceVersion = "1"
 17454  		if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
 17455  			t.Errorf("expected success: %v", errs)
 17456  		}
 17457  	}
 17458  	errorCases := map[string]rcUpdateTest{
 17459  		"negative replicas": {
 17460  			old: core.ReplicationController{
 17461  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 17462  				Spec: core.ReplicationControllerSpec{
 17463  					Selector: validSelector,
 17464  					Template: &validPodTemplate.Template,
 17465  				},
 17466  				Status: core.ReplicationControllerStatus{
 17467  					Replicas: 3,
 17468  				},
 17469  			},
 17470  			update: core.ReplicationController{
 17471  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17472  				Spec: core.ReplicationControllerSpec{
 17473  					Replicas: 2,
 17474  					Selector: validSelector,
 17475  					Template: &validPodTemplate.Template,
 17476  				},
 17477  				Status: core.ReplicationControllerStatus{
 17478  					Replicas: -3,
 17479  				},
 17480  			},
 17481  		},
 17482  	}
 17483  	for testName, errorCase := range errorCases {
 17484  		if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
 17485  			t.Errorf("expected failure: %s", testName)
 17486  		}
 17487  	}
 17488  
 17489  }
 17490  
 17491  func TestValidateReplicationControllerUpdate(t *testing.T) {
 17492  	validSelector := map[string]string{"a": "b"}
 17493  	validPodTemplate := core.PodTemplate{
 17494  		Template: core.PodTemplateSpec{
 17495  			ObjectMeta: metav1.ObjectMeta{
 17496  				Labels: validSelector,
 17497  			},
 17498  			Spec: core.PodSpec{
 17499  				RestartPolicy: core.RestartPolicyAlways,
 17500  				DNSPolicy:     core.DNSClusterFirst,
 17501  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17502  			},
 17503  		},
 17504  	}
 17505  	readWriteVolumePodTemplate := core.PodTemplate{
 17506  		Template: core.PodTemplateSpec{
 17507  			ObjectMeta: metav1.ObjectMeta{
 17508  				Labels: validSelector,
 17509  			},
 17510  			Spec: core.PodSpec{
 17511  				RestartPolicy: core.RestartPolicyAlways,
 17512  				DNSPolicy:     core.DNSClusterFirst,
 17513  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17514  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 17515  			},
 17516  		},
 17517  	}
 17518  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 17519  	invalidPodTemplate := core.PodTemplate{
 17520  		Template: core.PodTemplateSpec{
 17521  			Spec: core.PodSpec{
 17522  				RestartPolicy: core.RestartPolicyAlways,
 17523  				DNSPolicy:     core.DNSClusterFirst,
 17524  			},
 17525  			ObjectMeta: metav1.ObjectMeta{
 17526  				Labels: invalidSelector,
 17527  			},
 17528  		},
 17529  	}
 17530  	type rcUpdateTest struct {
 17531  		old    core.ReplicationController
 17532  		update core.ReplicationController
 17533  	}
 17534  	successCases := []rcUpdateTest{{
 17535  		old: core.ReplicationController{
 17536  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17537  			Spec: core.ReplicationControllerSpec{
 17538  				Selector: validSelector,
 17539  				Template: &validPodTemplate.Template,
 17540  			},
 17541  		},
 17542  		update: core.ReplicationController{
 17543  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17544  			Spec: core.ReplicationControllerSpec{
 17545  				Replicas: 3,
 17546  				Selector: validSelector,
 17547  				Template: &validPodTemplate.Template,
 17548  			},
 17549  		},
 17550  	}, {
 17551  		old: core.ReplicationController{
 17552  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17553  			Spec: core.ReplicationControllerSpec{
 17554  				Selector: validSelector,
 17555  				Template: &validPodTemplate.Template,
 17556  			},
 17557  		},
 17558  		update: core.ReplicationController{
 17559  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17560  			Spec: core.ReplicationControllerSpec{
 17561  				Replicas: 1,
 17562  				Selector: validSelector,
 17563  				Template: &readWriteVolumePodTemplate.Template,
 17564  			},
 17565  		},
 17566  	},
 17567  	}
 17568  	for _, successCase := range successCases {
 17569  		successCase.old.ObjectMeta.ResourceVersion = "1"
 17570  		successCase.update.ObjectMeta.ResourceVersion = "1"
 17571  		if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old, PodValidationOptions{}); len(errs) != 0 {
 17572  			t.Errorf("expected success: %v", errs)
 17573  		}
 17574  	}
 17575  	errorCases := map[string]rcUpdateTest{
 17576  		"more than one read/write": {
 17577  			old: core.ReplicationController{
 17578  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 17579  				Spec: core.ReplicationControllerSpec{
 17580  					Selector: validSelector,
 17581  					Template: &validPodTemplate.Template,
 17582  				},
 17583  			},
 17584  			update: core.ReplicationController{
 17585  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17586  				Spec: core.ReplicationControllerSpec{
 17587  					Replicas: 2,
 17588  					Selector: validSelector,
 17589  					Template: &readWriteVolumePodTemplate.Template,
 17590  				},
 17591  			},
 17592  		},
 17593  		"invalid selector": {
 17594  			old: core.ReplicationController{
 17595  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 17596  				Spec: core.ReplicationControllerSpec{
 17597  					Selector: validSelector,
 17598  					Template: &validPodTemplate.Template,
 17599  				},
 17600  			},
 17601  			update: core.ReplicationController{
 17602  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17603  				Spec: core.ReplicationControllerSpec{
 17604  					Replicas: 2,
 17605  					Selector: invalidSelector,
 17606  					Template: &validPodTemplate.Template,
 17607  				},
 17608  			},
 17609  		},
 17610  		"invalid pod": {
 17611  			old: core.ReplicationController{
 17612  				ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 17613  				Spec: core.ReplicationControllerSpec{
 17614  					Selector: validSelector,
 17615  					Template: &validPodTemplate.Template,
 17616  				},
 17617  			},
 17618  			update: core.ReplicationController{
 17619  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17620  				Spec: core.ReplicationControllerSpec{
 17621  					Replicas: 2,
 17622  					Selector: validSelector,
 17623  					Template: &invalidPodTemplate.Template,
 17624  				},
 17625  			},
 17626  		},
 17627  		"negative replicas": {
 17628  			old: core.ReplicationController{
 17629  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17630  				Spec: core.ReplicationControllerSpec{
 17631  					Selector: validSelector,
 17632  					Template: &validPodTemplate.Template,
 17633  				},
 17634  			},
 17635  			update: core.ReplicationController{
 17636  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17637  				Spec: core.ReplicationControllerSpec{
 17638  					Replicas: -1,
 17639  					Selector: validSelector,
 17640  					Template: &validPodTemplate.Template,
 17641  				},
 17642  			},
 17643  		},
 17644  	}
 17645  	for testName, errorCase := range errorCases {
 17646  		if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old, PodValidationOptions{}); len(errs) == 0 {
 17647  			t.Errorf("expected failure: %s", testName)
 17648  		}
 17649  	}
 17650  }
 17651  
 17652  func TestValidateReplicationController(t *testing.T) {
 17653  	validSelector := map[string]string{"a": "b"}
 17654  	validPodTemplate := core.PodTemplate{
 17655  		Template: core.PodTemplateSpec{
 17656  			ObjectMeta: metav1.ObjectMeta{
 17657  				Labels: validSelector,
 17658  			},
 17659  			Spec: core.PodSpec{
 17660  				RestartPolicy: core.RestartPolicyAlways,
 17661  				DNSPolicy:     core.DNSClusterFirst,
 17662  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17663  			},
 17664  		},
 17665  	}
 17666  	readWriteVolumePodTemplate := core.PodTemplate{
 17667  		Template: core.PodTemplateSpec{
 17668  			ObjectMeta: metav1.ObjectMeta{
 17669  				Labels: validSelector,
 17670  			},
 17671  			Spec: core.PodSpec{
 17672  				Volumes:       []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
 17673  				RestartPolicy: core.RestartPolicyAlways,
 17674  				DNSPolicy:     core.DNSClusterFirst,
 17675  				Containers:    []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17676  			},
 17677  		},
 17678  	}
 17679  	hostnetPodTemplate := core.PodTemplate{
 17680  		Template: core.PodTemplateSpec{
 17681  			ObjectMeta: metav1.ObjectMeta{
 17682  				Labels: validSelector,
 17683  			},
 17684  			Spec: core.PodSpec{
 17685  				SecurityContext: &core.PodSecurityContext{
 17686  					HostNetwork: true,
 17687  				},
 17688  				RestartPolicy: core.RestartPolicyAlways,
 17689  				DNSPolicy:     core.DNSClusterFirst,
 17690  				Containers: []core.Container{{
 17691  					Name:                     "abc",
 17692  					Image:                    "image",
 17693  					ImagePullPolicy:          "IfNotPresent",
 17694  					TerminationMessagePolicy: "File",
 17695  					Ports: []core.ContainerPort{{
 17696  						ContainerPort: 12345,
 17697  						Protocol:      core.ProtocolTCP,
 17698  					}},
 17699  				}},
 17700  			},
 17701  		},
 17702  	}
 17703  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 17704  	invalidPodTemplate := core.PodTemplate{
 17705  		Template: core.PodTemplateSpec{
 17706  			Spec: core.PodSpec{
 17707  				RestartPolicy: core.RestartPolicyAlways,
 17708  				DNSPolicy:     core.DNSClusterFirst,
 17709  			},
 17710  			ObjectMeta: metav1.ObjectMeta{
 17711  				Labels: invalidSelector,
 17712  			},
 17713  		},
 17714  	}
 17715  	successCases := []core.ReplicationController{{
 17716  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17717  		Spec: core.ReplicationControllerSpec{
 17718  			Selector: validSelector,
 17719  			Template: &validPodTemplate.Template,
 17720  		},
 17721  	}, {
 17722  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 17723  		Spec: core.ReplicationControllerSpec{
 17724  			Selector: validSelector,
 17725  			Template: &validPodTemplate.Template,
 17726  		},
 17727  	}, {
 17728  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 17729  		Spec: core.ReplicationControllerSpec{
 17730  			Replicas: 1,
 17731  			Selector: validSelector,
 17732  			Template: &readWriteVolumePodTemplate.Template,
 17733  		},
 17734  	}, {
 17735  		ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
 17736  		Spec: core.ReplicationControllerSpec{
 17737  			Replicas: 1,
 17738  			Selector: validSelector,
 17739  			Template: &hostnetPodTemplate.Template,
 17740  		},
 17741  	}}
 17742  	for _, successCase := range successCases {
 17743  		if errs := ValidateReplicationController(&successCase, PodValidationOptions{}); len(errs) != 0 {
 17744  			t.Errorf("expected success: %v", errs)
 17745  		}
 17746  	}
 17747  
 17748  	errorCases := map[string]core.ReplicationController{
 17749  		"zero-length ID": {
 17750  			ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
 17751  			Spec: core.ReplicationControllerSpec{
 17752  				Selector: validSelector,
 17753  				Template: &validPodTemplate.Template,
 17754  			},
 17755  		},
 17756  		"missing-namespace": {
 17757  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 17758  			Spec: core.ReplicationControllerSpec{
 17759  				Selector: validSelector,
 17760  				Template: &validPodTemplate.Template,
 17761  			},
 17762  		},
 17763  		"empty selector": {
 17764  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17765  			Spec: core.ReplicationControllerSpec{
 17766  				Template: &validPodTemplate.Template,
 17767  			},
 17768  		},
 17769  		"selector_doesnt_match": {
 17770  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17771  			Spec: core.ReplicationControllerSpec{
 17772  				Selector: map[string]string{"foo": "bar"},
 17773  				Template: &validPodTemplate.Template,
 17774  			},
 17775  		},
 17776  		"invalid manifest": {
 17777  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17778  			Spec: core.ReplicationControllerSpec{
 17779  				Selector: validSelector,
 17780  			},
 17781  		},
 17782  		"read-write persistent disk with > 1 pod": {
 17783  			ObjectMeta: metav1.ObjectMeta{Name: "abc"},
 17784  			Spec: core.ReplicationControllerSpec{
 17785  				Replicas: 2,
 17786  				Selector: validSelector,
 17787  				Template: &readWriteVolumePodTemplate.Template,
 17788  			},
 17789  		},
 17790  		"negative_replicas": {
 17791  			ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 17792  			Spec: core.ReplicationControllerSpec{
 17793  				Replicas: -1,
 17794  				Selector: validSelector,
 17795  			},
 17796  		},
 17797  		"invalid_label": {
 17798  			ObjectMeta: metav1.ObjectMeta{
 17799  				Name:      "abc-123",
 17800  				Namespace: metav1.NamespaceDefault,
 17801  				Labels: map[string]string{
 17802  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 17803  				},
 17804  			},
 17805  			Spec: core.ReplicationControllerSpec{
 17806  				Selector: validSelector,
 17807  				Template: &validPodTemplate.Template,
 17808  			},
 17809  		},
 17810  		"invalid_label 2": {
 17811  			ObjectMeta: metav1.ObjectMeta{
 17812  				Name:      "abc-123",
 17813  				Namespace: metav1.NamespaceDefault,
 17814  				Labels: map[string]string{
 17815  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 17816  				},
 17817  			},
 17818  			Spec: core.ReplicationControllerSpec{
 17819  				Template: &invalidPodTemplate.Template,
 17820  			},
 17821  		},
 17822  		"invalid_annotation": {
 17823  			ObjectMeta: metav1.ObjectMeta{
 17824  				Name:      "abc-123",
 17825  				Namespace: metav1.NamespaceDefault,
 17826  				Annotations: map[string]string{
 17827  					"NoUppercaseOrSpecialCharsLike=Equals": "bar",
 17828  				},
 17829  			},
 17830  			Spec: core.ReplicationControllerSpec{
 17831  				Selector: validSelector,
 17832  				Template: &validPodTemplate.Template,
 17833  			},
 17834  		},
 17835  		"invalid restart policy 1": {
 17836  			ObjectMeta: metav1.ObjectMeta{
 17837  				Name:      "abc-123",
 17838  				Namespace: metav1.NamespaceDefault,
 17839  			},
 17840  			Spec: core.ReplicationControllerSpec{
 17841  				Selector: validSelector,
 17842  				Template: &core.PodTemplateSpec{
 17843  					Spec: core.PodSpec{
 17844  						RestartPolicy: core.RestartPolicyOnFailure,
 17845  						DNSPolicy:     core.DNSClusterFirst,
 17846  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17847  					},
 17848  					ObjectMeta: metav1.ObjectMeta{
 17849  						Labels: validSelector,
 17850  					},
 17851  				},
 17852  			},
 17853  		},
 17854  		"invalid restart policy 2": {
 17855  			ObjectMeta: metav1.ObjectMeta{
 17856  				Name:      "abc-123",
 17857  				Namespace: metav1.NamespaceDefault,
 17858  			},
 17859  			Spec: core.ReplicationControllerSpec{
 17860  				Selector: validSelector,
 17861  				Template: &core.PodTemplateSpec{
 17862  					Spec: core.PodSpec{
 17863  						RestartPolicy: core.RestartPolicyNever,
 17864  						DNSPolicy:     core.DNSClusterFirst,
 17865  						Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17866  					},
 17867  					ObjectMeta: metav1.ObjectMeta{
 17868  						Labels: validSelector,
 17869  					},
 17870  				},
 17871  			},
 17872  		},
 17873  		"template may not contain ephemeral containers": {
 17874  			ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
 17875  			Spec: core.ReplicationControllerSpec{
 17876  				Replicas: 1,
 17877  				Selector: validSelector,
 17878  				Template: &core.PodTemplateSpec{
 17879  					ObjectMeta: metav1.ObjectMeta{
 17880  						Labels: validSelector,
 17881  					},
 17882  					Spec: core.PodSpec{
 17883  						RestartPolicy:       core.RestartPolicyAlways,
 17884  						DNSPolicy:           core.DNSClusterFirst,
 17885  						Containers:          []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 17886  						EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
 17887  					},
 17888  				},
 17889  			},
 17890  		},
 17891  	}
 17892  	for k, v := range errorCases {
 17893  		errs := ValidateReplicationController(&v, PodValidationOptions{})
 17894  		if len(errs) == 0 {
 17895  			t.Errorf("expected failure for %s", k)
 17896  		}
 17897  		for i := range errs {
 17898  			field := errs[i].Field
 17899  			if !strings.HasPrefix(field, "spec.template.") &&
 17900  				field != "metadata.name" &&
 17901  				field != "metadata.namespace" &&
 17902  				field != "spec.selector" &&
 17903  				field != "spec.template" &&
 17904  				field != "GCEPersistentDisk.ReadOnly" &&
 17905  				field != "spec.replicas" &&
 17906  				field != "spec.template.labels" &&
 17907  				field != "metadata.annotations" &&
 17908  				field != "metadata.labels" &&
 17909  				field != "status.replicas" {
 17910  				t.Errorf("%s: missing prefix for: %v", k, errs[i])
 17911  			}
 17912  		}
 17913  	}
 17914  }
 17915  
 17916  func TestValidateNode(t *testing.T) {
 17917  	validSelector := map[string]string{"a": "b"}
 17918  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 17919  	successCases := []core.Node{{
 17920  		ObjectMeta: metav1.ObjectMeta{
 17921  			Name:   "abc",
 17922  			Labels: validSelector,
 17923  		},
 17924  		Status: core.NodeStatus{
 17925  			Addresses: []core.NodeAddress{
 17926  				{Type: core.NodeExternalIP, Address: "something"},
 17927  			},
 17928  			Capacity: core.ResourceList{
 17929  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 17930  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 17931  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 17932  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 17933  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("0"),
 17934  			},
 17935  		},
 17936  	}, {
 17937  		ObjectMeta: metav1.ObjectMeta{
 17938  			Name: "abc",
 17939  		},
 17940  		Status: core.NodeStatus{
 17941  			Addresses: []core.NodeAddress{
 17942  				{Type: core.NodeExternalIP, Address: "something"},
 17943  			},
 17944  			Capacity: core.ResourceList{
 17945  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 17946  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 17947  			},
 17948  		},
 17949  	}, {
 17950  		ObjectMeta: metav1.ObjectMeta{
 17951  			Name:   "abc",
 17952  			Labels: validSelector,
 17953  		},
 17954  		Status: core.NodeStatus{
 17955  			Addresses: []core.NodeAddress{
 17956  				{Type: core.NodeExternalIP, Address: "something"},
 17957  			},
 17958  			Capacity: core.ResourceList{
 17959  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 17960  				core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 17961  				core.ResourceName("my.org/gpu"):        resource.MustParse("10"),
 17962  				core.ResourceName("hugepages-2Mi"):     resource.MustParse("10Gi"),
 17963  				core.ResourceName("hugepages-1Gi"):     resource.MustParse("10Gi"),
 17964  			},
 17965  		},
 17966  	}, {
 17967  		ObjectMeta: metav1.ObjectMeta{
 17968  			Name: "dedicated-node1",
 17969  		},
 17970  		Status: core.NodeStatus{
 17971  			Addresses: []core.NodeAddress{
 17972  				{Type: core.NodeExternalIP, Address: "something"},
 17973  			},
 17974  			Capacity: core.ResourceList{
 17975  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 17976  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 17977  			},
 17978  		},
 17979  		Spec: core.NodeSpec{
 17980  			// Add a valid taint to a node
 17981  			Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}},
 17982  		},
 17983  	}, {
 17984  		ObjectMeta: metav1.ObjectMeta{
 17985  			Name: "abc",
 17986  			Annotations: map[string]string{
 17987  				core.PreferAvoidPodsAnnotationKey: `
 17988  							{
 17989  							    "preferAvoidPods": [
 17990  							        {
 17991  							            "podSignature": {
 17992  							                "podController": {
 17993  							                    "apiVersion": "v1",
 17994  							                    "kind": "ReplicationController",
 17995  							                    "name": "foo",
 17996  							                    "uid": "abcdef123456",
 17997  							                    "controller": true
 17998  							                }
 17999  							            },
 18000  							            "reason": "some reason",
 18001  							            "message": "some message"
 18002  							        }
 18003  							    ]
 18004  							}`,
 18005  			},
 18006  		},
 18007  		Status: core.NodeStatus{
 18008  			Addresses: []core.NodeAddress{
 18009  				{Type: core.NodeExternalIP, Address: "something"},
 18010  			},
 18011  			Capacity: core.ResourceList{
 18012  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18013  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18014  			},
 18015  		},
 18016  	}, {
 18017  		ObjectMeta: metav1.ObjectMeta{
 18018  			Name: "abc",
 18019  		},
 18020  		Status: core.NodeStatus{
 18021  			Addresses: []core.NodeAddress{
 18022  				{Type: core.NodeExternalIP, Address: "something"},
 18023  			},
 18024  			Capacity: core.ResourceList{
 18025  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18026  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18027  			},
 18028  		},
 18029  		Spec: core.NodeSpec{
 18030  			PodCIDRs: []string{"192.168.0.0/16"},
 18031  		},
 18032  	},
 18033  	}
 18034  	for _, successCase := range successCases {
 18035  		if errs := ValidateNode(&successCase); len(errs) != 0 {
 18036  			t.Errorf("expected success: %v", errs)
 18037  		}
 18038  	}
 18039  
 18040  	errorCases := map[string]core.Node{
 18041  		"zero-length Name": {
 18042  			ObjectMeta: metav1.ObjectMeta{
 18043  				Name:   "",
 18044  				Labels: validSelector,
 18045  			},
 18046  			Status: core.NodeStatus{
 18047  				Addresses: []core.NodeAddress{},
 18048  				Capacity: core.ResourceList{
 18049  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18050  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18051  				},
 18052  			},
 18053  		},
 18054  		"invalid-labels": {
 18055  			ObjectMeta: metav1.ObjectMeta{
 18056  				Name:   "abc-123",
 18057  				Labels: invalidSelector,
 18058  			},
 18059  			Status: core.NodeStatus{
 18060  				Capacity: core.ResourceList{
 18061  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18062  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18063  				},
 18064  			},
 18065  		},
 18066  		"missing-taint-key": {
 18067  			ObjectMeta: metav1.ObjectMeta{
 18068  				Name: "dedicated-node1",
 18069  			},
 18070  			Spec: core.NodeSpec{
 18071  				// Add a taint with an empty key to a node
 18072  				Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}},
 18073  			},
 18074  		},
 18075  		"bad-taint-key": {
 18076  			ObjectMeta: metav1.ObjectMeta{
 18077  				Name: "dedicated-node1",
 18078  			},
 18079  			Spec: core.NodeSpec{
 18080  				// Add a taint with an invalid  key to a node
 18081  				Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}},
 18082  			},
 18083  		},
 18084  		"bad-taint-value": {
 18085  			ObjectMeta: metav1.ObjectMeta{
 18086  				Name: "dedicated-node2",
 18087  			},
 18088  			Status: core.NodeStatus{
 18089  				Addresses: []core.NodeAddress{
 18090  					{Type: core.NodeExternalIP, Address: "something"},
 18091  				},
 18092  				Capacity: core.ResourceList{
 18093  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18094  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18095  				},
 18096  			},
 18097  			Spec: core.NodeSpec{
 18098  				// Add a taint with a bad value to a node
 18099  				Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}},
 18100  			},
 18101  		},
 18102  		"missing-taint-effect": {
 18103  			ObjectMeta: metav1.ObjectMeta{
 18104  				Name: "dedicated-node3",
 18105  			},
 18106  			Status: core.NodeStatus{
 18107  				Addresses: []core.NodeAddress{
 18108  					{Type: core.NodeExternalIP, Address: "something"},
 18109  				},
 18110  				Capacity: core.ResourceList{
 18111  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18112  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18113  				},
 18114  			},
 18115  			Spec: core.NodeSpec{
 18116  				// Add a taint with an empty effect to a node
 18117  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}},
 18118  			},
 18119  		},
 18120  		"invalid-taint-effect": {
 18121  			ObjectMeta: metav1.ObjectMeta{
 18122  				Name: "dedicated-node3",
 18123  			},
 18124  			Status: core.NodeStatus{
 18125  				Addresses: []core.NodeAddress{
 18126  					{Type: core.NodeExternalIP, Address: "something"},
 18127  				},
 18128  				Capacity: core.ResourceList{
 18129  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18130  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18131  				},
 18132  			},
 18133  			Spec: core.NodeSpec{
 18134  				// Add a taint with NoExecute effect to a node
 18135  				Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}},
 18136  			},
 18137  		},
 18138  		"duplicated-taints-with-same-key-effect": {
 18139  			ObjectMeta: metav1.ObjectMeta{
 18140  				Name: "dedicated-node1",
 18141  			},
 18142  			Spec: core.NodeSpec{
 18143  				// Add two taints to the node with the same key and effect; should be rejected.
 18144  				Taints: []core.Taint{
 18145  					{Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"},
 18146  					{Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"},
 18147  				},
 18148  			},
 18149  		},
 18150  		"missing-podSignature": {
 18151  			ObjectMeta: metav1.ObjectMeta{
 18152  				Name: "abc-123",
 18153  				Annotations: map[string]string{
 18154  					core.PreferAvoidPodsAnnotationKey: `
 18155  							{
 18156  							    "preferAvoidPods": [
 18157  							        {
 18158  							            "reason": "some reason",
 18159  							            "message": "some message"
 18160  							        }
 18161  							    ]
 18162  							}`,
 18163  				},
 18164  			},
 18165  			Status: core.NodeStatus{
 18166  				Addresses: []core.NodeAddress{},
 18167  				Capacity: core.ResourceList{
 18168  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18169  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18170  				},
 18171  			},
 18172  		},
 18173  		"invalid-podController": {
 18174  			ObjectMeta: metav1.ObjectMeta{
 18175  				Name: "abc-123",
 18176  				Annotations: map[string]string{
 18177  					core.PreferAvoidPodsAnnotationKey: `
 18178  							{
 18179  							    "preferAvoidPods": [
 18180  							        {
 18181  							            "podSignature": {
 18182  							                "podController": {
 18183  							                    "apiVersion": "v1",
 18184  							                    "kind": "ReplicationController",
 18185  							                    "name": "foo",
 18186                                                                             "uid": "abcdef123456",
 18187                                                                             "controller": false
 18188  							                }
 18189  							            },
 18190  							            "reason": "some reason",
 18191  							            "message": "some message"
 18192  							        }
 18193  							    ]
 18194  							}`,
 18195  				},
 18196  			},
 18197  			Status: core.NodeStatus{
 18198  				Addresses: []core.NodeAddress{},
 18199  				Capacity: core.ResourceList{
 18200  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18201  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18202  				},
 18203  			},
 18204  		},
 18205  		"invalid-pod-cidr": {
 18206  			ObjectMeta: metav1.ObjectMeta{
 18207  				Name: "abc",
 18208  			},
 18209  			Status: core.NodeStatus{
 18210  				Addresses: []core.NodeAddress{
 18211  					{Type: core.NodeExternalIP, Address: "something"},
 18212  				},
 18213  				Capacity: core.ResourceList{
 18214  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18215  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18216  				},
 18217  			},
 18218  			Spec: core.NodeSpec{
 18219  				PodCIDRs: []string{"192.168.0.0"},
 18220  			},
 18221  		},
 18222  		"duplicate-pod-cidr": {
 18223  			ObjectMeta: metav1.ObjectMeta{
 18224  				Name: "abc",
 18225  			},
 18226  			Status: core.NodeStatus{
 18227  				Addresses: []core.NodeAddress{
 18228  					{Type: core.NodeExternalIP, Address: "something"},
 18229  				},
 18230  				Capacity: core.ResourceList{
 18231  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18232  					core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 18233  				},
 18234  			},
 18235  			Spec: core.NodeSpec{
 18236  				PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"},
 18237  			},
 18238  		},
 18239  	}
 18240  	for k, v := range errorCases {
 18241  		errs := ValidateNode(&v)
 18242  		if len(errs) == 0 {
 18243  			t.Errorf("expected failure for %s", k)
 18244  		}
 18245  		for i := range errs {
 18246  			field := errs[i].Field
 18247  			expectedFields := map[string]bool{
 18248  				"metadata.name":         true,
 18249  				"metadata.labels":       true,
 18250  				"metadata.annotations":  true,
 18251  				"metadata.namespace":    true,
 18252  				"spec.externalID":       true,
 18253  				"spec.taints[0].key":    true,
 18254  				"spec.taints[0].value":  true,
 18255  				"spec.taints[0].effect": true,
 18256  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature":                          true,
 18257  				"metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true,
 18258  			}
 18259  			if val, ok := expectedFields[field]; ok {
 18260  				if !val {
 18261  					t.Errorf("%s: missing prefix for: %v", k, errs[i])
 18262  				}
 18263  			}
 18264  		}
 18265  	}
 18266  }
 18267  
 18268  func TestValidateNodeUpdate(t *testing.T) {
 18269  	tests := []struct {
 18270  		oldNode core.Node
 18271  		node    core.Node
 18272  		valid   bool
 18273  	}{
 18274  		{core.Node{}, core.Node{}, true},
 18275  		{core.Node{
 18276  			ObjectMeta: metav1.ObjectMeta{
 18277  				Name: "foo"}},
 18278  			core.Node{
 18279  				ObjectMeta: metav1.ObjectMeta{
 18280  					Name: "bar"},
 18281  			}, false},
 18282  		{core.Node{
 18283  			ObjectMeta: metav1.ObjectMeta{
 18284  				Name:   "foo",
 18285  				Labels: map[string]string{"foo": "bar"},
 18286  			},
 18287  		}, core.Node{
 18288  			ObjectMeta: metav1.ObjectMeta{
 18289  				Name:   "foo",
 18290  				Labels: map[string]string{"foo": "baz"},
 18291  			},
 18292  		}, true},
 18293  		{core.Node{
 18294  			ObjectMeta: metav1.ObjectMeta{
 18295  				Name: "foo",
 18296  			},
 18297  		}, core.Node{
 18298  			ObjectMeta: metav1.ObjectMeta{
 18299  				Name:   "foo",
 18300  				Labels: map[string]string{"foo": "baz"},
 18301  			},
 18302  		}, true},
 18303  		{core.Node{
 18304  			ObjectMeta: metav1.ObjectMeta{
 18305  				Name:   "foo",
 18306  				Labels: map[string]string{"bar": "foo"},
 18307  			},
 18308  		}, core.Node{
 18309  			ObjectMeta: metav1.ObjectMeta{
 18310  				Name:   "foo",
 18311  				Labels: map[string]string{"foo": "baz"},
 18312  			},
 18313  		}, true},
 18314  		{core.Node{
 18315  			ObjectMeta: metav1.ObjectMeta{
 18316  				Name: "foo",
 18317  			},
 18318  			Spec: core.NodeSpec{
 18319  				PodCIDRs: []string{},
 18320  			},
 18321  		}, core.Node{
 18322  			ObjectMeta: metav1.ObjectMeta{
 18323  				Name: "foo",
 18324  			},
 18325  			Spec: core.NodeSpec{
 18326  				PodCIDRs: []string{"192.168.0.0/16"},
 18327  			},
 18328  		}, true},
 18329  		{core.Node{
 18330  			ObjectMeta: metav1.ObjectMeta{
 18331  				Name: "foo",
 18332  			},
 18333  			Spec: core.NodeSpec{
 18334  				PodCIDRs: []string{"192.123.0.0/16"},
 18335  			},
 18336  		}, core.Node{
 18337  			ObjectMeta: metav1.ObjectMeta{
 18338  				Name: "foo",
 18339  			},
 18340  			Spec: core.NodeSpec{
 18341  				PodCIDRs: []string{"192.168.0.0/16"},
 18342  			},
 18343  		}, false},
 18344  		{core.Node{
 18345  			ObjectMeta: metav1.ObjectMeta{
 18346  				Name: "foo",
 18347  			},
 18348  			Status: core.NodeStatus{
 18349  				Capacity: core.ResourceList{
 18350  					core.ResourceCPU:    resource.MustParse("10000"),
 18351  					core.ResourceMemory: resource.MustParse("100"),
 18352  				},
 18353  			},
 18354  		}, core.Node{
 18355  			ObjectMeta: metav1.ObjectMeta{
 18356  				Name: "foo",
 18357  			},
 18358  			Status: core.NodeStatus{
 18359  				Capacity: core.ResourceList{
 18360  					core.ResourceCPU:    resource.MustParse("100"),
 18361  					core.ResourceMemory: resource.MustParse("10000"),
 18362  				},
 18363  			},
 18364  		}, true},
 18365  		{core.Node{
 18366  			ObjectMeta: metav1.ObjectMeta{
 18367  				Name:   "foo",
 18368  				Labels: map[string]string{"bar": "foo"},
 18369  			},
 18370  			Status: core.NodeStatus{
 18371  				Capacity: core.ResourceList{
 18372  					core.ResourceCPU:    resource.MustParse("10000"),
 18373  					core.ResourceMemory: resource.MustParse("100"),
 18374  				},
 18375  			},
 18376  		}, core.Node{
 18377  			ObjectMeta: metav1.ObjectMeta{
 18378  				Name:   "foo",
 18379  				Labels: map[string]string{"bar": "fooobaz"},
 18380  			},
 18381  			Status: core.NodeStatus{
 18382  				Capacity: core.ResourceList{
 18383  					core.ResourceCPU:    resource.MustParse("100"),
 18384  					core.ResourceMemory: resource.MustParse("10000"),
 18385  				},
 18386  			},
 18387  		}, true},
 18388  		{core.Node{
 18389  			ObjectMeta: metav1.ObjectMeta{
 18390  				Name:   "foo",
 18391  				Labels: map[string]string{"bar": "foo"},
 18392  			},
 18393  			Status: core.NodeStatus{
 18394  				Addresses: []core.NodeAddress{
 18395  					{Type: core.NodeExternalIP, Address: "1.2.3.4"},
 18396  				},
 18397  			},
 18398  		}, core.Node{
 18399  			ObjectMeta: metav1.ObjectMeta{
 18400  				Name:   "foo",
 18401  				Labels: map[string]string{"bar": "fooobaz"},
 18402  			},
 18403  		}, true},
 18404  		{core.Node{
 18405  			ObjectMeta: metav1.ObjectMeta{
 18406  				Name:   "foo",
 18407  				Labels: map[string]string{"foo": "baz"},
 18408  			},
 18409  		}, core.Node{
 18410  			ObjectMeta: metav1.ObjectMeta{
 18411  				Name:   "foo",
 18412  				Labels: map[string]string{"Foo": "baz"},
 18413  			},
 18414  		}, true},
 18415  		{core.Node{
 18416  			ObjectMeta: metav1.ObjectMeta{
 18417  				Name: "foo",
 18418  			},
 18419  			Spec: core.NodeSpec{
 18420  				Unschedulable: false,
 18421  			},
 18422  		}, core.Node{
 18423  			ObjectMeta: metav1.ObjectMeta{
 18424  				Name: "foo",
 18425  			},
 18426  			Spec: core.NodeSpec{
 18427  				Unschedulable: true,
 18428  			},
 18429  		}, true},
 18430  		{core.Node{
 18431  			ObjectMeta: metav1.ObjectMeta{
 18432  				Name: "foo",
 18433  			},
 18434  			Spec: core.NodeSpec{
 18435  				Unschedulable: false,
 18436  			},
 18437  		}, core.Node{
 18438  			ObjectMeta: metav1.ObjectMeta{
 18439  				Name: "foo",
 18440  			},
 18441  			Status: core.NodeStatus{
 18442  				Addresses: []core.NodeAddress{
 18443  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 18444  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 18445  				},
 18446  			},
 18447  		}, false},
 18448  		{core.Node{
 18449  			ObjectMeta: metav1.ObjectMeta{
 18450  				Name: "foo",
 18451  			},
 18452  			Spec: core.NodeSpec{
 18453  				Unschedulable: false,
 18454  			},
 18455  		}, core.Node{
 18456  			ObjectMeta: metav1.ObjectMeta{
 18457  				Name: "foo",
 18458  			},
 18459  			Status: core.NodeStatus{
 18460  				Addresses: []core.NodeAddress{
 18461  					{Type: core.NodeExternalIP, Address: "1.1.1.1"},
 18462  					{Type: core.NodeInternalIP, Address: "10.1.1.1"},
 18463  				},
 18464  			},
 18465  		}, true},
 18466  		{core.Node{
 18467  			ObjectMeta: metav1.ObjectMeta{
 18468  				Name: "foo",
 18469  			},
 18470  		}, core.Node{
 18471  			ObjectMeta: metav1.ObjectMeta{
 18472  				Name: "foo",
 18473  				Annotations: map[string]string{
 18474  					core.PreferAvoidPodsAnnotationKey: `
 18475  							{
 18476  							    "preferAvoidPods": [
 18477  							        {
 18478  							            "podSignature": {
 18479  							                "podController": {
 18480  							                    "apiVersion": "v1",
 18481  							                    "kind": "ReplicationController",
 18482  							                    "name": "foo",
 18483                                                                             "uid": "abcdef123456",
 18484                                                                             "controller": true
 18485  							                }
 18486  							            },
 18487  							            "reason": "some reason",
 18488  							            "message": "some message"
 18489  							        }
 18490  							    ]
 18491  							}`,
 18492  				},
 18493  			},
 18494  			Spec: core.NodeSpec{
 18495  				Unschedulable: false,
 18496  			},
 18497  		}, true},
 18498  		{core.Node{
 18499  			ObjectMeta: metav1.ObjectMeta{
 18500  				Name: "foo",
 18501  			},
 18502  		}, core.Node{
 18503  			ObjectMeta: metav1.ObjectMeta{
 18504  				Name: "foo",
 18505  				Annotations: map[string]string{
 18506  					core.PreferAvoidPodsAnnotationKey: `
 18507  							{
 18508  							    "preferAvoidPods": [
 18509  							        {
 18510  							            "reason": "some reason",
 18511  							            "message": "some message"
 18512  							        }
 18513  							    ]
 18514  							}`,
 18515  				},
 18516  			},
 18517  		}, false},
 18518  		{core.Node{
 18519  			ObjectMeta: metav1.ObjectMeta{
 18520  				Name: "foo",
 18521  			},
 18522  		}, core.Node{
 18523  			ObjectMeta: metav1.ObjectMeta{
 18524  				Name: "foo",
 18525  				Annotations: map[string]string{
 18526  					core.PreferAvoidPodsAnnotationKey: `
 18527  							{
 18528  							    "preferAvoidPods": [
 18529  							        {
 18530  							            "podSignature": {
 18531  							                "podController": {
 18532  							                    "apiVersion": "v1",
 18533  							                    "kind": "ReplicationController",
 18534  							                    "name": "foo",
 18535  							                    "uid": "abcdef123456",
 18536  							                    "controller": false
 18537  							                }
 18538  							            },
 18539  							            "reason": "some reason",
 18540  							            "message": "some message"
 18541  							        }
 18542  							    ]
 18543  							}`,
 18544  				},
 18545  			},
 18546  		}, false},
 18547  		{core.Node{
 18548  			ObjectMeta: metav1.ObjectMeta{
 18549  				Name: "valid-extended-resources",
 18550  			},
 18551  		}, core.Node{
 18552  			ObjectMeta: metav1.ObjectMeta{
 18553  				Name: "valid-extended-resources",
 18554  			},
 18555  			Status: core.NodeStatus{
 18556  				Capacity: core.ResourceList{
 18557  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18558  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18559  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 18560  					core.ResourceName("example.com/b"):     resource.MustParse("10"),
 18561  				},
 18562  			},
 18563  		}, true},
 18564  		{core.Node{
 18565  			ObjectMeta: metav1.ObjectMeta{
 18566  				Name: "invalid-fractional-extended-capacity",
 18567  			},
 18568  		}, core.Node{
 18569  			ObjectMeta: metav1.ObjectMeta{
 18570  				Name: "invalid-fractional-extended-capacity",
 18571  			},
 18572  			Status: core.NodeStatus{
 18573  				Capacity: core.ResourceList{
 18574  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18575  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18576  					core.ResourceName("example.com/a"):     resource.MustParse("500m"),
 18577  				},
 18578  			},
 18579  		}, false},
 18580  		{core.Node{
 18581  			ObjectMeta: metav1.ObjectMeta{
 18582  				Name: "invalid-fractional-extended-allocatable",
 18583  			},
 18584  		}, core.Node{
 18585  			ObjectMeta: metav1.ObjectMeta{
 18586  				Name: "invalid-fractional-extended-allocatable",
 18587  			},
 18588  			Status: core.NodeStatus{
 18589  				Capacity: core.ResourceList{
 18590  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18591  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18592  					core.ResourceName("example.com/a"):     resource.MustParse("5"),
 18593  				},
 18594  				Allocatable: core.ResourceList{
 18595  					core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 18596  					core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 18597  					core.ResourceName("example.com/a"):     resource.MustParse("4.5"),
 18598  				},
 18599  			},
 18600  		}, false},
 18601  		{core.Node{
 18602  			ObjectMeta: metav1.ObjectMeta{
 18603  				Name: "update-provider-id-when-not-set",
 18604  			},
 18605  		}, core.Node{
 18606  			ObjectMeta: metav1.ObjectMeta{
 18607  				Name: "update-provider-id-when-not-set",
 18608  			},
 18609  			Spec: core.NodeSpec{
 18610  				ProviderID: "provider:///new",
 18611  			},
 18612  		}, true},
 18613  		{core.Node{
 18614  			ObjectMeta: metav1.ObjectMeta{
 18615  				Name: "update-provider-id-when-set",
 18616  			},
 18617  			Spec: core.NodeSpec{
 18618  				ProviderID: "provider:///old",
 18619  			},
 18620  		}, core.Node{
 18621  			ObjectMeta: metav1.ObjectMeta{
 18622  				Name: "update-provider-id-when-set",
 18623  			},
 18624  			Spec: core.NodeSpec{
 18625  				ProviderID: "provider:///new",
 18626  			},
 18627  		}, false},
 18628  		{core.Node{
 18629  			ObjectMeta: metav1.ObjectMeta{
 18630  				Name: "pod-cidrs-as-is",
 18631  			},
 18632  			Spec: core.NodeSpec{
 18633  				PodCIDRs: []string{"192.168.0.0/16"},
 18634  			},
 18635  		}, core.Node{
 18636  			ObjectMeta: metav1.ObjectMeta{
 18637  				Name: "pod-cidrs-as-is",
 18638  			},
 18639  			Spec: core.NodeSpec{
 18640  				PodCIDRs: []string{"192.168.0.0/16"},
 18641  			},
 18642  		}, true},
 18643  		{core.Node{
 18644  			ObjectMeta: metav1.ObjectMeta{
 18645  				Name: "pod-cidrs-as-is-2",
 18646  			},
 18647  			Spec: core.NodeSpec{
 18648  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 18649  			},
 18650  		}, core.Node{
 18651  			ObjectMeta: metav1.ObjectMeta{
 18652  				Name: "pod-cidrs-as-is-2",
 18653  			},
 18654  			Spec: core.NodeSpec{
 18655  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 18656  			},
 18657  		}, true},
 18658  		{core.Node{
 18659  			ObjectMeta: metav1.ObjectMeta{
 18660  				Name: "pod-cidrs-not-same-length",
 18661  			},
 18662  			Spec: core.NodeSpec{
 18663  				PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"},
 18664  			},
 18665  		}, core.Node{
 18666  			ObjectMeta: metav1.ObjectMeta{
 18667  				Name: "pod-cidrs-not-same-length",
 18668  			},
 18669  			Spec: core.NodeSpec{
 18670  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 18671  			},
 18672  		}, false},
 18673  		{core.Node{
 18674  			ObjectMeta: metav1.ObjectMeta{
 18675  				Name: "pod-cidrs-not-same",
 18676  			},
 18677  			Spec: core.NodeSpec{
 18678  				PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
 18679  			},
 18680  		}, core.Node{
 18681  			ObjectMeta: metav1.ObjectMeta{
 18682  				Name: "pod-cidrs-not-same",
 18683  			},
 18684  			Spec: core.NodeSpec{
 18685  				PodCIDRs: []string{"2000::/10", "192.168.0.0/16"},
 18686  			},
 18687  		}, false},
 18688  	}
 18689  	for i, test := range tests {
 18690  		test.oldNode.ObjectMeta.ResourceVersion = "1"
 18691  		test.node.ObjectMeta.ResourceVersion = "1"
 18692  		errs := ValidateNodeUpdate(&test.node, &test.oldNode)
 18693  		if test.valid && len(errs) > 0 {
 18694  			t.Errorf("%d: Unexpected error: %v", i, errs)
 18695  			t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
 18696  		}
 18697  		if !test.valid && len(errs) == 0 {
 18698  			t.Errorf("%d: Unexpected non-error", i)
 18699  		}
 18700  	}
 18701  }
 18702  
 18703  func TestValidateServiceUpdate(t *testing.T) {
 18704  	requireDualStack := core.IPFamilyPolicyRequireDualStack
 18705  	preferDualStack := core.IPFamilyPolicyPreferDualStack
 18706  	singleStack := core.IPFamilyPolicySingleStack
 18707  	testCases := []struct {
 18708  		name     string
 18709  		tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
 18710  		numErrs  int
 18711  	}{{
 18712  		name: "no change",
 18713  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18714  			// do nothing
 18715  		},
 18716  		numErrs: 0,
 18717  	}, {
 18718  		name: "change name",
 18719  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18720  			newSvc.Name += "2"
 18721  		},
 18722  		numErrs: 1,
 18723  	}, {
 18724  		name: "change namespace",
 18725  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18726  			newSvc.Namespace += "2"
 18727  		},
 18728  		numErrs: 1,
 18729  	}, {
 18730  		name: "change label valid",
 18731  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18732  			newSvc.Labels["key"] = "other-value"
 18733  		},
 18734  		numErrs: 0,
 18735  	}, {
 18736  		name: "add label",
 18737  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18738  			newSvc.Labels["key2"] = "value2"
 18739  		},
 18740  		numErrs: 0,
 18741  	}, {
 18742  		name: "change cluster IP",
 18743  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18744  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 18745  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18746  
 18747  			newSvc.Spec.ClusterIP = "8.6.7.5"
 18748  			newSvc.Spec.ClusterIPs = []string{"8.6.7.5"}
 18749  		},
 18750  		numErrs: 1,
 18751  	}, {
 18752  		name: "remove cluster IP",
 18753  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18754  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 18755  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18756  
 18757  			newSvc.Spec.ClusterIP = ""
 18758  			newSvc.Spec.ClusterIPs = nil
 18759  		},
 18760  		numErrs: 1,
 18761  	}, {
 18762  		name: "change affinity",
 18763  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18764  			newSvc.Spec.SessionAffinity = "ClientIP"
 18765  			newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
 18766  				ClientIP: &core.ClientIPConfig{
 18767  					TimeoutSeconds: utilpointer.Int32(90),
 18768  				},
 18769  			}
 18770  		},
 18771  		numErrs: 0,
 18772  	}, {
 18773  		name: "remove affinity",
 18774  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18775  			newSvc.Spec.SessionAffinity = ""
 18776  		},
 18777  		numErrs: 1,
 18778  	}, {
 18779  		name: "change type",
 18780  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18781  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18782  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18783  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18784  		},
 18785  		numErrs: 0,
 18786  	}, {
 18787  		name: "remove type",
 18788  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18789  			newSvc.Spec.Type = ""
 18790  		},
 18791  		numErrs: 1,
 18792  	}, {
 18793  		name: "change type -> nodeport",
 18794  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18795  			newSvc.Spec.Type = core.ServiceTypeNodePort
 18796  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18797  		},
 18798  		numErrs: 0,
 18799  	}, {
 18800  		name: "add loadBalancerSourceRanges",
 18801  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18802  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18803  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18804  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18805  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18806  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18807  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 18808  		},
 18809  		numErrs: 0,
 18810  	}, {
 18811  		name: "update loadBalancerSourceRanges",
 18812  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18813  			oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18814  			oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18815  			oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
 18816  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18817  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18818  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18819  			newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"}
 18820  		},
 18821  		numErrs: 0,
 18822  	}, {
 18823  		name: "LoadBalancer type cannot have None ClusterIP",
 18824  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18825  			newSvc.Spec.ClusterIP = "None"
 18826  			newSvc.Spec.ClusterIPs = []string{"None"}
 18827  			newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18828  			newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18829  			newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18830  		},
 18831  		numErrs: 1,
 18832  	}, {
 18833  		name: "`None` ClusterIP can NOT be changed",
 18834  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18835  			oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18836  			newSvc.Spec.Type = core.ServiceTypeClusterIP
 18837  
 18838  			oldSvc.Spec.ClusterIP = "None"
 18839  			oldSvc.Spec.ClusterIPs = []string{"None"}
 18840  
 18841  			newSvc.Spec.ClusterIP = "1.2.3.4"
 18842  			newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18843  		},
 18844  		numErrs: 1,
 18845  	}, {
 18846  		name: "`None` ClusterIP can NOT be removed",
 18847  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18848  			oldSvc.Spec.ClusterIP = "None"
 18849  			oldSvc.Spec.ClusterIPs = []string{"None"}
 18850  
 18851  			newSvc.Spec.ClusterIP = ""
 18852  			newSvc.Spec.ClusterIPs = nil
 18853  		},
 18854  		numErrs: 1,
 18855  	}, {
 18856  		name: "ClusterIP can NOT be changed to None",
 18857  		tweakSvc: func(oldSvc, newSvc *core.Service) {
 18858  			oldSvc.Spec.ClusterIP = "1.2.3.4"
 18859  			oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18860  
 18861  			newSvc.Spec.ClusterIP = "None"
 18862  			newSvc.Spec.ClusterIPs = []string{"None"}
 18863  		},
 18864  		numErrs: 1,
 18865  	},
 18866  
 18867  		{
 18868  			name: "Service with ClusterIP type cannot change its set ClusterIP",
 18869  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18870  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18871  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 18872  
 18873  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 18874  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18875  
 18876  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18877  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18878  			},
 18879  			numErrs: 1,
 18880  		}, {
 18881  			name: "Service with ClusterIP type can change its empty ClusterIP",
 18882  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18883  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18884  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 18885  
 18886  				oldSvc.Spec.ClusterIP = ""
 18887  				oldSvc.Spec.ClusterIPs = nil
 18888  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18889  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18890  			},
 18891  			numErrs: 0,
 18892  		}, {
 18893  			name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort",
 18894  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18895  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18896  				newSvc.Spec.Type = core.ServiceTypeNodePort
 18897  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18898  
 18899  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 18900  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18901  
 18902  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18903  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18904  			},
 18905  			numErrs: 1,
 18906  		}, {
 18907  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort",
 18908  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18909  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18910  				newSvc.Spec.Type = core.ServiceTypeNodePort
 18911  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18912  
 18913  				oldSvc.Spec.ClusterIP = ""
 18914  				oldSvc.Spec.ClusterIPs = nil
 18915  
 18916  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18917  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18918  			},
 18919  			numErrs: 0,
 18920  		}, {
 18921  			name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer",
 18922  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18923  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18924  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18925  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18926  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18927  
 18928  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 18929  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18930  
 18931  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18932  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18933  			},
 18934  			numErrs: 1,
 18935  		}, {
 18936  			name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer",
 18937  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18938  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 18939  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18940  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18941  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18942  
 18943  				oldSvc.Spec.ClusterIP = ""
 18944  				oldSvc.Spec.ClusterIPs = nil
 18945  
 18946  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18947  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18948  			},
 18949  			numErrs: 0,
 18950  		}, {
 18951  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from true to false",
 18952  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18953  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18954  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18955  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18956  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18957  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 18958  			},
 18959  			numErrs: 0,
 18960  		}, {
 18961  			name: "Service with LoadBalancer type can change its AllocateLoadBalancerNodePorts from false to true",
 18962  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18963  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18964  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false)
 18965  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 18966  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18967  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 18968  			},
 18969  			numErrs: 0,
 18970  		}, {
 18971  			name: "Service with NodePort type cannot change its set ClusterIP",
 18972  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18973  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 18974  				newSvc.Spec.Type = core.ServiceTypeNodePort
 18975  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18976  
 18977  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 18978  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 18979  
 18980  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18981  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18982  			},
 18983  			numErrs: 1,
 18984  		}, {
 18985  			name: "Service with NodePort type can change its empty ClusterIP",
 18986  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 18987  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 18988  				newSvc.Spec.Type = core.ServiceTypeNodePort
 18989  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 18990  
 18991  				oldSvc.Spec.ClusterIP = ""
 18992  				oldSvc.Spec.ClusterIPs = nil
 18993  
 18994  				newSvc.Spec.ClusterIP = "1.2.3.5"
 18995  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 18996  			},
 18997  			numErrs: 0,
 18998  		}, {
 18999  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP",
 19000  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19001  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19002  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19003  
 19004  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19005  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19006  
 19007  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19008  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19009  			},
 19010  			numErrs: 1,
 19011  		}, {
 19012  			name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP",
 19013  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19014  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19015  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19016  
 19017  				oldSvc.Spec.ClusterIP = ""
 19018  				oldSvc.Spec.ClusterIPs = nil
 19019  
 19020  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19021  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19022  			},
 19023  			numErrs: 0,
 19024  		}, {
 19025  			name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer",
 19026  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19027  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19028  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19029  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19030  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19031  
 19032  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19033  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19034  
 19035  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19036  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19037  			},
 19038  			numErrs: 1,
 19039  		}, {
 19040  			name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer",
 19041  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19042  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19043  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19044  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19045  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19046  
 19047  				oldSvc.Spec.ClusterIP = ""
 19048  				oldSvc.Spec.ClusterIPs = nil
 19049  
 19050  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19051  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19052  			},
 19053  			numErrs: 0,
 19054  		}, {
 19055  			name: "Service with LoadBalancer type cannot change its set ClusterIP",
 19056  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19057  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19058  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19059  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19060  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19061  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19062  
 19063  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19064  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19065  
 19066  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19067  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19068  			},
 19069  			numErrs: 1,
 19070  		}, {
 19071  			name: "Service with LoadBalancer type can change its empty ClusterIP",
 19072  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19073  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19074  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19075  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19076  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19077  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19078  
 19079  				oldSvc.Spec.ClusterIP = ""
 19080  				oldSvc.Spec.ClusterIPs = nil
 19081  
 19082  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19083  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19084  			},
 19085  			numErrs: 0,
 19086  		}, {
 19087  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP",
 19088  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19089  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19090  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19091  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19092  
 19093  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19094  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19095  
 19096  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19097  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19098  			},
 19099  			numErrs: 1,
 19100  		}, {
 19101  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP",
 19102  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19103  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19104  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19105  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19106  
 19107  				oldSvc.Spec.ClusterIP = ""
 19108  				oldSvc.Spec.ClusterIPs = nil
 19109  
 19110  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19111  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19112  			},
 19113  			numErrs: 0,
 19114  		}, {
 19115  			name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort",
 19116  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19117  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19118  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19119  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19120  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19121  
 19122  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19123  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19124  
 19125  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19126  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19127  			},
 19128  			numErrs: 1,
 19129  		}, {
 19130  			name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort",
 19131  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19132  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19133  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19134  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19135  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19136  
 19137  				oldSvc.Spec.ClusterIP = ""
 19138  				oldSvc.Spec.ClusterIPs = nil
 19139  
 19140  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19141  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19142  			},
 19143  			numErrs: 0,
 19144  		}, {
 19145  			name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP",
 19146  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19147  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19148  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19149  
 19150  				oldSvc.Spec.ClusterIP = ""
 19151  				oldSvc.Spec.ClusterIPs = nil
 19152  
 19153  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19154  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19155  			},
 19156  			numErrs: 0,
 19157  		}, {
 19158  			name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP",
 19159  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19160  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19161  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19162  
 19163  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19164  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19165  
 19166  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19167  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19168  			},
 19169  			numErrs: 0,
 19170  		}, {
 19171  			name: "invalid node port with clusterIP None",
 19172  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19173  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19174  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19175  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19176  
 19177  				oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 19178  				newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt32(1)})
 19179  
 19180  				oldSvc.Spec.ClusterIP = ""
 19181  				oldSvc.Spec.ClusterIPs = nil
 19182  
 19183  				newSvc.Spec.ClusterIP = "None"
 19184  				newSvc.Spec.ClusterIPs = []string{"None"}
 19185  			},
 19186  			numErrs: 1,
 19187  		},
 19188  		/* Service IP Family */
 19189  		{
 19190  			name: "convert from ExternalName",
 19191  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19192  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19193  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19194  			},
 19195  			numErrs: 0,
 19196  		}, {
 19197  			name: "invalid: convert to ExternalName",
 19198  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19199  				singleStack := core.IPFamilyPolicySingleStack
 19200  
 19201  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19202  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 19203  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 19204  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19205  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19206  
 19207  				newSvc.Spec.Type = core.ServiceTypeExternalName
 19208  				newSvc.Spec.ExternalName = "foo"
 19209  				/*
 19210  					not removing these fields is a validation error
 19211  					strategy takes care of resetting Families & Policy if ClusterIPs
 19212  					were reset. But it does not get called in validation testing.
 19213  				*/
 19214  				newSvc.Spec.ClusterIP = "10.0.0.10"
 19215  				newSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 19216  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19217  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19218  
 19219  			},
 19220  			numErrs: 3,
 19221  		}, {
 19222  			name: "valid: convert to ExternalName",
 19223  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19224  				singleStack := core.IPFamilyPolicySingleStack
 19225  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19226  				oldSvc.Spec.ClusterIP = "10.0.0.10"
 19227  				oldSvc.Spec.ClusterIPs = []string{"10.0.0.10"}
 19228  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19229  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19230  
 19231  				newSvc.Spec.Type = core.ServiceTypeExternalName
 19232  				newSvc.Spec.ExternalName = "foo"
 19233  			},
 19234  			numErrs: 0,
 19235  		},
 19236  
 19237  		{
 19238  			name: "same ServiceIPFamily",
 19239  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19240  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19241  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19242  
 19243  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19244  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19245  			},
 19246  			numErrs: 0,
 19247  		}, {
 19248  			name: "same ServiceIPFamily, change IPFamilyPolicy to singleStack",
 19249  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19250  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19251  				oldSvc.Spec.IPFamilyPolicy = nil
 19252  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19253  
 19254  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19255  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19256  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19257  			},
 19258  			numErrs: 0,
 19259  		}, {
 19260  			name: "same ServiceIPFamily, change IPFamilyPolicy singleStack => requireDualStack",
 19261  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19262  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19263  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19264  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19265  
 19266  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19267  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19268  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19269  			},
 19270  			numErrs: 0,
 19271  		},
 19272  
 19273  		{
 19274  			name: "add a new ServiceIPFamily",
 19275  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19276  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19277  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19278  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19279  
 19280  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19281  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19282  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19283  			},
 19284  			numErrs: 0,
 19285  		},
 19286  
 19287  		{
 19288  			name: "ExternalName while changing Service IPFamily",
 19289  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19290  				oldSvc.Spec.ExternalName = "somename"
 19291  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19292  
 19293  				newSvc.Spec.ExternalName = "somename"
 19294  				newSvc.Spec.Type = core.ServiceTypeExternalName
 19295  			},
 19296  			numErrs: 0,
 19297  		}, {
 19298  			name: "setting ipfamily from nil to v4",
 19299  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19300  				oldSvc.Spec.IPFamilies = nil
 19301  
 19302  				newSvc.Spec.ExternalName = "somename"
 19303  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19304  			},
 19305  			numErrs: 0,
 19306  		}, {
 19307  			name: "setting ipfamily from nil to v6",
 19308  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19309  				oldSvc.Spec.IPFamilies = nil
 19310  
 19311  				newSvc.Spec.ExternalName = "somename"
 19312  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 19313  			},
 19314  			numErrs: 0,
 19315  		}, {
 19316  			name: "change primary ServiceIPFamily",
 19317  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19318  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19319  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19320  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19321  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19322  
 19323  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19324  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19325  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19326  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 19327  			},
 19328  			numErrs: 2,
 19329  		},
 19330  		/* upgrade + downgrade from/to dualstack tests */
 19331  		{
 19332  			name: "valid: upgrade to dual stack with requiredDualStack",
 19333  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19334  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19335  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19336  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19337  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19338  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19339  
 19340  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19341  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19342  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19343  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19344  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19345  			},
 19346  			numErrs: 0,
 19347  		}, {
 19348  			name: "valid: upgrade to dual stack with preferDualStack",
 19349  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19350  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19351  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19352  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19353  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19354  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19355  
 19356  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19357  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19358  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19359  				newSvc.Spec.IPFamilyPolicy = &preferDualStack
 19360  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19361  			},
 19362  			numErrs: 0,
 19363  		},
 19364  
 19365  		{
 19366  			name: "valid: upgrade to dual stack, no specific secondary ip",
 19367  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19368  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19369  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19370  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19371  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19372  
 19373  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19374  
 19375  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19376  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19377  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19378  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19379  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19380  			},
 19381  			numErrs: 0,
 19382  		}, {
 19383  			name: "valid: upgrade to dual stack, with specific secondary ip",
 19384  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19385  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19386  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19387  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19388  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19389  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19390  
 19391  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19392  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19393  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19394  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19395  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19396  			},
 19397  			numErrs: 0,
 19398  		}, {
 19399  			name: "valid: downgrade from dual to single",
 19400  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19401  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19402  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19403  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19404  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19405  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19406  
 19407  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19408  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19409  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19410  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19411  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19412  			},
 19413  			numErrs: 0,
 19414  		}, {
 19415  			name: "valid: change families for a headless service",
 19416  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19417  				oldSvc.Spec.ClusterIP = "None"
 19418  				oldSvc.Spec.ClusterIPs = []string{"None"}
 19419  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19420  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19421  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19422  
 19423  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19424  				newSvc.Spec.ClusterIP = "None"
 19425  				newSvc.Spec.ClusterIPs = []string{"None"}
 19426  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19427  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 19428  			},
 19429  			numErrs: 0,
 19430  		}, {
 19431  			name: "valid: upgrade a headless service",
 19432  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19433  				oldSvc.Spec.ClusterIP = "None"
 19434  				oldSvc.Spec.ClusterIPs = []string{"None"}
 19435  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19436  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19437  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19438  
 19439  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19440  				newSvc.Spec.ClusterIP = "None"
 19441  				newSvc.Spec.ClusterIPs = []string{"None"}
 19442  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19443  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 19444  			},
 19445  			numErrs: 0,
 19446  		}, {
 19447  			name: "valid: downgrade a headless service",
 19448  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19449  				oldSvc.Spec.ClusterIP = "None"
 19450  				oldSvc.Spec.ClusterIPs = []string{"None"}
 19451  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19452  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19453  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19454  
 19455  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19456  				newSvc.Spec.ClusterIP = "None"
 19457  				newSvc.Spec.ClusterIPs = []string{"None"}
 19458  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19459  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol}
 19460  			},
 19461  			numErrs: 0,
 19462  		},
 19463  
 19464  		{
 19465  			name: "invalid flip families",
 19466  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19467  				oldSvc.Spec.ClusterIP = "1.2.3.40"
 19468  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19469  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19470  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19471  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19472  
 19473  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19474  				newSvc.Spec.ClusterIP = "2001::1"
 19475  				newSvc.Spec.ClusterIPs = []string{"2001::1", "1.2.3.5"}
 19476  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19477  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv6Protocol, core.IPv4Protocol}
 19478  			},
 19479  			numErrs: 4,
 19480  		}, {
 19481  			name: "invalid change first ip, in dualstack service",
 19482  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19483  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19484  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19485  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19486  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19487  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19488  
 19489  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19490  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19491  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5", "2001::1"}
 19492  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19493  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19494  			},
 19495  			numErrs: 1,
 19496  		}, {
 19497  			name: "invalid, change second ip in dualstack service",
 19498  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19499  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19500  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19501  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19502  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19503  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19504  
 19505  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19506  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19507  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2002::1"}
 19508  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19509  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19510  			},
 19511  			numErrs: 1,
 19512  		}, {
 19513  			name: "downgrade keeping the families",
 19514  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19515  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19516  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19517  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19518  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19519  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19520  
 19521  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19522  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19523  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19524  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19525  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19526  			},
 19527  			numErrs: 0, // families and ips are trimmed in strategy
 19528  		}, {
 19529  			name: "invalid, downgrade without changing to singleStack",
 19530  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19531  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19532  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19533  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19534  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19535  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19536  
 19537  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19538  				newSvc.Spec.ClusterIP = "1.2.3.4"
 19539  				newSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19540  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19541  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19542  			},
 19543  			numErrs: 2,
 19544  		}, {
 19545  			name: "invalid, downgrade and change primary ip",
 19546  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19547  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19548  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4", "2001::1"}
 19549  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19550  				oldSvc.Spec.IPFamilyPolicy = &requireDualStack
 19551  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19552  
 19553  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19554  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19555  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19556  				newSvc.Spec.IPFamilyPolicy = &singleStack
 19557  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19558  			},
 19559  			numErrs: 1,
 19560  		}, {
 19561  			name: "invalid: upgrade to dual stack and change primary",
 19562  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19563  				oldSvc.Spec.ClusterIP = "1.2.3.4"
 19564  				oldSvc.Spec.ClusterIPs = []string{"1.2.3.4"}
 19565  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19566  				oldSvc.Spec.IPFamilyPolicy = &singleStack
 19567  
 19568  				oldSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol}
 19569  
 19570  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19571  				newSvc.Spec.ClusterIP = "1.2.3.5"
 19572  				newSvc.Spec.ClusterIPs = []string{"1.2.3.5"}
 19573  				newSvc.Spec.IPFamilyPolicy = &requireDualStack
 19574  				newSvc.Spec.IPFamilies = []core.IPFamily{core.IPv4Protocol, core.IPv6Protocol}
 19575  			},
 19576  			numErrs: 1,
 19577  		}, {
 19578  			name: "update to valid app protocol",
 19579  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19580  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 19581  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 19582  			},
 19583  			numErrs: 0,
 19584  		}, {
 19585  			name: "update to invalid app protocol",
 19586  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19587  				oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP"}}
 19588  				newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt32(3000), Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 19589  			},
 19590  			numErrs: 1,
 19591  		}, {
 19592  			name: "Set AllocateLoadBalancerNodePorts when type is not LoadBalancer",
 19593  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19594  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19595  			},
 19596  			numErrs: 1,
 19597  		}, {
 19598  			name: "update LoadBalancer type of service without change LoadBalancerClass",
 19599  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19600  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19601  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19602  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 19603  
 19604  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19605  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19606  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19607  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 19608  			},
 19609  			numErrs: 0,
 19610  		}, {
 19611  			name: "invalid: change LoadBalancerClass when update service",
 19612  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19613  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19614  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19615  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 19616  
 19617  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19618  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19619  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19620  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 19621  			},
 19622  			numErrs: 1,
 19623  		}, {
 19624  			name: "invalid: unset LoadBalancerClass when update service",
 19625  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19626  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19627  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19628  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-old")
 19629  
 19630  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19631  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19632  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19633  				newSvc.Spec.LoadBalancerClass = nil
 19634  			},
 19635  			numErrs: 1,
 19636  		}, {
 19637  			name: "invalid: set LoadBalancerClass when update service",
 19638  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19639  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19640  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19641  				oldSvc.Spec.LoadBalancerClass = nil
 19642  
 19643  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19644  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19645  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19646  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-new")
 19647  			},
 19648  			numErrs: 1,
 19649  		}, {
 19650  			name: "update to LoadBalancer type of service with valid LoadBalancerClass",
 19651  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19652  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19653  
 19654  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19655  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19656  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19657  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19658  			},
 19659  			numErrs: 0,
 19660  		}, {
 19661  			name: "update to LoadBalancer type of service without LoadBalancerClass",
 19662  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19663  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19664  
 19665  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19666  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19667  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19668  				newSvc.Spec.LoadBalancerClass = nil
 19669  			},
 19670  			numErrs: 0,
 19671  		}, {
 19672  			name: "invalid: set invalid LoadBalancerClass when update service to LoadBalancer",
 19673  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19674  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19675  
 19676  				newSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19677  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19678  				newSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19679  				newSvc.Spec.LoadBalancerClass = utilpointer.String("Bad/LoadBalancerclass")
 19680  			},
 19681  			numErrs: 2,
 19682  		}, {
 19683  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 19684  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19685  				oldSvc.Spec.Type = core.ServiceTypeClusterIP
 19686  
 19687  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19688  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19689  			},
 19690  			numErrs: 2,
 19691  		}, {
 19692  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 19693  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19694  				oldSvc.Spec.Type = core.ServiceTypeExternalName
 19695  
 19696  				newSvc.Spec.Type = core.ServiceTypeExternalName
 19697  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19698  			},
 19699  			numErrs: 3,
 19700  		}, {
 19701  			name: "invalid: set LoadBalancerClass when update service to non LoadBalancer type of service",
 19702  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19703  				oldSvc.Spec.Type = core.ServiceTypeNodePort
 19704  
 19705  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19706  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19707  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19708  			},
 19709  			numErrs: 2,
 19710  		}, {
 19711  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 19712  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19713  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19714  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19715  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19716  
 19717  				newSvc.Spec.Type = core.ServiceTypeClusterIP
 19718  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19719  			},
 19720  			numErrs: 2,
 19721  		}, {
 19722  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 19723  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19724  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19725  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19726  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19727  
 19728  				newSvc.Spec.Type = core.ServiceTypeExternalName
 19729  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19730  			},
 19731  			numErrs: 3,
 19732  		}, {
 19733  			name: "invalid: set LoadBalancerClass when update from LoadBalancer service to non LoadBalancer type of service",
 19734  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19735  				oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
 19736  				oldSvc.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true)
 19737  				oldSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19738  
 19739  				newSvc.Spec.Type = core.ServiceTypeNodePort
 19740  				newSvc.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyCluster
 19741  				newSvc.Spec.LoadBalancerClass = utilpointer.String("test.com/test-load-balancer-class")
 19742  			},
 19743  			numErrs: 2,
 19744  		}, {
 19745  			name: "update internalTrafficPolicy from Cluster to Local",
 19746  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19747  				cluster := core.ServiceInternalTrafficPolicyCluster
 19748  				oldSvc.Spec.InternalTrafficPolicy = &cluster
 19749  
 19750  				local := core.ServiceInternalTrafficPolicyLocal
 19751  				newSvc.Spec.InternalTrafficPolicy = &local
 19752  			},
 19753  			numErrs: 0,
 19754  		}, {
 19755  			name: "update internalTrafficPolicy from Local to Cluster",
 19756  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19757  				local := core.ServiceInternalTrafficPolicyLocal
 19758  				oldSvc.Spec.InternalTrafficPolicy = &local
 19759  
 19760  				cluster := core.ServiceInternalTrafficPolicyCluster
 19761  				newSvc.Spec.InternalTrafficPolicy = &cluster
 19762  			},
 19763  			numErrs: 0,
 19764  		}, {
 19765  			name: "topology annotations are mismatched",
 19766  			tweakSvc: func(oldSvc, newSvc *core.Service) {
 19767  				newSvc.Annotations[core.DeprecatedAnnotationTopologyAwareHints] = "original"
 19768  				newSvc.Annotations[core.AnnotationTopologyMode] = "different"
 19769  			},
 19770  			numErrs: 1,
 19771  		},
 19772  	}
 19773  
 19774  	for _, tc := range testCases {
 19775  		t.Run(tc.name, func(t *testing.T) {
 19776  			oldSvc := makeValidService()
 19777  			newSvc := makeValidService()
 19778  			tc.tweakSvc(&oldSvc, &newSvc)
 19779  			errs := ValidateServiceUpdate(&newSvc, &oldSvc)
 19780  			if len(errs) != tc.numErrs {
 19781  				t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate())
 19782  			}
 19783  		})
 19784  	}
 19785  }
 19786  
 19787  func TestValidateResourceNames(t *testing.T) {
 19788  	table := []struct {
 19789  		input   core.ResourceName
 19790  		success bool
 19791  		expect  string
 19792  	}{
 19793  		{"memory", true, ""},
 19794  		{"cpu", true, ""},
 19795  		{"storage", true, ""},
 19796  		{"requests.cpu", true, ""},
 19797  		{"requests.memory", true, ""},
 19798  		{"requests.storage", true, ""},
 19799  		{"limits.cpu", true, ""},
 19800  		{"limits.memory", true, ""},
 19801  		{"network", false, ""},
 19802  		{"disk", false, ""},
 19803  		{"", false, ""},
 19804  		{".", false, ""},
 19805  		{"..", false, ""},
 19806  		{"my.favorite.app.co/12345", true, ""},
 19807  		{"my.favorite.app.co/_12345", false, ""},
 19808  		{"my.favorite.app.co/12345_", false, ""},
 19809  		{"kubernetes.io/..", false, ""},
 19810  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)), true, ""},
 19811  		{core.ResourceName("kubernetes.io/" + strings.Repeat("a", 64)), false, ""},
 19812  		{"kubernetes.io//", false, ""},
 19813  		{"kubernetes.io", false, ""},
 19814  		{"kubernetes.io/will/not/work/", false, ""},
 19815  	}
 19816  	for k, item := range table {
 19817  		err := validateResourceName(item.input, field.NewPath("field"))
 19818  		if len(err) != 0 && item.success {
 19819  			t.Errorf("expected no failure for input %q", item.input)
 19820  		} else if len(err) == 0 && !item.success {
 19821  			t.Errorf("expected failure for input %q", item.input)
 19822  			for i := range err {
 19823  				detail := err[i].Detail
 19824  				if detail != "" && !strings.Contains(detail, item.expect) {
 19825  					t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail)
 19826  				}
 19827  			}
 19828  		}
 19829  	}
 19830  }
 19831  
 19832  func getResourceList(cpu, memory string) core.ResourceList {
 19833  	res := core.ResourceList{}
 19834  	if cpu != "" {
 19835  		res[core.ResourceCPU] = resource.MustParse(cpu)
 19836  	}
 19837  	if memory != "" {
 19838  		res[core.ResourceMemory] = resource.MustParse(memory)
 19839  	}
 19840  	return res
 19841  }
 19842  
 19843  func getStorageResourceList(storage string) core.ResourceList {
 19844  	res := core.ResourceList{}
 19845  	if storage != "" {
 19846  		res[core.ResourceStorage] = resource.MustParse(storage)
 19847  	}
 19848  	return res
 19849  }
 19850  
 19851  func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList {
 19852  	res := core.ResourceList{}
 19853  	if ephemeralStorage != "" {
 19854  		res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
 19855  	}
 19856  	return res
 19857  }
 19858  
 19859  func TestValidateLimitRangeForLocalStorage(t *testing.T) {
 19860  	testCases := []struct {
 19861  		name string
 19862  		spec core.LimitRangeSpec
 19863  	}{{
 19864  		name: "all-fields-valid",
 19865  		spec: core.LimitRangeSpec{
 19866  			Limits: []core.LimitRangeItem{{
 19867  				Type:                 core.LimitTypePod,
 19868  				Max:                  getLocalStorageResourceList("10000Mi"),
 19869  				Min:                  getLocalStorageResourceList("100Mi"),
 19870  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 19871  			}, {
 19872  				Type:                 core.LimitTypeContainer,
 19873  				Max:                  getLocalStorageResourceList("10000Mi"),
 19874  				Min:                  getLocalStorageResourceList("100Mi"),
 19875  				Default:              getLocalStorageResourceList("500Mi"),
 19876  				DefaultRequest:       getLocalStorageResourceList("200Mi"),
 19877  				MaxLimitRequestRatio: getLocalStorageResourceList(""),
 19878  			}},
 19879  		},
 19880  	},
 19881  	}
 19882  
 19883  	for _, testCase := range testCases {
 19884  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec}
 19885  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 19886  			t.Errorf("Case %v, unexpected error: %v", testCase.name, errs)
 19887  		}
 19888  	}
 19889  }
 19890  
 19891  func TestValidateLimitRange(t *testing.T) {
 19892  	successCases := []struct {
 19893  		name string
 19894  		spec core.LimitRangeSpec
 19895  	}{{
 19896  		name: "all-fields-valid",
 19897  		spec: core.LimitRangeSpec{
 19898  			Limits: []core.LimitRangeItem{{
 19899  				Type:                 core.LimitTypePod,
 19900  				Max:                  getResourceList("100m", "10000Mi"),
 19901  				Min:                  getResourceList("5m", "100Mi"),
 19902  				MaxLimitRequestRatio: getResourceList("10", ""),
 19903  			}, {
 19904  				Type:                 core.LimitTypeContainer,
 19905  				Max:                  getResourceList("100m", "10000Mi"),
 19906  				Min:                  getResourceList("5m", "100Mi"),
 19907  				Default:              getResourceList("50m", "500Mi"),
 19908  				DefaultRequest:       getResourceList("10m", "200Mi"),
 19909  				MaxLimitRequestRatio: getResourceList("10", ""),
 19910  			}, {
 19911  				Type: core.LimitTypePersistentVolumeClaim,
 19912  				Max:  getStorageResourceList("10Gi"),
 19913  				Min:  getStorageResourceList("5Gi"),
 19914  			}},
 19915  		},
 19916  	}, {
 19917  		name: "pvc-min-only",
 19918  		spec: core.LimitRangeSpec{
 19919  			Limits: []core.LimitRangeItem{{
 19920  				Type: core.LimitTypePersistentVolumeClaim,
 19921  				Min:  getStorageResourceList("5Gi"),
 19922  			}},
 19923  		},
 19924  	}, {
 19925  		name: "pvc-max-only",
 19926  		spec: core.LimitRangeSpec{
 19927  			Limits: []core.LimitRangeItem{{
 19928  				Type: core.LimitTypePersistentVolumeClaim,
 19929  				Max:  getStorageResourceList("10Gi"),
 19930  			}},
 19931  		},
 19932  	}, {
 19933  		name: "all-fields-valid-big-numbers",
 19934  		spec: core.LimitRangeSpec{
 19935  			Limits: []core.LimitRangeItem{{
 19936  				Type:                 core.LimitTypeContainer,
 19937  				Max:                  getResourceList("100m", "10000T"),
 19938  				Min:                  getResourceList("5m", "100Mi"),
 19939  				Default:              getResourceList("50m", "500Mi"),
 19940  				DefaultRequest:       getResourceList("10m", "200Mi"),
 19941  				MaxLimitRequestRatio: getResourceList("10", ""),
 19942  			}},
 19943  		},
 19944  	}, {
 19945  		name: "thirdparty-fields-all-valid-standard-container-resources",
 19946  		spec: core.LimitRangeSpec{
 19947  			Limits: []core.LimitRangeItem{{
 19948  				Type:                 "thirdparty.com/foo",
 19949  				Max:                  getResourceList("100m", "10000T"),
 19950  				Min:                  getResourceList("5m", "100Mi"),
 19951  				Default:              getResourceList("50m", "500Mi"),
 19952  				DefaultRequest:       getResourceList("10m", "200Mi"),
 19953  				MaxLimitRequestRatio: getResourceList("10", ""),
 19954  			}},
 19955  		},
 19956  	}, {
 19957  		name: "thirdparty-fields-all-valid-storage-resources",
 19958  		spec: core.LimitRangeSpec{
 19959  			Limits: []core.LimitRangeItem{{
 19960  				Type:                 "thirdparty.com/foo",
 19961  				Max:                  getStorageResourceList("10000T"),
 19962  				Min:                  getStorageResourceList("100Mi"),
 19963  				Default:              getStorageResourceList("500Mi"),
 19964  				DefaultRequest:       getStorageResourceList("200Mi"),
 19965  				MaxLimitRequestRatio: getStorageResourceList(""),
 19966  			}},
 19967  		},
 19968  	},
 19969  	}
 19970  
 19971  	for _, successCase := range successCases {
 19972  		limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec}
 19973  		if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
 19974  			t.Errorf("Case %v, unexpected error: %v", successCase.name, errs)
 19975  		}
 19976  	}
 19977  
 19978  	errorCases := map[string]struct {
 19979  		R core.LimitRange
 19980  		D string
 19981  	}{
 19982  		"zero-length-name": {
 19983  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 19984  			"name or generateName is required",
 19985  		},
 19986  		"zero-length-namespace": {
 19987  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}},
 19988  			"",
 19989  		},
 19990  		"invalid-name": {
 19991  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
 19992  			dnsSubdomainLabelErrMsg,
 19993  		},
 19994  		"invalid-namespace": {
 19995  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}},
 19996  			dnsLabelErrMsg,
 19997  		},
 19998  		"duplicate-limit-type": {
 19999  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20000  				Limits: []core.LimitRangeItem{{
 20001  					Type: core.LimitTypePod,
 20002  					Max:  getResourceList("100m", "10000m"),
 20003  					Min:  getResourceList("0m", "100m"),
 20004  				}, {
 20005  					Type: core.LimitTypePod,
 20006  					Min:  getResourceList("0m", "100m"),
 20007  				}},
 20008  			}},
 20009  			"",
 20010  		},
 20011  		"default-limit-type-pod": {
 20012  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20013  				Limits: []core.LimitRangeItem{{
 20014  					Type:    core.LimitTypePod,
 20015  					Max:     getResourceList("100m", "10000m"),
 20016  					Min:     getResourceList("0m", "100m"),
 20017  					Default: getResourceList("10m", "100m"),
 20018  				}},
 20019  			}},
 20020  			"may not be specified when `type` is 'Pod'",
 20021  		},
 20022  		"default-request-limit-type-pod": {
 20023  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20024  				Limits: []core.LimitRangeItem{{
 20025  					Type:           core.LimitTypePod,
 20026  					Max:            getResourceList("100m", "10000m"),
 20027  					Min:            getResourceList("0m", "100m"),
 20028  					DefaultRequest: getResourceList("10m", "100m"),
 20029  				}},
 20030  			}},
 20031  			"may not be specified when `type` is 'Pod'",
 20032  		},
 20033  		"min value 100m is greater than max value 10m": {
 20034  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20035  				Limits: []core.LimitRangeItem{{
 20036  					Type: core.LimitTypePod,
 20037  					Max:  getResourceList("10m", ""),
 20038  					Min:  getResourceList("100m", ""),
 20039  				}},
 20040  			}},
 20041  			"min value 100m is greater than max value 10m",
 20042  		},
 20043  		"invalid spec default outside range": {
 20044  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20045  				Limits: []core.LimitRangeItem{{
 20046  					Type:    core.LimitTypeContainer,
 20047  					Max:     getResourceList("1", ""),
 20048  					Min:     getResourceList("100m", ""),
 20049  					Default: getResourceList("2000m", ""),
 20050  				}},
 20051  			}},
 20052  			"default value 2 is greater than max value 1",
 20053  		},
 20054  		"invalid spec default request outside range": {
 20055  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20056  				Limits: []core.LimitRangeItem{{
 20057  					Type:           core.LimitTypeContainer,
 20058  					Max:            getResourceList("1", ""),
 20059  					Min:            getResourceList("100m", ""),
 20060  					DefaultRequest: getResourceList("2000m", ""),
 20061  				}},
 20062  			}},
 20063  			"default request value 2 is greater than max value 1",
 20064  		},
 20065  		"invalid spec default request more than default": {
 20066  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20067  				Limits: []core.LimitRangeItem{{
 20068  					Type:           core.LimitTypeContainer,
 20069  					Max:            getResourceList("2", ""),
 20070  					Min:            getResourceList("100m", ""),
 20071  					Default:        getResourceList("500m", ""),
 20072  					DefaultRequest: getResourceList("800m", ""),
 20073  				}},
 20074  			}},
 20075  			"default request value 800m is greater than default limit value 500m",
 20076  		},
 20077  		"invalid spec maxLimitRequestRatio less than 1": {
 20078  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20079  				Limits: []core.LimitRangeItem{{
 20080  					Type:                 core.LimitTypePod,
 20081  					MaxLimitRequestRatio: getResourceList("800m", ""),
 20082  				}},
 20083  			}},
 20084  			"ratio 800m is less than 1",
 20085  		},
 20086  		"invalid spec maxLimitRequestRatio greater than max/min": {
 20087  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20088  				Limits: []core.LimitRangeItem{{
 20089  					Type:                 core.LimitTypeContainer,
 20090  					Max:                  getResourceList("", "2Gi"),
 20091  					Min:                  getResourceList("", "512Mi"),
 20092  					MaxLimitRequestRatio: getResourceList("", "10"),
 20093  				}},
 20094  			}},
 20095  			"ratio 10 is greater than max/min = 4.000000",
 20096  		},
 20097  		"invalid non standard limit type": {
 20098  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20099  				Limits: []core.LimitRangeItem{{
 20100  					Type:                 "foo",
 20101  					Max:                  getStorageResourceList("10000T"),
 20102  					Min:                  getStorageResourceList("100Mi"),
 20103  					Default:              getStorageResourceList("500Mi"),
 20104  					DefaultRequest:       getStorageResourceList("200Mi"),
 20105  					MaxLimitRequestRatio: getStorageResourceList(""),
 20106  				}},
 20107  			}},
 20108  			"must be a standard limit type or fully qualified",
 20109  		},
 20110  		"min and max values missing, one required": {
 20111  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20112  				Limits: []core.LimitRangeItem{{
 20113  					Type: core.LimitTypePersistentVolumeClaim,
 20114  				}},
 20115  			}},
 20116  			"either minimum or maximum storage value is required, but neither was provided",
 20117  		},
 20118  		"invalid min greater than max": {
 20119  			core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
 20120  				Limits: []core.LimitRangeItem{{
 20121  					Type: core.LimitTypePersistentVolumeClaim,
 20122  					Min:  getStorageResourceList("10Gi"),
 20123  					Max:  getStorageResourceList("1Gi"),
 20124  				}},
 20125  			}},
 20126  			"min value 10Gi is greater than max value 1Gi",
 20127  		},
 20128  	}
 20129  
 20130  	for k, v := range errorCases {
 20131  		errs := ValidateLimitRange(&v.R)
 20132  		if len(errs) == 0 {
 20133  			t.Errorf("expected failure for %s", k)
 20134  		}
 20135  		for i := range errs {
 20136  			detail := errs[i].Detail
 20137  			if !strings.Contains(detail, v.D) {
 20138  				t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
 20139  			}
 20140  		}
 20141  	}
 20142  
 20143  }
 20144  
 20145  func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
 20146  	validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
 20147  		AccessModes: []core.PersistentVolumeAccessMode{
 20148  			core.ReadWriteOnce,
 20149  			core.ReadOnlyMany,
 20150  		},
 20151  		Resources: core.VolumeResourceRequirements{
 20152  			Requests: core.ResourceList{
 20153  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20154  			},
 20155  		},
 20156  	})
 20157  	validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20158  		AccessModes: []core.PersistentVolumeAccessMode{
 20159  			core.ReadWriteOnce,
 20160  			core.ReadOnlyMany,
 20161  		},
 20162  		Resources: core.VolumeResourceRequirements{
 20163  			Requests: core.ResourceList{
 20164  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20165  			},
 20166  		},
 20167  	}, core.PersistentVolumeClaimStatus{
 20168  		Phase: core.ClaimPending,
 20169  		Conditions: []core.PersistentVolumeClaimCondition{
 20170  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 20171  		},
 20172  	})
 20173  	validAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20174  		AccessModes: []core.PersistentVolumeAccessMode{
 20175  			core.ReadWriteOnce,
 20176  			core.ReadOnlyMany,
 20177  		},
 20178  		Resources: core.VolumeResourceRequirements{
 20179  			Requests: core.ResourceList{
 20180  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20181  			},
 20182  		},
 20183  	}, core.PersistentVolumeClaimStatus{
 20184  		Phase: core.ClaimPending,
 20185  		Conditions: []core.PersistentVolumeClaimCondition{
 20186  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 20187  		},
 20188  		AllocatedResources: core.ResourceList{
 20189  			core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20190  		},
 20191  	})
 20192  
 20193  	invalidAllocatedResources := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20194  		AccessModes: []core.PersistentVolumeAccessMode{
 20195  			core.ReadWriteOnce,
 20196  			core.ReadOnlyMany,
 20197  		},
 20198  		Resources: core.VolumeResourceRequirements{
 20199  			Requests: core.ResourceList{
 20200  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20201  			},
 20202  		},
 20203  	}, core.PersistentVolumeClaimStatus{
 20204  		Phase: core.ClaimPending,
 20205  		Conditions: []core.PersistentVolumeClaimCondition{
 20206  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 20207  		},
 20208  		AllocatedResources: core.ResourceList{
 20209  			core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
 20210  		},
 20211  	})
 20212  
 20213  	noStoraegeClaimStatus := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20214  		AccessModes: []core.PersistentVolumeAccessMode{
 20215  			core.ReadWriteOnce,
 20216  		},
 20217  		Resources: core.VolumeResourceRequirements{
 20218  			Requests: core.ResourceList{
 20219  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20220  			},
 20221  		},
 20222  	}, core.PersistentVolumeClaimStatus{
 20223  		Phase: core.ClaimPending,
 20224  		AllocatedResources: core.ResourceList{
 20225  			core.ResourceName(core.ResourceCPU): resource.MustParse("10G"),
 20226  		},
 20227  	})
 20228  	progressResizeStatus := core.PersistentVolumeClaimControllerResizeInProgress
 20229  
 20230  	invalidResizeStatus := core.ClaimResourceStatus("foo")
 20231  	validResizeKeyCustom := core.ResourceName("example.com/foo")
 20232  	invalidNativeResizeKey := core.ResourceName("kubernetes.io/foo")
 20233  
 20234  	validResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20235  		AccessModes: []core.PersistentVolumeAccessMode{
 20236  			core.ReadWriteOnce,
 20237  		},
 20238  	}, core.PersistentVolumeClaimStatus{
 20239  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20240  			core.ResourceStorage: progressResizeStatus,
 20241  		},
 20242  	})
 20243  
 20244  	validResizeStatusControllerResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20245  		AccessModes: []core.PersistentVolumeAccessMode{
 20246  			core.ReadWriteOnce,
 20247  		},
 20248  	}, core.PersistentVolumeClaimStatus{
 20249  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20250  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 20251  		},
 20252  	})
 20253  
 20254  	validNodeResizePending := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20255  		AccessModes: []core.PersistentVolumeAccessMode{
 20256  			core.ReadWriteOnce,
 20257  		},
 20258  	}, core.PersistentVolumeClaimStatus{
 20259  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20260  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizePending,
 20261  		},
 20262  	})
 20263  
 20264  	validNodeResizeInProgress := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20265  		AccessModes: []core.PersistentVolumeAccessMode{
 20266  			core.ReadWriteOnce,
 20267  		},
 20268  	}, core.PersistentVolumeClaimStatus{
 20269  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20270  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeInProgress,
 20271  		},
 20272  	})
 20273  
 20274  	validNodeResizeFailed := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20275  		AccessModes: []core.PersistentVolumeAccessMode{
 20276  			core.ReadWriteOnce,
 20277  		},
 20278  	}, core.PersistentVolumeClaimStatus{
 20279  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20280  			core.ResourceStorage: core.PersistentVolumeClaimNodeResizeFailed,
 20281  		},
 20282  	})
 20283  
 20284  	invalidResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20285  		AccessModes: []core.PersistentVolumeAccessMode{
 20286  			core.ReadWriteOnce,
 20287  		},
 20288  	}, core.PersistentVolumeClaimStatus{
 20289  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20290  			core.ResourceStorage: invalidResizeStatus,
 20291  		},
 20292  	})
 20293  
 20294  	invalidNativeResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20295  		AccessModes: []core.PersistentVolumeAccessMode{
 20296  			core.ReadWriteOnce,
 20297  		},
 20298  	}, core.PersistentVolumeClaimStatus{
 20299  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20300  			invalidNativeResizeKey: core.PersistentVolumeClaimNodeResizePending,
 20301  		},
 20302  	})
 20303  
 20304  	validExternalResizeStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20305  		AccessModes: []core.PersistentVolumeAccessMode{
 20306  			core.ReadWriteOnce,
 20307  		},
 20308  	}, core.PersistentVolumeClaimStatus{
 20309  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20310  			validResizeKeyCustom: core.PersistentVolumeClaimNodeResizePending,
 20311  		},
 20312  	})
 20313  
 20314  	multipleResourceStatusPVC := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20315  		AccessModes: []core.PersistentVolumeAccessMode{
 20316  			core.ReadWriteOnce,
 20317  		},
 20318  	}, core.PersistentVolumeClaimStatus{
 20319  		AllocatedResources: core.ResourceList{
 20320  			core.ResourceStorage: resource.MustParse("5Gi"),
 20321  			validResizeKeyCustom: resource.MustParse("10Gi"),
 20322  		},
 20323  		AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
 20324  			core.ResourceStorage: core.PersistentVolumeClaimControllerResizeFailed,
 20325  			validResizeKeyCustom: core.PersistentVolumeClaimControllerResizeInProgress,
 20326  		},
 20327  	})
 20328  
 20329  	invalidNativeResourceAllocatedKey := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20330  		AccessModes: []core.PersistentVolumeAccessMode{
 20331  			core.ReadWriteOnce,
 20332  			core.ReadOnlyMany,
 20333  		},
 20334  		Resources: core.VolumeResourceRequirements{
 20335  			Requests: core.ResourceList{
 20336  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20337  			},
 20338  		},
 20339  	}, core.PersistentVolumeClaimStatus{
 20340  		Phase: core.ClaimPending,
 20341  		Conditions: []core.PersistentVolumeClaimCondition{
 20342  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 20343  		},
 20344  		AllocatedResources: core.ResourceList{
 20345  			invalidNativeResizeKey: resource.MustParse("14G"),
 20346  		},
 20347  	})
 20348  
 20349  	validExternalAllocatedResource := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
 20350  		AccessModes: []core.PersistentVolumeAccessMode{
 20351  			core.ReadWriteOnce,
 20352  			core.ReadOnlyMany,
 20353  		},
 20354  		Resources: core.VolumeResourceRequirements{
 20355  			Requests: core.ResourceList{
 20356  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 20357  			},
 20358  		},
 20359  	}, core.PersistentVolumeClaimStatus{
 20360  		Phase: core.ClaimPending,
 20361  		Conditions: []core.PersistentVolumeClaimCondition{
 20362  			{Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
 20363  		},
 20364  		AllocatedResources: core.ResourceList{
 20365  			validResizeKeyCustom: resource.MustParse("14G"),
 20366  		},
 20367  	})
 20368  
 20369  	scenarios := map[string]struct {
 20370  		isExpectedFailure          bool
 20371  		oldClaim                   *core.PersistentVolumeClaim
 20372  		newClaim                   *core.PersistentVolumeClaim
 20373  		enableRecoverFromExpansion bool
 20374  	}{
 20375  		"condition-update-with-enabled-feature-gate": {
 20376  			isExpectedFailure: false,
 20377  			oldClaim:          validClaim,
 20378  			newClaim:          validConditionUpdate,
 20379  		},
 20380  		"status-update-with-valid-allocatedResources-feature-enabled": {
 20381  			isExpectedFailure:          false,
 20382  			oldClaim:                   validClaim,
 20383  			newClaim:                   validAllocatedResources,
 20384  			enableRecoverFromExpansion: true,
 20385  		},
 20386  		"status-update-with-invalid-allocatedResources-native-key-feature-enabled": {
 20387  			isExpectedFailure:          true,
 20388  			oldClaim:                   validClaim,
 20389  			newClaim:                   invalidNativeResourceAllocatedKey,
 20390  			enableRecoverFromExpansion: true,
 20391  		},
 20392  		"status-update-with-valid-allocatedResources-external-key-feature-enabled": {
 20393  			isExpectedFailure:          false,
 20394  			oldClaim:                   validClaim,
 20395  			newClaim:                   validExternalAllocatedResource,
 20396  			enableRecoverFromExpansion: true,
 20397  		},
 20398  
 20399  		"status-update-with-invalid-allocatedResources-feature-enabled": {
 20400  			isExpectedFailure:          true,
 20401  			oldClaim:                   validClaim,
 20402  			newClaim:                   invalidAllocatedResources,
 20403  			enableRecoverFromExpansion: true,
 20404  		},
 20405  		"status-update-with-no-storage-update": {
 20406  			isExpectedFailure:          true,
 20407  			oldClaim:                   validClaim,
 20408  			newClaim:                   noStoraegeClaimStatus,
 20409  			enableRecoverFromExpansion: true,
 20410  		},
 20411  		"staus-update-with-controller-resize-failed": {
 20412  			isExpectedFailure:          false,
 20413  			oldClaim:                   validClaim,
 20414  			newClaim:                   validResizeStatusControllerResizeFailed,
 20415  			enableRecoverFromExpansion: true,
 20416  		},
 20417  		"staus-update-with-node-resize-pending": {
 20418  			isExpectedFailure:          false,
 20419  			oldClaim:                   validClaim,
 20420  			newClaim:                   validNodeResizePending,
 20421  			enableRecoverFromExpansion: true,
 20422  		},
 20423  		"staus-update-with-node-resize-inprogress": {
 20424  			isExpectedFailure:          false,
 20425  			oldClaim:                   validClaim,
 20426  			newClaim:                   validNodeResizeInProgress,
 20427  			enableRecoverFromExpansion: true,
 20428  		},
 20429  		"staus-update-with-node-resize-failed": {
 20430  			isExpectedFailure:          false,
 20431  			oldClaim:                   validClaim,
 20432  			newClaim:                   validNodeResizeFailed,
 20433  			enableRecoverFromExpansion: true,
 20434  		},
 20435  		"staus-update-with-invalid-native-resource-status-key": {
 20436  			isExpectedFailure:          true,
 20437  			oldClaim:                   validClaim,
 20438  			newClaim:                   invalidNativeResizeStatusPVC,
 20439  			enableRecoverFromExpansion: true,
 20440  		},
 20441  		"staus-update-with-valid-external-resource-status-key": {
 20442  			isExpectedFailure:          false,
 20443  			oldClaim:                   validClaim,
 20444  			newClaim:                   validExternalResizeStatusPVC,
 20445  			enableRecoverFromExpansion: true,
 20446  		},
 20447  		"status-update-with-multiple-resources-key": {
 20448  			isExpectedFailure:          false,
 20449  			oldClaim:                   validClaim,
 20450  			newClaim:                   multipleResourceStatusPVC,
 20451  			enableRecoverFromExpansion: true,
 20452  		},
 20453  		"status-update-with-valid-pvc-resize-status": {
 20454  			isExpectedFailure:          false,
 20455  			oldClaim:                   validClaim,
 20456  			newClaim:                   validResizeStatusPVC,
 20457  			enableRecoverFromExpansion: true,
 20458  		},
 20459  		"status-update-with-invalid-pvc-resize-status": {
 20460  			isExpectedFailure:          true,
 20461  			oldClaim:                   validClaim,
 20462  			newClaim:                   invalidResizeStatusPVC,
 20463  			enableRecoverFromExpansion: true,
 20464  		},
 20465  		"status-update-with-old-pvc-valid-resourcestatus-newpvc-invalid-recovery-disabled": {
 20466  			isExpectedFailure:          true,
 20467  			oldClaim:                   validResizeStatusPVC,
 20468  			newClaim:                   invalidResizeStatusPVC,
 20469  			enableRecoverFromExpansion: false,
 20470  		},
 20471  		"status-update-with-old-pvc-valid-allocatedResource-newpvc-invalid-recovery-disabled": {
 20472  			isExpectedFailure:          true,
 20473  			oldClaim:                   validExternalAllocatedResource,
 20474  			newClaim:                   invalidNativeResourceAllocatedKey,
 20475  			enableRecoverFromExpansion: false,
 20476  		},
 20477  	}
 20478  	for name, scenario := range scenarios {
 20479  		t.Run(name, func(t *testing.T) {
 20480  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, scenario.enableRecoverFromExpansion)()
 20481  
 20482  			validateOpts := ValidationOptionsForPersistentVolumeClaim(scenario.newClaim, scenario.oldClaim)
 20483  
 20484  			// ensure we have a resource version specified for updates
 20485  			scenario.oldClaim.ResourceVersion = "1"
 20486  			scenario.newClaim.ResourceVersion = "1"
 20487  			errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim, validateOpts)
 20488  			if len(errs) == 0 && scenario.isExpectedFailure {
 20489  				t.Errorf("Unexpected success for scenario: %s", name)
 20490  			}
 20491  			if len(errs) > 0 && !scenario.isExpectedFailure {
 20492  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
 20493  			}
 20494  		})
 20495  	}
 20496  }
 20497  
 20498  func TestValidateResourceQuota(t *testing.T) {
 20499  	spec := core.ResourceQuotaSpec{
 20500  		Hard: core.ResourceList{
 20501  			core.ResourceCPU:                    resource.MustParse("100"),
 20502  			core.ResourceMemory:                 resource.MustParse("10000"),
 20503  			core.ResourceRequestsCPU:            resource.MustParse("100"),
 20504  			core.ResourceRequestsMemory:         resource.MustParse("10000"),
 20505  			core.ResourceLimitsCPU:              resource.MustParse("100"),
 20506  			core.ResourceLimitsMemory:           resource.MustParse("10000"),
 20507  			core.ResourcePods:                   resource.MustParse("10"),
 20508  			core.ResourceServices:               resource.MustParse("0"),
 20509  			core.ResourceReplicationControllers: resource.MustParse("10"),
 20510  			core.ResourceQuotas:                 resource.MustParse("10"),
 20511  			core.ResourceConfigMaps:             resource.MustParse("10"),
 20512  			core.ResourceSecrets:                resource.MustParse("10"),
 20513  		},
 20514  	}
 20515  
 20516  	terminatingSpec := core.ResourceQuotaSpec{
 20517  		Hard: core.ResourceList{
 20518  			core.ResourceCPU:       resource.MustParse("100"),
 20519  			core.ResourceLimitsCPU: resource.MustParse("200"),
 20520  		},
 20521  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating},
 20522  	}
 20523  
 20524  	nonTerminatingSpec := core.ResourceQuotaSpec{
 20525  		Hard: core.ResourceList{
 20526  			core.ResourceCPU: resource.MustParse("100"),
 20527  		},
 20528  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating},
 20529  	}
 20530  
 20531  	bestEffortSpec := core.ResourceQuotaSpec{
 20532  		Hard: core.ResourceList{
 20533  			core.ResourcePods: resource.MustParse("100"),
 20534  		},
 20535  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort},
 20536  	}
 20537  
 20538  	nonBestEffortSpec := core.ResourceQuotaSpec{
 20539  		Hard: core.ResourceList{
 20540  			core.ResourceCPU: resource.MustParse("100"),
 20541  		},
 20542  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
 20543  	}
 20544  
 20545  	crossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 20546  		Hard: core.ResourceList{
 20547  			core.ResourceCPU:       resource.MustParse("100"),
 20548  			core.ResourceLimitsCPU: resource.MustParse("200"),
 20549  		},
 20550  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeCrossNamespacePodAffinity},
 20551  	}
 20552  
 20553  	scopeSelectorSpec := core.ResourceQuotaSpec{
 20554  		ScopeSelector: &core.ScopeSelector{
 20555  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 20556  				ScopeName: core.ResourceQuotaScopePriorityClass,
 20557  				Operator:  core.ScopeSelectorOpIn,
 20558  				Values:    []string{"cluster-services"},
 20559  			}},
 20560  		},
 20561  	}
 20562  
 20563  	// storage is not yet supported as a quota tracked resource
 20564  	invalidQuotaResourceSpec := core.ResourceQuotaSpec{
 20565  		Hard: core.ResourceList{
 20566  			core.ResourceStorage: resource.MustParse("10"),
 20567  		},
 20568  	}
 20569  
 20570  	negativeSpec := core.ResourceQuotaSpec{
 20571  		Hard: core.ResourceList{
 20572  			core.ResourceCPU:                    resource.MustParse("-100"),
 20573  			core.ResourceMemory:                 resource.MustParse("-10000"),
 20574  			core.ResourcePods:                   resource.MustParse("-10"),
 20575  			core.ResourceServices:               resource.MustParse("-10"),
 20576  			core.ResourceReplicationControllers: resource.MustParse("-10"),
 20577  			core.ResourceQuotas:                 resource.MustParse("-10"),
 20578  			core.ResourceConfigMaps:             resource.MustParse("-10"),
 20579  			core.ResourceSecrets:                resource.MustParse("-10"),
 20580  		},
 20581  	}
 20582  
 20583  	fractionalComputeSpec := core.ResourceQuotaSpec{
 20584  		Hard: core.ResourceList{
 20585  			core.ResourceCPU: resource.MustParse("100m"),
 20586  		},
 20587  	}
 20588  
 20589  	fractionalPodSpec := core.ResourceQuotaSpec{
 20590  		Hard: core.ResourceList{
 20591  			core.ResourcePods:                   resource.MustParse(".1"),
 20592  			core.ResourceServices:               resource.MustParse(".5"),
 20593  			core.ResourceReplicationControllers: resource.MustParse("1.25"),
 20594  			core.ResourceQuotas:                 resource.MustParse("2.5"),
 20595  		},
 20596  	}
 20597  
 20598  	invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{
 20599  		Hard: core.ResourceList{
 20600  			core.ResourceCPU: resource.MustParse("100"),
 20601  		},
 20602  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating},
 20603  	}
 20604  
 20605  	invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{
 20606  		Hard: core.ResourceList{
 20607  			core.ResourcePods: resource.MustParse("100"),
 20608  		},
 20609  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort},
 20610  	}
 20611  
 20612  	invalidCrossNamespaceAffinitySpec := core.ResourceQuotaSpec{
 20613  		ScopeSelector: &core.ScopeSelector{
 20614  			MatchExpressions: []core.ScopedResourceSelectorRequirement{{
 20615  				ScopeName: core.ResourceQuotaScopeCrossNamespacePodAffinity,
 20616  				Operator:  core.ScopeSelectorOpIn,
 20617  				Values:    []string{"cluster-services"},
 20618  			}},
 20619  		},
 20620  	}
 20621  
 20622  	invalidScopeNameSpec := core.ResourceQuotaSpec{
 20623  		Hard: core.ResourceList{
 20624  			core.ResourceCPU: resource.MustParse("100"),
 20625  		},
 20626  		Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")},
 20627  	}
 20628  
 20629  	testCases := map[string]struct {
 20630  		rq        core.ResourceQuota
 20631  		errDetail string
 20632  		errField  string
 20633  	}{
 20634  		"no-scope": {
 20635  			rq: core.ResourceQuota{
 20636  				ObjectMeta: metav1.ObjectMeta{
 20637  					Name:      "abc",
 20638  					Namespace: "foo",
 20639  				},
 20640  				Spec: spec,
 20641  			},
 20642  		},
 20643  		"fractional-compute-spec": {
 20644  			rq: core.ResourceQuota{
 20645  				ObjectMeta: metav1.ObjectMeta{
 20646  					Name:      "abc",
 20647  					Namespace: "foo",
 20648  				},
 20649  				Spec: fractionalComputeSpec,
 20650  			},
 20651  		},
 20652  		"terminating-spec": {
 20653  			rq: core.ResourceQuota{
 20654  				ObjectMeta: metav1.ObjectMeta{
 20655  					Name:      "abc",
 20656  					Namespace: "foo",
 20657  				},
 20658  				Spec: terminatingSpec,
 20659  			},
 20660  		},
 20661  		"non-terminating-spec": {
 20662  			rq: core.ResourceQuota{
 20663  				ObjectMeta: metav1.ObjectMeta{
 20664  					Name:      "abc",
 20665  					Namespace: "foo",
 20666  				},
 20667  				Spec: nonTerminatingSpec,
 20668  			},
 20669  		},
 20670  		"best-effort-spec": {
 20671  			rq: core.ResourceQuota{
 20672  				ObjectMeta: metav1.ObjectMeta{
 20673  					Name:      "abc",
 20674  					Namespace: "foo",
 20675  				},
 20676  				Spec: bestEffortSpec,
 20677  			},
 20678  		},
 20679  		"cross-namespace-affinity-spec": {
 20680  			rq: core.ResourceQuota{
 20681  				ObjectMeta: metav1.ObjectMeta{
 20682  					Name:      "abc",
 20683  					Namespace: "foo",
 20684  				},
 20685  				Spec: crossNamespaceAffinitySpec,
 20686  			},
 20687  		},
 20688  		"scope-selector-spec": {
 20689  			rq: core.ResourceQuota{
 20690  				ObjectMeta: metav1.ObjectMeta{
 20691  					Name:      "abc",
 20692  					Namespace: "foo",
 20693  				},
 20694  				Spec: scopeSelectorSpec,
 20695  			},
 20696  		},
 20697  		"non-best-effort-spec": {
 20698  			rq: core.ResourceQuota{
 20699  				ObjectMeta: metav1.ObjectMeta{
 20700  					Name:      "abc",
 20701  					Namespace: "foo",
 20702  				},
 20703  				Spec: nonBestEffortSpec,
 20704  			},
 20705  		},
 20706  		"zero-length Name": {
 20707  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
 20708  			errDetail: "name or generateName is required",
 20709  		},
 20710  		"zero-length Namespace": {
 20711  			rq:       core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
 20712  			errField: "metadata.namespace",
 20713  		},
 20714  		"invalid Name": {
 20715  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
 20716  			errDetail: dnsSubdomainLabelErrMsg,
 20717  		},
 20718  		"invalid Namespace": {
 20719  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
 20720  			errDetail: dnsLabelErrMsg,
 20721  		},
 20722  		"negative-limits": {
 20723  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec},
 20724  			errDetail: isNegativeErrorMsg,
 20725  		},
 20726  		"fractional-api-resource": {
 20727  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
 20728  			errDetail: isNotIntegerErrorMsg,
 20729  		},
 20730  		"invalid-quota-resource": {
 20731  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
 20732  			errDetail: isInvalidQuotaResource,
 20733  		},
 20734  		"invalid-quota-terminating-pair": {
 20735  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
 20736  			errDetail: "conflicting scopes",
 20737  		},
 20738  		"invalid-quota-besteffort-pair": {
 20739  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
 20740  			errDetail: "conflicting scopes",
 20741  		},
 20742  		"invalid-quota-scope-name": {
 20743  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
 20744  			errDetail: "unsupported scope",
 20745  		},
 20746  		"invalid-cross-namespace-affinity": {
 20747  			rq:        core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidCrossNamespaceAffinitySpec},
 20748  			errDetail: "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity",
 20749  		},
 20750  	}
 20751  	for name, tc := range testCases {
 20752  		t.Run(name, func(t *testing.T) {
 20753  			errs := ValidateResourceQuota(&tc.rq)
 20754  			if len(tc.errDetail) == 0 && len(tc.errField) == 0 && len(errs) != 0 {
 20755  				t.Errorf("expected success: %v", errs)
 20756  			} else if (len(tc.errDetail) != 0 || len(tc.errField) != 0) && len(errs) == 0 {
 20757  				t.Errorf("expected failure")
 20758  			} else {
 20759  				for i := range errs {
 20760  					if !strings.Contains(errs[i].Detail, tc.errDetail) {
 20761  						t.Errorf("expected error detail either empty or %s, got %s", tc.errDetail, errs[i].Detail)
 20762  					}
 20763  				}
 20764  			}
 20765  		})
 20766  	}
 20767  }
 20768  
 20769  func TestValidateNamespace(t *testing.T) {
 20770  	validLabels := map[string]string{"a": "b"}
 20771  	invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
 20772  	successCases := []core.Namespace{{
 20773  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels},
 20774  	}, {
 20775  		ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
 20776  		Spec: core.NamespaceSpec{
 20777  			Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"},
 20778  		},
 20779  	},
 20780  	}
 20781  	for _, successCase := range successCases {
 20782  		if errs := ValidateNamespace(&successCase); len(errs) != 0 {
 20783  			t.Errorf("expected success: %v", errs)
 20784  		}
 20785  	}
 20786  	errorCases := map[string]struct {
 20787  		R core.Namespace
 20788  		D string
 20789  	}{
 20790  		"zero-length name": {
 20791  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}},
 20792  			"",
 20793  		},
 20794  		"defined-namespace": {
 20795  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
 20796  			"",
 20797  		},
 20798  		"invalid-labels": {
 20799  			core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}},
 20800  			"",
 20801  		},
 20802  	}
 20803  	for k, v := range errorCases {
 20804  		errs := ValidateNamespace(&v.R)
 20805  		if len(errs) == 0 {
 20806  			t.Errorf("expected failure for %s", k)
 20807  		}
 20808  	}
 20809  }
 20810  
 20811  func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
 20812  	tests := []struct {
 20813  		oldNamespace core.Namespace
 20814  		namespace    core.Namespace
 20815  		valid        bool
 20816  	}{
 20817  		{core.Namespace{}, core.Namespace{}, true},
 20818  		{core.Namespace{
 20819  			ObjectMeta: metav1.ObjectMeta{
 20820  				Name: "foo"}},
 20821  			core.Namespace{
 20822  				ObjectMeta: metav1.ObjectMeta{
 20823  					Name: "foo"},
 20824  				Spec: core.NamespaceSpec{
 20825  					Finalizers: []core.FinalizerName{"Foo"},
 20826  				},
 20827  			}, false},
 20828  		{core.Namespace{
 20829  			ObjectMeta: metav1.ObjectMeta{
 20830  				Name: "foo"},
 20831  			Spec: core.NamespaceSpec{
 20832  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 20833  			},
 20834  		},
 20835  			core.Namespace{
 20836  				ObjectMeta: metav1.ObjectMeta{
 20837  					Name: "foo"},
 20838  				Spec: core.NamespaceSpec{
 20839  					Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"},
 20840  				},
 20841  			}, true},
 20842  		{core.Namespace{
 20843  			ObjectMeta: metav1.ObjectMeta{
 20844  				Name: "fooemptyfinalizer"},
 20845  			Spec: core.NamespaceSpec{
 20846  				Finalizers: []core.FinalizerName{"foo.com/bar"},
 20847  			},
 20848  		},
 20849  			core.Namespace{
 20850  				ObjectMeta: metav1.ObjectMeta{
 20851  					Name: "fooemptyfinalizer"},
 20852  				Spec: core.NamespaceSpec{
 20853  					Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"},
 20854  				},
 20855  			}, false},
 20856  	}
 20857  	for i, test := range tests {
 20858  		test.namespace.ObjectMeta.ResourceVersion = "1"
 20859  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 20860  		errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
 20861  		if test.valid && len(errs) > 0 {
 20862  			t.Errorf("%d: Unexpected error: %v", i, errs)
 20863  			t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
 20864  		}
 20865  		if !test.valid && len(errs) == 0 {
 20866  			t.Errorf("%d: Unexpected non-error", i)
 20867  		}
 20868  	}
 20869  }
 20870  
 20871  func TestValidateNamespaceStatusUpdate(t *testing.T) {
 20872  	now := metav1.Now()
 20873  
 20874  	tests := []struct {
 20875  		oldNamespace core.Namespace
 20876  		namespace    core.Namespace
 20877  		valid        bool
 20878  	}{
 20879  		{core.Namespace{}, core.Namespace{
 20880  			Status: core.NamespaceStatus{
 20881  				Phase: core.NamespaceActive,
 20882  			},
 20883  		}, true},
 20884  		// Cannot set deletionTimestamp via status update
 20885  		{core.Namespace{
 20886  			ObjectMeta: metav1.ObjectMeta{
 20887  				Name: "foo"}},
 20888  			core.Namespace{
 20889  				ObjectMeta: metav1.ObjectMeta{
 20890  					Name:              "foo",
 20891  					DeletionTimestamp: &now},
 20892  				Status: core.NamespaceStatus{
 20893  					Phase: core.NamespaceTerminating,
 20894  				},
 20895  			}, false},
 20896  		// Can update phase via status update
 20897  		{core.Namespace{
 20898  			ObjectMeta: metav1.ObjectMeta{
 20899  				Name:              "foo",
 20900  				DeletionTimestamp: &now}},
 20901  			core.Namespace{
 20902  				ObjectMeta: metav1.ObjectMeta{
 20903  					Name:              "foo",
 20904  					DeletionTimestamp: &now},
 20905  				Status: core.NamespaceStatus{
 20906  					Phase: core.NamespaceTerminating,
 20907  				},
 20908  			}, true},
 20909  		{core.Namespace{
 20910  			ObjectMeta: metav1.ObjectMeta{
 20911  				Name: "foo"}},
 20912  			core.Namespace{
 20913  				ObjectMeta: metav1.ObjectMeta{
 20914  					Name: "foo"},
 20915  				Status: core.NamespaceStatus{
 20916  					Phase: core.NamespaceTerminating,
 20917  				},
 20918  			}, false},
 20919  		{core.Namespace{
 20920  			ObjectMeta: metav1.ObjectMeta{
 20921  				Name: "foo"}},
 20922  			core.Namespace{
 20923  				ObjectMeta: metav1.ObjectMeta{
 20924  					Name: "bar"},
 20925  				Status: core.NamespaceStatus{
 20926  					Phase: core.NamespaceTerminating,
 20927  				},
 20928  			}, false},
 20929  	}
 20930  	for i, test := range tests {
 20931  		test.namespace.ObjectMeta.ResourceVersion = "1"
 20932  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 20933  		errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace)
 20934  		if test.valid && len(errs) > 0 {
 20935  			t.Errorf("%d: Unexpected error: %v", i, errs)
 20936  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 20937  		}
 20938  		if !test.valid && len(errs) == 0 {
 20939  			t.Errorf("%d: Unexpected non-error", i)
 20940  		}
 20941  	}
 20942  }
 20943  
 20944  func TestValidateNamespaceUpdate(t *testing.T) {
 20945  	tests := []struct {
 20946  		oldNamespace core.Namespace
 20947  		namespace    core.Namespace
 20948  		valid        bool
 20949  	}{
 20950  		{core.Namespace{}, core.Namespace{}, true},
 20951  		{core.Namespace{
 20952  			ObjectMeta: metav1.ObjectMeta{
 20953  				Name: "foo1"}},
 20954  			core.Namespace{
 20955  				ObjectMeta: metav1.ObjectMeta{
 20956  					Name: "bar1"},
 20957  			}, false},
 20958  		{core.Namespace{
 20959  			ObjectMeta: metav1.ObjectMeta{
 20960  				Name:   "foo2",
 20961  				Labels: map[string]string{"foo": "bar"},
 20962  			},
 20963  		}, core.Namespace{
 20964  			ObjectMeta: metav1.ObjectMeta{
 20965  				Name:   "foo2",
 20966  				Labels: map[string]string{"foo": "baz"},
 20967  			},
 20968  		}, true},
 20969  		{core.Namespace{
 20970  			ObjectMeta: metav1.ObjectMeta{
 20971  				Name: "foo3",
 20972  			},
 20973  		}, core.Namespace{
 20974  			ObjectMeta: metav1.ObjectMeta{
 20975  				Name:   "foo3",
 20976  				Labels: map[string]string{"foo": "baz"},
 20977  			},
 20978  		}, true},
 20979  		{core.Namespace{
 20980  			ObjectMeta: metav1.ObjectMeta{
 20981  				Name:   "foo4",
 20982  				Labels: map[string]string{"bar": "foo"},
 20983  			},
 20984  		}, core.Namespace{
 20985  			ObjectMeta: metav1.ObjectMeta{
 20986  				Name:   "foo4",
 20987  				Labels: map[string]string{"foo": "baz"},
 20988  			},
 20989  		}, true},
 20990  		{core.Namespace{
 20991  			ObjectMeta: metav1.ObjectMeta{
 20992  				Name:   "foo5",
 20993  				Labels: map[string]string{"foo": "baz"},
 20994  			},
 20995  		}, core.Namespace{
 20996  			ObjectMeta: metav1.ObjectMeta{
 20997  				Name:   "foo5",
 20998  				Labels: map[string]string{"Foo": "baz"},
 20999  			},
 21000  		}, true},
 21001  		{core.Namespace{
 21002  			ObjectMeta: metav1.ObjectMeta{
 21003  				Name:   "foo6",
 21004  				Labels: map[string]string{"foo": "baz"},
 21005  			},
 21006  		}, core.Namespace{
 21007  			ObjectMeta: metav1.ObjectMeta{
 21008  				Name:   "foo6",
 21009  				Labels: map[string]string{"Foo": "baz"},
 21010  			},
 21011  			Spec: core.NamespaceSpec{
 21012  				Finalizers: []core.FinalizerName{"kubernetes"},
 21013  			},
 21014  			Status: core.NamespaceStatus{
 21015  				Phase: core.NamespaceTerminating,
 21016  			},
 21017  		}, true},
 21018  	}
 21019  	for i, test := range tests {
 21020  		test.namespace.ObjectMeta.ResourceVersion = "1"
 21021  		test.oldNamespace.ObjectMeta.ResourceVersion = "1"
 21022  		errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace)
 21023  		if test.valid && len(errs) > 0 {
 21024  			t.Errorf("%d: Unexpected error: %v", i, errs)
 21025  			t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
 21026  		}
 21027  		if !test.valid && len(errs) == 0 {
 21028  			t.Errorf("%d: Unexpected non-error", i)
 21029  		}
 21030  	}
 21031  }
 21032  
 21033  func TestValidateSecret(t *testing.T) {
 21034  	// Opaque secret validation
 21035  	validSecret := func() core.Secret {
 21036  		return core.Secret{
 21037  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21038  			Data: map[string][]byte{
 21039  				"data-1": []byte("bar"),
 21040  			},
 21041  		}
 21042  	}
 21043  
 21044  	var (
 21045  		emptyName     = validSecret()
 21046  		invalidName   = validSecret()
 21047  		emptyNs       = validSecret()
 21048  		invalidNs     = validSecret()
 21049  		overMaxSize   = validSecret()
 21050  		invalidKey    = validSecret()
 21051  		leadingDotKey = validSecret()
 21052  		dotKey        = validSecret()
 21053  		doubleDotKey  = validSecret()
 21054  	)
 21055  
 21056  	emptyName.Name = ""
 21057  	invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals"
 21058  	emptyNs.Namespace = ""
 21059  	invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals"
 21060  	overMaxSize.Data = map[string][]byte{
 21061  		"over": make([]byte, core.MaxSecretSize+1),
 21062  	}
 21063  	invalidKey.Data["a*b"] = []byte("whoops")
 21064  	leadingDotKey.Data[".key"] = []byte("bar")
 21065  	dotKey.Data["."] = []byte("bar")
 21066  	doubleDotKey.Data[".."] = []byte("bar")
 21067  
 21068  	// kubernetes.io/service-account-token secret validation
 21069  	validServiceAccountTokenSecret := func() core.Secret {
 21070  		return core.Secret{
 21071  			ObjectMeta: metav1.ObjectMeta{
 21072  				Name:      "foo",
 21073  				Namespace: "bar",
 21074  				Annotations: map[string]string{
 21075  					core.ServiceAccountNameKey: "foo",
 21076  				},
 21077  			},
 21078  			Type: core.SecretTypeServiceAccountToken,
 21079  			Data: map[string][]byte{
 21080  				"data-1": []byte("bar"),
 21081  			},
 21082  		}
 21083  	}
 21084  
 21085  	var (
 21086  		emptyTokenAnnotation    = validServiceAccountTokenSecret()
 21087  		missingTokenAnnotation  = validServiceAccountTokenSecret()
 21088  		missingTokenAnnotations = validServiceAccountTokenSecret()
 21089  	)
 21090  	emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = ""
 21091  	delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey)
 21092  	missingTokenAnnotations.Annotations = nil
 21093  
 21094  	tests := map[string]struct {
 21095  		secret core.Secret
 21096  		valid  bool
 21097  	}{
 21098  		"valid":                                     {validSecret(), true},
 21099  		"empty name":                                {emptyName, false},
 21100  		"invalid name":                              {invalidName, false},
 21101  		"empty namespace":                           {emptyNs, false},
 21102  		"invalid namespace":                         {invalidNs, false},
 21103  		"over max size":                             {overMaxSize, false},
 21104  		"invalid key":                               {invalidKey, false},
 21105  		"valid service-account-token secret":        {validServiceAccountTokenSecret(), true},
 21106  		"empty service-account-token annotation":    {emptyTokenAnnotation, false},
 21107  		"missing service-account-token annotation":  {missingTokenAnnotation, false},
 21108  		"missing service-account-token annotations": {missingTokenAnnotations, false},
 21109  		"leading dot key":                           {leadingDotKey, true},
 21110  		"dot key":                                   {dotKey, false},
 21111  		"double dot key":                            {doubleDotKey, false},
 21112  	}
 21113  
 21114  	for name, tc := range tests {
 21115  		errs := ValidateSecret(&tc.secret)
 21116  		if tc.valid && len(errs) > 0 {
 21117  			t.Errorf("%v: Unexpected error: %v", name, errs)
 21118  		}
 21119  		if !tc.valid && len(errs) == 0 {
 21120  			t.Errorf("%v: Unexpected non-error", name)
 21121  		}
 21122  	}
 21123  }
 21124  
 21125  func TestValidateSecretUpdate(t *testing.T) {
 21126  	validSecret := func() core.Secret {
 21127  		return core.Secret{
 21128  			ObjectMeta: metav1.ObjectMeta{
 21129  				Name:            "foo",
 21130  				Namespace:       "bar",
 21131  				ResourceVersion: "20",
 21132  			},
 21133  			Data: map[string][]byte{
 21134  				"data-1": []byte("bar"),
 21135  			},
 21136  		}
 21137  	}
 21138  
 21139  	falseVal := false
 21140  	trueVal := true
 21141  
 21142  	secret := validSecret()
 21143  	immutableSecret := validSecret()
 21144  	immutableSecret.Immutable = &trueVal
 21145  	mutableSecret := validSecret()
 21146  	mutableSecret.Immutable = &falseVal
 21147  
 21148  	secretWithData := validSecret()
 21149  	secretWithData.Data["data-2"] = []byte("baz")
 21150  	immutableSecretWithData := validSecret()
 21151  	immutableSecretWithData.Immutable = &trueVal
 21152  	immutableSecretWithData.Data["data-2"] = []byte("baz")
 21153  
 21154  	secretWithChangedData := validSecret()
 21155  	secretWithChangedData.Data["data-1"] = []byte("foo")
 21156  	immutableSecretWithChangedData := validSecret()
 21157  	immutableSecretWithChangedData.Immutable = &trueVal
 21158  	immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
 21159  
 21160  	tests := []struct {
 21161  		name      string
 21162  		oldSecret core.Secret
 21163  		newSecret core.Secret
 21164  		valid     bool
 21165  	}{{
 21166  		name:      "mark secret immutable",
 21167  		oldSecret: secret,
 21168  		newSecret: immutableSecret,
 21169  		valid:     true,
 21170  	}, {
 21171  		name:      "revert immutable secret",
 21172  		oldSecret: immutableSecret,
 21173  		newSecret: secret,
 21174  		valid:     false,
 21175  	}, {
 21176  		name:      "makr immutable secret mutable",
 21177  		oldSecret: immutableSecret,
 21178  		newSecret: mutableSecret,
 21179  		valid:     false,
 21180  	}, {
 21181  		name:      "add data in secret",
 21182  		oldSecret: secret,
 21183  		newSecret: secretWithData,
 21184  		valid:     true,
 21185  	}, {
 21186  		name:      "add data in immutable secret",
 21187  		oldSecret: immutableSecret,
 21188  		newSecret: immutableSecretWithData,
 21189  		valid:     false,
 21190  	}, {
 21191  		name:      "change data in secret",
 21192  		oldSecret: secret,
 21193  		newSecret: secretWithChangedData,
 21194  		valid:     true,
 21195  	}, {
 21196  		name:      "change data in immutable secret",
 21197  		oldSecret: immutableSecret,
 21198  		newSecret: immutableSecretWithChangedData,
 21199  		valid:     false,
 21200  	},
 21201  	}
 21202  
 21203  	for _, tc := range tests {
 21204  		t.Run(tc.name, func(t *testing.T) {
 21205  			errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
 21206  			if tc.valid && len(errs) > 0 {
 21207  				t.Errorf("Unexpected error: %v", errs)
 21208  			}
 21209  			if !tc.valid && len(errs) == 0 {
 21210  				t.Errorf("Unexpected lack of error")
 21211  			}
 21212  		})
 21213  	}
 21214  }
 21215  
 21216  func TestValidateDockerConfigSecret(t *testing.T) {
 21217  	validDockerSecret := func() core.Secret {
 21218  		return core.Secret{
 21219  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21220  			Type:       core.SecretTypeDockercfg,
 21221  			Data: map[string][]byte{
 21222  				core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`),
 21223  			},
 21224  		}
 21225  	}
 21226  	validDockerSecret2 := func() core.Secret {
 21227  		return core.Secret{
 21228  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21229  			Type:       core.SecretTypeDockerConfigJSON,
 21230  			Data: map[string][]byte{
 21231  				core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`),
 21232  			},
 21233  		}
 21234  	}
 21235  
 21236  	var (
 21237  		missingDockerConfigKey  = validDockerSecret()
 21238  		emptyDockerConfigKey    = validDockerSecret()
 21239  		invalidDockerConfigKey  = validDockerSecret()
 21240  		missingDockerConfigKey2 = validDockerSecret2()
 21241  		emptyDockerConfigKey2   = validDockerSecret2()
 21242  		invalidDockerConfigKey2 = validDockerSecret2()
 21243  	)
 21244  
 21245  	delete(missingDockerConfigKey.Data, core.DockerConfigKey)
 21246  	emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("")
 21247  	invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad")
 21248  	delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey)
 21249  	emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("")
 21250  	invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad")
 21251  
 21252  	tests := map[string]struct {
 21253  		secret core.Secret
 21254  		valid  bool
 21255  	}{
 21256  		"valid dockercfg":     {validDockerSecret(), true},
 21257  		"missing dockercfg":   {missingDockerConfigKey, false},
 21258  		"empty dockercfg":     {emptyDockerConfigKey, false},
 21259  		"invalid dockercfg":   {invalidDockerConfigKey, false},
 21260  		"valid config.json":   {validDockerSecret2(), true},
 21261  		"missing config.json": {missingDockerConfigKey2, false},
 21262  		"empty config.json":   {emptyDockerConfigKey2, false},
 21263  		"invalid config.json": {invalidDockerConfigKey2, false},
 21264  	}
 21265  
 21266  	for name, tc := range tests {
 21267  		errs := ValidateSecret(&tc.secret)
 21268  		if tc.valid && len(errs) > 0 {
 21269  			t.Errorf("%v: Unexpected error: %v", name, errs)
 21270  		}
 21271  		if !tc.valid && len(errs) == 0 {
 21272  			t.Errorf("%v: Unexpected non-error", name)
 21273  		}
 21274  	}
 21275  }
 21276  
 21277  func TestValidateBasicAuthSecret(t *testing.T) {
 21278  	validBasicAuthSecret := func() core.Secret {
 21279  		return core.Secret{
 21280  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21281  			Type:       core.SecretTypeBasicAuth,
 21282  			Data: map[string][]byte{
 21283  				core.BasicAuthUsernameKey: []byte("username"),
 21284  				core.BasicAuthPasswordKey: []byte("password"),
 21285  			},
 21286  		}
 21287  	}
 21288  
 21289  	var (
 21290  		missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret()
 21291  	)
 21292  
 21293  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey)
 21294  	delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey)
 21295  
 21296  	tests := map[string]struct {
 21297  		secret core.Secret
 21298  		valid  bool
 21299  	}{
 21300  		"valid":                         {validBasicAuthSecret(), true},
 21301  		"missing username and password": {missingBasicAuthUsernamePasswordKeys, false},
 21302  	}
 21303  
 21304  	for name, tc := range tests {
 21305  		errs := ValidateSecret(&tc.secret)
 21306  		if tc.valid && len(errs) > 0 {
 21307  			t.Errorf("%v: Unexpected error: %v", name, errs)
 21308  		}
 21309  		if !tc.valid && len(errs) == 0 {
 21310  			t.Errorf("%v: Unexpected non-error", name)
 21311  		}
 21312  	}
 21313  }
 21314  
 21315  func TestValidateSSHAuthSecret(t *testing.T) {
 21316  	validSSHAuthSecret := func() core.Secret {
 21317  		return core.Secret{
 21318  			ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
 21319  			Type:       core.SecretTypeSSHAuth,
 21320  			Data: map[string][]byte{
 21321  				core.SSHAuthPrivateKey: []byte("foo-bar-baz"),
 21322  			},
 21323  		}
 21324  	}
 21325  
 21326  	missingSSHAuthPrivateKey := validSSHAuthSecret()
 21327  
 21328  	delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey)
 21329  
 21330  	tests := map[string]struct {
 21331  		secret core.Secret
 21332  		valid  bool
 21333  	}{
 21334  		"valid":               {validSSHAuthSecret(), true},
 21335  		"missing private key": {missingSSHAuthPrivateKey, false},
 21336  	}
 21337  
 21338  	for name, tc := range tests {
 21339  		errs := ValidateSecret(&tc.secret)
 21340  		if tc.valid && len(errs) > 0 {
 21341  			t.Errorf("%v: Unexpected error: %v", name, errs)
 21342  		}
 21343  		if !tc.valid && len(errs) == 0 {
 21344  			t.Errorf("%v: Unexpected non-error", name)
 21345  		}
 21346  	}
 21347  }
 21348  
 21349  func TestValidateEndpointsCreate(t *testing.T) {
 21350  	successCases := map[string]struct {
 21351  		endpoints core.Endpoints
 21352  	}{
 21353  		"simple endpoint": {
 21354  			endpoints: core.Endpoints{
 21355  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21356  				Subsets: []core.EndpointSubset{{
 21357  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
 21358  					Ports:     []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 21359  				}, {
 21360  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 21361  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
 21362  				}},
 21363  			},
 21364  		},
 21365  		"empty subsets": {
 21366  			endpoints: core.Endpoints{
 21367  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21368  			},
 21369  		},
 21370  		"no name required for singleton port": {
 21371  			endpoints: core.Endpoints{
 21372  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21373  				Subsets: []core.EndpointSubset{{
 21374  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21375  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
 21376  				}},
 21377  			},
 21378  		},
 21379  		"valid appProtocol": {
 21380  			endpoints: core.Endpoints{
 21381  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21382  				Subsets: []core.EndpointSubset{{
 21383  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21384  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("HTTP")}},
 21385  				}},
 21386  			},
 21387  		},
 21388  		"empty ports": {
 21389  			endpoints: core.Endpoints{
 21390  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21391  				Subsets: []core.EndpointSubset{{
 21392  					Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
 21393  				}},
 21394  			},
 21395  		},
 21396  	}
 21397  
 21398  	for name, tc := range successCases {
 21399  		t.Run(name, func(t *testing.T) {
 21400  			errs := ValidateEndpointsCreate(&tc.endpoints)
 21401  			if len(errs) != 0 {
 21402  				t.Errorf("Expected no validation errors, got %v", errs)
 21403  			}
 21404  
 21405  		})
 21406  	}
 21407  
 21408  	errorCases := map[string]struct {
 21409  		endpoints   core.Endpoints
 21410  		errorType   field.ErrorType
 21411  		errorDetail string
 21412  	}{
 21413  		"missing namespace": {
 21414  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
 21415  			errorType: "FieldValueRequired",
 21416  		},
 21417  		"missing name": {
 21418  			endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}},
 21419  			errorType: "FieldValueRequired",
 21420  		},
 21421  		"invalid namespace": {
 21422  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}},
 21423  			errorType:   "FieldValueInvalid",
 21424  			errorDetail: dnsLabelErrMsg,
 21425  		},
 21426  		"invalid name": {
 21427  			endpoints:   core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}},
 21428  			errorType:   "FieldValueInvalid",
 21429  			errorDetail: dnsSubdomainLabelErrMsg,
 21430  		},
 21431  		"empty addresses": {
 21432  			endpoints: core.Endpoints{
 21433  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21434  				Subsets: []core.EndpointSubset{{
 21435  					Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 21436  				}},
 21437  			},
 21438  			errorType: "FieldValueRequired",
 21439  		},
 21440  		"invalid IP": {
 21441  			endpoints: core.Endpoints{
 21442  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21443  				Subsets: []core.EndpointSubset{{
 21444  					Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}},
 21445  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 21446  				}},
 21447  			},
 21448  			errorType:   "FieldValueInvalid",
 21449  			errorDetail: "must be a valid IP address",
 21450  		},
 21451  		"Multiple ports, one without name": {
 21452  			endpoints: core.Endpoints{
 21453  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21454  				Subsets: []core.EndpointSubset{{
 21455  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21456  					Ports:     []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
 21457  				}},
 21458  			},
 21459  			errorType: "FieldValueRequired",
 21460  		},
 21461  		"Invalid port number": {
 21462  			endpoints: core.Endpoints{
 21463  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21464  				Subsets: []core.EndpointSubset{{
 21465  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21466  					Ports:     []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}},
 21467  				}},
 21468  			},
 21469  			errorType:   "FieldValueInvalid",
 21470  			errorDetail: "between",
 21471  		},
 21472  		"Invalid protocol": {
 21473  			endpoints: core.Endpoints{
 21474  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21475  				Subsets: []core.EndpointSubset{{
 21476  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21477  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}},
 21478  				}},
 21479  			},
 21480  			errorType: "FieldValueNotSupported",
 21481  		},
 21482  		"Address missing IP": {
 21483  			endpoints: core.Endpoints{
 21484  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21485  				Subsets: []core.EndpointSubset{{
 21486  					Addresses: []core.EndpointAddress{{}},
 21487  					Ports:     []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
 21488  				}},
 21489  			},
 21490  			errorType:   "FieldValueInvalid",
 21491  			errorDetail: "must be a valid IP address",
 21492  		},
 21493  		"Port missing number": {
 21494  			endpoints: core.Endpoints{
 21495  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21496  				Subsets: []core.EndpointSubset{{
 21497  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21498  					Ports:     []core.EndpointPort{{Name: "a", Protocol: "TCP"}},
 21499  				}},
 21500  			},
 21501  			errorType:   "FieldValueInvalid",
 21502  			errorDetail: "between",
 21503  		},
 21504  		"Port missing protocol": {
 21505  			endpoints: core.Endpoints{
 21506  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21507  				Subsets: []core.EndpointSubset{{
 21508  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21509  					Ports:     []core.EndpointPort{{Name: "a", Port: 93}},
 21510  				}},
 21511  			},
 21512  			errorType: "FieldValueRequired",
 21513  		},
 21514  		"Address is loopback": {
 21515  			endpoints: core.Endpoints{
 21516  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21517  				Subsets: []core.EndpointSubset{{
 21518  					Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}},
 21519  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 21520  				}},
 21521  			},
 21522  			errorType:   "FieldValueInvalid",
 21523  			errorDetail: "loopback",
 21524  		},
 21525  		"Address is link-local": {
 21526  			endpoints: core.Endpoints{
 21527  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21528  				Subsets: []core.EndpointSubset{{
 21529  					Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}},
 21530  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 21531  				}},
 21532  			},
 21533  			errorType:   "FieldValueInvalid",
 21534  			errorDetail: "link-local",
 21535  		},
 21536  		"Address is link-local multicast": {
 21537  			endpoints: core.Endpoints{
 21538  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21539  				Subsets: []core.EndpointSubset{{
 21540  					Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}},
 21541  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
 21542  				}},
 21543  			},
 21544  			errorType:   "FieldValueInvalid",
 21545  			errorDetail: "link-local multicast",
 21546  		},
 21547  		"Invalid AppProtocol": {
 21548  			endpoints: core.Endpoints{
 21549  				ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
 21550  				Subsets: []core.EndpointSubset{{
 21551  					Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
 21552  					Ports:     []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.String("lots-of[invalid]-{chars}")}},
 21553  				}},
 21554  			},
 21555  			errorType:   "FieldValueInvalid",
 21556  			errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character",
 21557  		},
 21558  	}
 21559  
 21560  	for k, v := range errorCases {
 21561  		t.Run(k, func(t *testing.T) {
 21562  			if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 21563  				t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs)
 21564  			}
 21565  		})
 21566  	}
 21567  }
 21568  
 21569  func TestValidateEndpointsUpdate(t *testing.T) {
 21570  	baseEndpoints := core.Endpoints{
 21571  		ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"},
 21572  		Subsets: []core.EndpointSubset{{
 21573  			Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}},
 21574  		}},
 21575  	}
 21576  
 21577  	testCases := map[string]struct {
 21578  		tweakOldEndpoints func(ep *core.Endpoints)
 21579  		tweakNewEndpoints func(ep *core.Endpoints)
 21580  		numExpectedErrors int
 21581  	}{
 21582  		"update to valid app protocol": {
 21583  			tweakOldEndpoints: func(ep *core.Endpoints) {
 21584  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 21585  			},
 21586  			tweakNewEndpoints: func(ep *core.Endpoints) {
 21587  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("https")}}
 21588  			},
 21589  			numExpectedErrors: 0,
 21590  		},
 21591  		"update to invalid app protocol": {
 21592  			tweakOldEndpoints: func(ep *core.Endpoints) {
 21593  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
 21594  			},
 21595  			tweakNewEndpoints: func(ep *core.Endpoints) {
 21596  				ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.String("~https")}}
 21597  			},
 21598  			numExpectedErrors: 1,
 21599  		},
 21600  	}
 21601  
 21602  	for name, tc := range testCases {
 21603  		t.Run(name, func(t *testing.T) {
 21604  			oldEndpoints := baseEndpoints.DeepCopy()
 21605  			tc.tweakOldEndpoints(oldEndpoints)
 21606  			newEndpoints := baseEndpoints.DeepCopy()
 21607  			tc.tweakNewEndpoints(newEndpoints)
 21608  
 21609  			errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints)
 21610  			if len(errs) != tc.numExpectedErrors {
 21611  				t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs)
 21612  			}
 21613  
 21614  		})
 21615  	}
 21616  }
 21617  
 21618  func TestValidateWindowsSecurityContext(t *testing.T) {
 21619  	tests := []struct {
 21620  		name        string
 21621  		sc          *core.PodSpec
 21622  		expectError bool
 21623  		errorMsg    string
 21624  		errorType   field.ErrorType
 21625  	}{{
 21626  		name:        "pod with SELinux Options",
 21627  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SELinuxOptions: &core.SELinuxOptions{Role: "dummy"}}}}},
 21628  		expectError: true,
 21629  		errorMsg:    "cannot be set for a windows pod",
 21630  		errorType:   "FieldValueForbidden",
 21631  	}, {
 21632  		name:        "pod with SeccompProfile",
 21633  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{SeccompProfile: &core.SeccompProfile{LocalhostProfile: utilpointer.String("dummy")}}}}},
 21634  		expectError: true,
 21635  		errorMsg:    "cannot be set for a windows pod",
 21636  		errorType:   "FieldValueForbidden",
 21637  	}, {
 21638  		name:        "pod with WindowsOptions, no error",
 21639  		sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: &core.SecurityContext{WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("dummy")}}}}},
 21640  		expectError: false,
 21641  	},
 21642  	}
 21643  	for _, test := range tests {
 21644  		t.Run(test.name, func(t *testing.T) {
 21645  			errs := validateWindows(test.sc, field.NewPath("field"))
 21646  			if test.expectError && len(errs) > 0 {
 21647  				if errs[0].Type != test.errorType {
 21648  					t.Errorf("expected error type %q got %q", test.errorType, errs[0].Type)
 21649  				}
 21650  				if errs[0].Detail != test.errorMsg {
 21651  					t.Errorf("expected error detail %q, got %q", test.errorMsg, errs[0].Detail)
 21652  				}
 21653  			} else if test.expectError && len(errs) == 0 {
 21654  				t.Error("Unexpected success")
 21655  			}
 21656  			if !test.expectError && len(errs) != 0 {
 21657  				t.Errorf("Unexpected error(s): %v", errs)
 21658  			}
 21659  		})
 21660  	}
 21661  }
 21662  
 21663  func TestValidateOSFields(t *testing.T) {
 21664  	// Contains the list of OS specific fields within pod spec.
 21665  	// All the fields in pod spec should be either osSpecific or osNeutral field
 21666  	// To make a field OS specific:
 21667  	// - Add documentation to the os specific field indicating which os it can/cannot be set for
 21668  	// - Add documentation to the os field in the api
 21669  	// - Add validation logic validateLinux, validateWindows functions to make sure the field is only set for eligible OSes
 21670  	osSpecificFields := sets.NewString(
 21671  		"Containers[*].SecurityContext.AllowPrivilegeEscalation",
 21672  		"Containers[*].SecurityContext.Capabilities",
 21673  		"Containers[*].SecurityContext.Privileged",
 21674  		"Containers[*].SecurityContext.ProcMount",
 21675  		"Containers[*].SecurityContext.ReadOnlyRootFilesystem",
 21676  		"Containers[*].SecurityContext.RunAsGroup",
 21677  		"Containers[*].SecurityContext.RunAsUser",
 21678  		"Containers[*].SecurityContext.SELinuxOptions",
 21679  		"Containers[*].SecurityContext.SeccompProfile",
 21680  		"Containers[*].SecurityContext.WindowsOptions",
 21681  		"InitContainers[*].SecurityContext.AllowPrivilegeEscalation",
 21682  		"InitContainers[*].SecurityContext.Capabilities",
 21683  		"InitContainers[*].SecurityContext.Privileged",
 21684  		"InitContainers[*].SecurityContext.ProcMount",
 21685  		"InitContainers[*].SecurityContext.ReadOnlyRootFilesystem",
 21686  		"InitContainers[*].SecurityContext.RunAsGroup",
 21687  		"InitContainers[*].SecurityContext.RunAsUser",
 21688  		"InitContainers[*].SecurityContext.SELinuxOptions",
 21689  		"InitContainers[*].SecurityContext.SeccompProfile",
 21690  		"InitContainers[*].SecurityContext.WindowsOptions",
 21691  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.AllowPrivilegeEscalation",
 21692  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Capabilities",
 21693  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.Privileged",
 21694  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ProcMount",
 21695  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.ReadOnlyRootFilesystem",
 21696  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsGroup",
 21697  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsUser",
 21698  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SELinuxOptions",
 21699  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.SeccompProfile",
 21700  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.WindowsOptions",
 21701  		"OS",
 21702  		"SecurityContext.FSGroup",
 21703  		"SecurityContext.FSGroupChangePolicy",
 21704  		"SecurityContext.HostIPC",
 21705  		"SecurityContext.HostNetwork",
 21706  		"SecurityContext.HostPID",
 21707  		"SecurityContext.HostUsers",
 21708  		"SecurityContext.RunAsGroup",
 21709  		"SecurityContext.RunAsUser",
 21710  		"SecurityContext.SELinuxOptions",
 21711  		"SecurityContext.SeccompProfile",
 21712  		"SecurityContext.ShareProcessNamespace",
 21713  		"SecurityContext.SupplementalGroups",
 21714  		"SecurityContext.Sysctls",
 21715  		"SecurityContext.WindowsOptions",
 21716  	)
 21717  	osNeutralFields := sets.NewString(
 21718  		"ActiveDeadlineSeconds",
 21719  		"Affinity",
 21720  		"AutomountServiceAccountToken",
 21721  		"Containers[*].Args",
 21722  		"Containers[*].Command",
 21723  		"Containers[*].Env",
 21724  		"Containers[*].EnvFrom",
 21725  		"Containers[*].Image",
 21726  		"Containers[*].ImagePullPolicy",
 21727  		"Containers[*].Lifecycle",
 21728  		"Containers[*].LivenessProbe",
 21729  		"Containers[*].Name",
 21730  		"Containers[*].Ports",
 21731  		"Containers[*].ReadinessProbe",
 21732  		"Containers[*].Resources",
 21733  		"Containers[*].ResizePolicy[*].RestartPolicy",
 21734  		"Containers[*].ResizePolicy[*].ResourceName",
 21735  		"Containers[*].RestartPolicy",
 21736  		"Containers[*].SecurityContext.RunAsNonRoot",
 21737  		"Containers[*].Stdin",
 21738  		"Containers[*].StdinOnce",
 21739  		"Containers[*].StartupProbe",
 21740  		"Containers[*].VolumeDevices[*]",
 21741  		"Containers[*].VolumeMounts[*]",
 21742  		"Containers[*].TTY",
 21743  		"Containers[*].TerminationMessagePath",
 21744  		"Containers[*].TerminationMessagePolicy",
 21745  		"Containers[*].WorkingDir",
 21746  		"DNSPolicy",
 21747  		"EnableServiceLinks",
 21748  		"EphemeralContainers[*].EphemeralContainerCommon.Args",
 21749  		"EphemeralContainers[*].EphemeralContainerCommon.Command",
 21750  		"EphemeralContainers[*].EphemeralContainerCommon.Env",
 21751  		"EphemeralContainers[*].EphemeralContainerCommon.EnvFrom",
 21752  		"EphemeralContainers[*].EphemeralContainerCommon.Image",
 21753  		"EphemeralContainers[*].EphemeralContainerCommon.ImagePullPolicy",
 21754  		"EphemeralContainers[*].EphemeralContainerCommon.Lifecycle",
 21755  		"EphemeralContainers[*].EphemeralContainerCommon.LivenessProbe",
 21756  		"EphemeralContainers[*].EphemeralContainerCommon.Name",
 21757  		"EphemeralContainers[*].EphemeralContainerCommon.Ports",
 21758  		"EphemeralContainers[*].EphemeralContainerCommon.ReadinessProbe",
 21759  		"EphemeralContainers[*].EphemeralContainerCommon.Resources",
 21760  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].RestartPolicy",
 21761  		"EphemeralContainers[*].EphemeralContainerCommon.ResizePolicy[*].ResourceName",
 21762  		"EphemeralContainers[*].EphemeralContainerCommon.RestartPolicy",
 21763  		"EphemeralContainers[*].EphemeralContainerCommon.Stdin",
 21764  		"EphemeralContainers[*].EphemeralContainerCommon.StdinOnce",
 21765  		"EphemeralContainers[*].EphemeralContainerCommon.TTY",
 21766  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePath",
 21767  		"EphemeralContainers[*].EphemeralContainerCommon.TerminationMessagePolicy",
 21768  		"EphemeralContainers[*].EphemeralContainerCommon.WorkingDir",
 21769  		"EphemeralContainers[*].TargetContainerName",
 21770  		"EphemeralContainers[*].EphemeralContainerCommon.SecurityContext.RunAsNonRoot",
 21771  		"EphemeralContainers[*].EphemeralContainerCommon.StartupProbe",
 21772  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeDevices[*]",
 21773  		"EphemeralContainers[*].EphemeralContainerCommon.VolumeMounts[*]",
 21774  		"HostAliases",
 21775  		"Hostname",
 21776  		"ImagePullSecrets",
 21777  		"InitContainers[*].Args",
 21778  		"InitContainers[*].Command",
 21779  		"InitContainers[*].Env",
 21780  		"InitContainers[*].EnvFrom",
 21781  		"InitContainers[*].Image",
 21782  		"InitContainers[*].ImagePullPolicy",
 21783  		"InitContainers[*].Lifecycle",
 21784  		"InitContainers[*].LivenessProbe",
 21785  		"InitContainers[*].Name",
 21786  		"InitContainers[*].Ports",
 21787  		"InitContainers[*].ReadinessProbe",
 21788  		"InitContainers[*].Resources",
 21789  		"InitContainers[*].ResizePolicy[*].RestartPolicy",
 21790  		"InitContainers[*].ResizePolicy[*].ResourceName",
 21791  		"InitContainers[*].RestartPolicy",
 21792  		"InitContainers[*].Stdin",
 21793  		"InitContainers[*].StdinOnce",
 21794  		"InitContainers[*].TTY",
 21795  		"InitContainers[*].TerminationMessagePath",
 21796  		"InitContainers[*].TerminationMessagePolicy",
 21797  		"InitContainers[*].WorkingDir",
 21798  		"InitContainers[*].SecurityContext.RunAsNonRoot",
 21799  		"InitContainers[*].StartupProbe",
 21800  		"InitContainers[*].VolumeDevices[*]",
 21801  		"InitContainers[*].VolumeMounts[*]",
 21802  		"NodeName",
 21803  		"NodeSelector",
 21804  		"PreemptionPolicy",
 21805  		"Priority",
 21806  		"PriorityClassName",
 21807  		"ReadinessGates",
 21808  		"ResourceClaims[*].Name",
 21809  		"ResourceClaims[*].Source.ResourceClaimName",
 21810  		"ResourceClaims[*].Source.ResourceClaimTemplateName",
 21811  		"RestartPolicy",
 21812  		"RuntimeClassName",
 21813  		"SchedulerName",
 21814  		"SchedulingGates[*].Name",
 21815  		"SecurityContext.RunAsNonRoot",
 21816  		"ServiceAccountName",
 21817  		"SetHostnameAsFQDN",
 21818  		"Subdomain",
 21819  		"TerminationGracePeriodSeconds",
 21820  		"Volumes",
 21821  		"DNSConfig",
 21822  		"Overhead",
 21823  		"Tolerations",
 21824  		"TopologySpreadConstraints",
 21825  	)
 21826  
 21827  	expect := sets.NewString().Union(osSpecificFields).Union(osNeutralFields)
 21828  
 21829  	result := collectResourcePaths(t, expect, reflect.TypeOf(&core.PodSpec{}), nil)
 21830  
 21831  	if !expect.Equal(result) {
 21832  		// expected fields missing from result
 21833  		missing := expect.Difference(result)
 21834  		// unexpected fields in result but not specified in expect
 21835  		unexpected := result.Difference(expect)
 21836  		if len(missing) > 0 {
 21837  			t.Errorf("the following fields were expected, but missing from the result. "+
 21838  				"If the field has been removed, please remove it from the osNeutralFields set "+
 21839  				"or remove it from the osSpecificFields set, as appropriate:\n%s",
 21840  				strings.Join(missing.List(), "\n"))
 21841  		}
 21842  		if len(unexpected) > 0 {
 21843  			t.Errorf("the following fields were in the result, but unexpected. "+
 21844  				"If the field is new, please add it to the osNeutralFields set "+
 21845  				"or add it to the osSpecificFields set, as appropriate:\n%s",
 21846  				strings.Join(unexpected.List(), "\n"))
 21847  		}
 21848  	}
 21849  }
 21850  
 21851  func TestValidateSchedulingGates(t *testing.T) {
 21852  	fieldPath := field.NewPath("field")
 21853  
 21854  	tests := []struct {
 21855  		name            string
 21856  		schedulingGates []core.PodSchedulingGate
 21857  		wantFieldErrors field.ErrorList
 21858  	}{{
 21859  		name:            "nil gates",
 21860  		schedulingGates: nil,
 21861  		wantFieldErrors: field.ErrorList{},
 21862  	}, {
 21863  		name: "empty string in gates",
 21864  		schedulingGates: []core.PodSchedulingGate{
 21865  			{Name: "foo"},
 21866  			{Name: ""},
 21867  		},
 21868  		wantFieldErrors: field.ErrorList{
 21869  			field.Invalid(fieldPath.Index(1), "", "name part must be non-empty"),
 21870  			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]')"),
 21871  		},
 21872  	}, {
 21873  		name: "legal gates",
 21874  		schedulingGates: []core.PodSchedulingGate{
 21875  			{Name: "foo"},
 21876  			{Name: "bar"},
 21877  		},
 21878  		wantFieldErrors: field.ErrorList{},
 21879  	}, {
 21880  		name: "illegal gates",
 21881  		schedulingGates: []core.PodSchedulingGate{
 21882  			{Name: "foo"},
 21883  			{Name: "\nbar"},
 21884  		},
 21885  		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]')")},
 21886  	}, {
 21887  		name: "duplicated gates (single duplication)",
 21888  		schedulingGates: []core.PodSchedulingGate{
 21889  			{Name: "foo"},
 21890  			{Name: "bar"},
 21891  			{Name: "bar"},
 21892  		},
 21893  		wantFieldErrors: []*field.Error{field.Duplicate(fieldPath.Index(2), "bar")},
 21894  	}, {
 21895  		name: "duplicated gates (multiple duplications)",
 21896  		schedulingGates: []core.PodSchedulingGate{
 21897  			{Name: "foo"},
 21898  			{Name: "bar"},
 21899  			{Name: "foo"},
 21900  			{Name: "baz"},
 21901  			{Name: "foo"},
 21902  			{Name: "bar"},
 21903  		},
 21904  		wantFieldErrors: field.ErrorList{
 21905  			field.Duplicate(fieldPath.Index(2), "foo"),
 21906  			field.Duplicate(fieldPath.Index(4), "foo"),
 21907  			field.Duplicate(fieldPath.Index(5), "bar"),
 21908  		},
 21909  	},
 21910  	}
 21911  	for _, tt := range tests {
 21912  		t.Run(tt.name, func(t *testing.T) {
 21913  			errs := validateSchedulingGates(tt.schedulingGates, fieldPath)
 21914  			if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
 21915  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 21916  			}
 21917  		})
 21918  	}
 21919  }
 21920  
 21921  // collectResourcePaths traverses the object, computing all the struct paths.
 21922  func collectResourcePaths(t *testing.T, skipRecurseList sets.String, tp reflect.Type, path *field.Path) sets.String {
 21923  	if pathStr := path.String(); len(pathStr) > 0 && skipRecurseList.Has(pathStr) {
 21924  		return sets.NewString(pathStr)
 21925  	}
 21926  
 21927  	paths := sets.NewString()
 21928  	switch tp.Kind() {
 21929  	case reflect.Pointer:
 21930  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path).List()...)
 21931  	case reflect.Struct:
 21932  		for i := 0; i < tp.NumField(); i++ {
 21933  			field := tp.Field(i)
 21934  			paths.Insert(collectResourcePaths(t, skipRecurseList, field.Type, path.Child(field.Name)).List()...)
 21935  		}
 21936  	case reflect.Map, reflect.Slice:
 21937  		paths.Insert(collectResourcePaths(t, skipRecurseList, tp.Elem(), path.Key("*")).List()...)
 21938  	case reflect.Interface:
 21939  		t.Fatalf("unexpected interface{} field %s", path.String())
 21940  	default:
 21941  		// if we hit a primitive type, we're at a leaf
 21942  		paths.Insert(path.String())
 21943  	}
 21944  	return paths
 21945  }
 21946  
 21947  func TestValidateTLSSecret(t *testing.T) {
 21948  	successCases := map[string]core.Secret{
 21949  		"empty certificate chain": {
 21950  			ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"},
 21951  			Data: map[string][]byte{
 21952  				core.TLSCertKey:       []byte("public key"),
 21953  				core.TLSPrivateKeyKey: []byte("private key"),
 21954  			},
 21955  		},
 21956  	}
 21957  	for k, v := range successCases {
 21958  		if errs := ValidateSecret(&v); len(errs) != 0 {
 21959  			t.Errorf("Expected success for %s, got %v", k, errs)
 21960  		}
 21961  	}
 21962  	errorCases := map[string]struct {
 21963  		secrets     core.Secret
 21964  		errorType   field.ErrorType
 21965  		errorDetail string
 21966  	}{
 21967  		"missing public key": {
 21968  			secrets: core.Secret{
 21969  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 21970  				Data: map[string][]byte{
 21971  					core.TLSCertKey: []byte("public key"),
 21972  				},
 21973  			},
 21974  			errorType: "FieldValueRequired",
 21975  		},
 21976  		"missing private key": {
 21977  			secrets: core.Secret{
 21978  				ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
 21979  				Data: map[string][]byte{
 21980  					core.TLSCertKey: []byte("public key"),
 21981  				},
 21982  			},
 21983  			errorType: "FieldValueRequired",
 21984  		},
 21985  	}
 21986  	for k, v := range errorCases {
 21987  		if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 21988  			t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 21989  		}
 21990  	}
 21991  }
 21992  
 21993  func TestValidateLinuxSecurityContext(t *testing.T) {
 21994  	runAsUser := int64(1)
 21995  	validLinuxSC := &core.SecurityContext{
 21996  		Privileged: utilpointer.Bool(false),
 21997  		Capabilities: &core.Capabilities{
 21998  			Add:  []core.Capability{"foo"},
 21999  			Drop: []core.Capability{"bar"},
 22000  		},
 22001  		SELinuxOptions: &core.SELinuxOptions{
 22002  			User:  "user",
 22003  			Role:  "role",
 22004  			Type:  "type",
 22005  			Level: "level",
 22006  		},
 22007  		RunAsUser: &runAsUser,
 22008  	}
 22009  	invalidLinuxSC := &core.SecurityContext{
 22010  		WindowsOptions: &core.WindowsSecurityContextOptions{RunAsUserName: utilpointer.String("myUser")},
 22011  	}
 22012  	cases := map[string]struct {
 22013  		sc          *core.PodSpec
 22014  		expectErr   bool
 22015  		errorType   field.ErrorType
 22016  		errorDetail string
 22017  	}{
 22018  		"valid SC, linux, no error": {
 22019  			sc:        &core.PodSpec{Containers: []core.Container{{SecurityContext: validLinuxSC}}},
 22020  			expectErr: false,
 22021  		},
 22022  		"invalid SC, linux, error": {
 22023  			sc:          &core.PodSpec{Containers: []core.Container{{SecurityContext: invalidLinuxSC}}},
 22024  			errorType:   "FieldValueForbidden",
 22025  			errorDetail: "windows options cannot be set for a linux pod",
 22026  			expectErr:   true,
 22027  		},
 22028  	}
 22029  	for k, v := range cases {
 22030  		t.Run(k, func(t *testing.T) {
 22031  			errs := validateLinux(v.sc, field.NewPath("field"))
 22032  			if v.expectErr && len(errs) > 0 {
 22033  				if errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22034  					t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22035  				}
 22036  			} else if v.expectErr && len(errs) == 0 {
 22037  				t.Errorf("Unexpected success")
 22038  			}
 22039  			if !v.expectErr && len(errs) != 0 {
 22040  				t.Errorf("Unexpected error(s): %v", errs)
 22041  			}
 22042  		})
 22043  	}
 22044  }
 22045  
 22046  func TestValidateSecurityContext(t *testing.T) {
 22047  	runAsUser := int64(1)
 22048  	fullValidSC := func() *core.SecurityContext {
 22049  		return &core.SecurityContext{
 22050  			Privileged: utilpointer.Bool(false),
 22051  			Capabilities: &core.Capabilities{
 22052  				Add:  []core.Capability{"foo"},
 22053  				Drop: []core.Capability{"bar"},
 22054  			},
 22055  			SELinuxOptions: &core.SELinuxOptions{
 22056  				User:  "user",
 22057  				Role:  "role",
 22058  				Type:  "type",
 22059  				Level: "level",
 22060  			},
 22061  			RunAsUser: &runAsUser,
 22062  		}
 22063  	}
 22064  
 22065  	// setup data
 22066  	allSettings := fullValidSC()
 22067  	noCaps := fullValidSC()
 22068  	noCaps.Capabilities = nil
 22069  
 22070  	noSELinux := fullValidSC()
 22071  	noSELinux.SELinuxOptions = nil
 22072  
 22073  	noPrivRequest := fullValidSC()
 22074  	noPrivRequest.Privileged = nil
 22075  
 22076  	noRunAsUser := fullValidSC()
 22077  	noRunAsUser.RunAsUser = nil
 22078  
 22079  	successCases := map[string]struct {
 22080  		sc *core.SecurityContext
 22081  	}{
 22082  		"all settings":    {allSettings},
 22083  		"no capabilities": {noCaps},
 22084  		"no selinux":      {noSELinux},
 22085  		"no priv request": {noPrivRequest},
 22086  		"no run as user":  {noRunAsUser},
 22087  	}
 22088  	for k, v := range successCases {
 22089  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) != 0 {
 22090  			t.Errorf("[%s] Expected success, got %v", k, errs)
 22091  		}
 22092  	}
 22093  
 22094  	privRequestWithGlobalDeny := fullValidSC()
 22095  	privRequestWithGlobalDeny.Privileged = utilpointer.Bool(true)
 22096  
 22097  	negativeRunAsUser := fullValidSC()
 22098  	negativeUser := int64(-1)
 22099  	negativeRunAsUser.RunAsUser = &negativeUser
 22100  
 22101  	privWithoutEscalation := fullValidSC()
 22102  	privWithoutEscalation.Privileged = utilpointer.Bool(true)
 22103  	privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 22104  
 22105  	capSysAdminWithoutEscalation := fullValidSC()
 22106  	capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
 22107  	capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.Bool(false)
 22108  
 22109  	errorCases := map[string]struct {
 22110  		sc           *core.SecurityContext
 22111  		errorType    field.ErrorType
 22112  		errorDetail  string
 22113  		capAllowPriv bool
 22114  	}{
 22115  		"request privileged when capabilities forbids": {
 22116  			sc:          privRequestWithGlobalDeny,
 22117  			errorType:   "FieldValueForbidden",
 22118  			errorDetail: "disallowed by cluster policy",
 22119  		},
 22120  		"negative RunAsUser": {
 22121  			sc:          negativeRunAsUser,
 22122  			errorType:   "FieldValueInvalid",
 22123  			errorDetail: "must be between",
 22124  		},
 22125  		"with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
 22126  			sc:          capSysAdminWithoutEscalation,
 22127  			errorType:   "FieldValueInvalid",
 22128  			errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
 22129  		},
 22130  		"with privileged and allowPrivilegeEscalation false": {
 22131  			sc:           privWithoutEscalation,
 22132  			errorType:    "FieldValueInvalid",
 22133  			errorDetail:  "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
 22134  			capAllowPriv: true,
 22135  		},
 22136  	}
 22137  	for k, v := range errorCases {
 22138  		capabilities.SetForTests(capabilities.Capabilities{
 22139  			AllowPrivileged: v.capAllowPriv,
 22140  		})
 22141  		if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
 22142  			t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
 22143  		}
 22144  	}
 22145  }
 22146  
 22147  func fakeValidSecurityContext(priv bool) *core.SecurityContext {
 22148  	return &core.SecurityContext{
 22149  		Privileged: &priv,
 22150  	}
 22151  }
 22152  
 22153  func TestValidPodLogOptions(t *testing.T) {
 22154  	now := metav1.Now()
 22155  	negative := int64(-1)
 22156  	zero := int64(0)
 22157  	positive := int64(1)
 22158  	tests := []struct {
 22159  		opt  core.PodLogOptions
 22160  		errs int
 22161  	}{
 22162  		{core.PodLogOptions{}, 0},
 22163  		{core.PodLogOptions{Previous: true}, 0},
 22164  		{core.PodLogOptions{Follow: true}, 0},
 22165  		{core.PodLogOptions{TailLines: &zero}, 0},
 22166  		{core.PodLogOptions{TailLines: &negative}, 1},
 22167  		{core.PodLogOptions{TailLines: &positive}, 0},
 22168  		{core.PodLogOptions{LimitBytes: &zero}, 1},
 22169  		{core.PodLogOptions{LimitBytes: &negative}, 1},
 22170  		{core.PodLogOptions{LimitBytes: &positive}, 0},
 22171  		{core.PodLogOptions{SinceSeconds: &negative}, 1},
 22172  		{core.PodLogOptions{SinceSeconds: &positive}, 0},
 22173  		{core.PodLogOptions{SinceSeconds: &zero}, 1},
 22174  		{core.PodLogOptions{SinceTime: &now}, 0},
 22175  	}
 22176  	for i, test := range tests {
 22177  		errs := ValidatePodLogOptions(&test.opt)
 22178  		if test.errs != len(errs) {
 22179  			t.Errorf("%d: Unexpected errors: %v", i, errs)
 22180  		}
 22181  	}
 22182  }
 22183  
 22184  func TestValidateConfigMap(t *testing.T) {
 22185  	newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap {
 22186  		return core.ConfigMap{
 22187  			ObjectMeta: metav1.ObjectMeta{
 22188  				Name:      name,
 22189  				Namespace: namespace,
 22190  			},
 22191  			Data:       data,
 22192  			BinaryData: binaryData,
 22193  		}
 22194  	}
 22195  
 22196  	var (
 22197  		validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")})
 22198  		maxKeyLength   = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil)
 22199  
 22200  		emptyName               = newConfigMap("", "validns", nil, nil)
 22201  		invalidName             = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil)
 22202  		emptyNs                 = newConfigMap("validname", "", nil, nil)
 22203  		invalidNs               = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil)
 22204  		invalidKey              = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil)
 22205  		leadingDotKey           = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil)
 22206  		dotKey                  = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil)
 22207  		doubleDotKey            = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil)
 22208  		overMaxKeyLength        = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil)
 22209  		overMaxSize             = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil)
 22210  		duplicatedKey           = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")})
 22211  		binDataInvalidKey       = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")})
 22212  		binDataLeadingDotKey    = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")})
 22213  		binDataDotKey           = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")})
 22214  		binDataDoubleDotKey     = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")})
 22215  		binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")})
 22216  		binDataOverMaxSize      = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)})
 22217  		binNonUtf8Value         = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}})
 22218  	)
 22219  
 22220  	tests := map[string]struct {
 22221  		cfg     core.ConfigMap
 22222  		isValid bool
 22223  	}{
 22224  		"valid":                           {validConfigMap, true},
 22225  		"max key length":                  {maxKeyLength, true},
 22226  		"leading dot key":                 {leadingDotKey, true},
 22227  		"empty name":                      {emptyName, false},
 22228  		"invalid name":                    {invalidName, false},
 22229  		"invalid key":                     {invalidKey, false},
 22230  		"empty namespace":                 {emptyNs, false},
 22231  		"invalid namespace":               {invalidNs, false},
 22232  		"dot key":                         {dotKey, false},
 22233  		"double dot key":                  {doubleDotKey, false},
 22234  		"over max key length":             {overMaxKeyLength, false},
 22235  		"over max size":                   {overMaxSize, false},
 22236  		"duplicated key":                  {duplicatedKey, false},
 22237  		"binary data invalid key":         {binDataInvalidKey, false},
 22238  		"binary data leading dot key":     {binDataLeadingDotKey, true},
 22239  		"binary data dot key":             {binDataDotKey, false},
 22240  		"binary data double dot key":      {binDataDoubleDotKey, false},
 22241  		"binary data over max key length": {binDataOverMaxKeyLength, false},
 22242  		"binary data max size":            {binDataOverMaxSize, false},
 22243  		"binary data non utf-8 bytes":     {binNonUtf8Value, true},
 22244  	}
 22245  
 22246  	for name, tc := range tests {
 22247  		errs := ValidateConfigMap(&tc.cfg)
 22248  		if tc.isValid && len(errs) > 0 {
 22249  			t.Errorf("%v: unexpected error: %v", name, errs)
 22250  		}
 22251  		if !tc.isValid && len(errs) == 0 {
 22252  			t.Errorf("%v: unexpected non-error", name)
 22253  		}
 22254  	}
 22255  }
 22256  
 22257  func TestValidateConfigMapUpdate(t *testing.T) {
 22258  	newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap {
 22259  		return core.ConfigMap{
 22260  			ObjectMeta: metav1.ObjectMeta{
 22261  				Name:            name,
 22262  				Namespace:       namespace,
 22263  				ResourceVersion: version,
 22264  			},
 22265  			Data: data,
 22266  		}
 22267  	}
 22268  	validConfigMap := func() core.ConfigMap {
 22269  		return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
 22270  	}
 22271  
 22272  	falseVal := false
 22273  	trueVal := true
 22274  
 22275  	configMap := validConfigMap()
 22276  	immutableConfigMap := validConfigMap()
 22277  	immutableConfigMap.Immutable = &trueVal
 22278  	mutableConfigMap := validConfigMap()
 22279  	mutableConfigMap.Immutable = &falseVal
 22280  
 22281  	configMapWithData := validConfigMap()
 22282  	configMapWithData.Data["key-2"] = "value-2"
 22283  	immutableConfigMapWithData := validConfigMap()
 22284  	immutableConfigMapWithData.Immutable = &trueVal
 22285  	immutableConfigMapWithData.Data["key-2"] = "value-2"
 22286  
 22287  	configMapWithChangedData := validConfigMap()
 22288  	configMapWithChangedData.Data["key"] = "foo"
 22289  	immutableConfigMapWithChangedData := validConfigMap()
 22290  	immutableConfigMapWithChangedData.Immutable = &trueVal
 22291  	immutableConfigMapWithChangedData.Data["key"] = "foo"
 22292  
 22293  	noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
 22294  
 22295  	cases := []struct {
 22296  		name   string
 22297  		newCfg core.ConfigMap
 22298  		oldCfg core.ConfigMap
 22299  		valid  bool
 22300  	}{{
 22301  		name:   "valid",
 22302  		newCfg: configMap,
 22303  		oldCfg: configMap,
 22304  		valid:  true,
 22305  	}, {
 22306  		name:   "invalid",
 22307  		newCfg: noVersion,
 22308  		oldCfg: configMap,
 22309  		valid:  false,
 22310  	}, {
 22311  		name:   "mark configmap immutable",
 22312  		oldCfg: configMap,
 22313  		newCfg: immutableConfigMap,
 22314  		valid:  true,
 22315  	}, {
 22316  		name:   "revert immutable configmap",
 22317  		oldCfg: immutableConfigMap,
 22318  		newCfg: configMap,
 22319  		valid:  false,
 22320  	}, {
 22321  		name:   "mark immutable configmap mutable",
 22322  		oldCfg: immutableConfigMap,
 22323  		newCfg: mutableConfigMap,
 22324  		valid:  false,
 22325  	}, {
 22326  		name:   "add data in configmap",
 22327  		oldCfg: configMap,
 22328  		newCfg: configMapWithData,
 22329  		valid:  true,
 22330  	}, {
 22331  		name:   "add data in immutable configmap",
 22332  		oldCfg: immutableConfigMap,
 22333  		newCfg: immutableConfigMapWithData,
 22334  		valid:  false,
 22335  	}, {
 22336  		name:   "change data in configmap",
 22337  		oldCfg: configMap,
 22338  		newCfg: configMapWithChangedData,
 22339  		valid:  true,
 22340  	}, {
 22341  		name:   "change data in immutable configmap",
 22342  		oldCfg: immutableConfigMap,
 22343  		newCfg: immutableConfigMapWithChangedData,
 22344  		valid:  false,
 22345  	},
 22346  	}
 22347  
 22348  	for _, tc := range cases {
 22349  		t.Run(tc.name, func(t *testing.T) {
 22350  			errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
 22351  			if tc.valid && len(errs) > 0 {
 22352  				t.Errorf("Unexpected error: %v", errs)
 22353  			}
 22354  			if !tc.valid && len(errs) == 0 {
 22355  				t.Errorf("Unexpected lack of error")
 22356  			}
 22357  		})
 22358  	}
 22359  }
 22360  
 22361  func TestValidateHasLabel(t *testing.T) {
 22362  	successCase := metav1.ObjectMeta{
 22363  		Name:      "123",
 22364  		Namespace: "ns",
 22365  		Labels: map[string]string{
 22366  			"other": "blah",
 22367  			"foo":   "bar",
 22368  		},
 22369  	}
 22370  	if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 {
 22371  		t.Errorf("expected success: %v", errs)
 22372  	}
 22373  
 22374  	missingCase := metav1.ObjectMeta{
 22375  		Name:      "123",
 22376  		Namespace: "ns",
 22377  		Labels: map[string]string{
 22378  			"other": "blah",
 22379  		},
 22380  	}
 22381  	if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 22382  		t.Errorf("expected failure")
 22383  	}
 22384  
 22385  	wrongValueCase := metav1.ObjectMeta{
 22386  		Name:      "123",
 22387  		Namespace: "ns",
 22388  		Labels: map[string]string{
 22389  			"other": "blah",
 22390  			"foo":   "notbar",
 22391  		},
 22392  	}
 22393  	if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
 22394  		t.Errorf("expected failure")
 22395  	}
 22396  }
 22397  
 22398  func TestIsValidSysctlName(t *testing.T) {
 22399  	valid := []string{
 22400  		"a.b.c.d",
 22401  		"a",
 22402  		"a_b",
 22403  		"a-b",
 22404  		"abc",
 22405  		"abc.def",
 22406  		"a/b/c/d",
 22407  		"a/b.c",
 22408  	}
 22409  	invalid := []string{
 22410  		"",
 22411  		"*",
 22412  		"ä",
 22413  		"a_",
 22414  		"_",
 22415  		"__",
 22416  		"_a",
 22417  		"_a._b",
 22418  		"-",
 22419  		".",
 22420  		"a.",
 22421  		".a",
 22422  		"a.b.",
 22423  		"a*.b",
 22424  		"a*b",
 22425  		"*a",
 22426  		"a.*",
 22427  		"*",
 22428  		"abc*",
 22429  		"a.abc*",
 22430  		"a.b.*",
 22431  		"Abc",
 22432  		"/",
 22433  		"/a",
 22434  		"a/abc*",
 22435  		"a/b/*",
 22436  		func(n int) string {
 22437  			x := make([]byte, n)
 22438  			for i := range x {
 22439  				x[i] = byte('a')
 22440  			}
 22441  			return string(x)
 22442  		}(256),
 22443  	}
 22444  
 22445  	for _, s := range valid {
 22446  		if !IsValidSysctlName(s) {
 22447  			t.Errorf("%q expected to be a valid sysctl name", s)
 22448  		}
 22449  	}
 22450  	for _, s := range invalid {
 22451  		if IsValidSysctlName(s) {
 22452  			t.Errorf("%q expected to be an invalid sysctl name", s)
 22453  		}
 22454  	}
 22455  }
 22456  
 22457  func TestValidateSysctls(t *testing.T) {
 22458  	valid := []string{
 22459  		"net.foo.bar",
 22460  		"kernel.shmmax",
 22461  		"net.ipv4.conf.enp3s0/200.forwarding",
 22462  		"net/ipv4/conf/enp3s0.200/forwarding",
 22463  	}
 22464  	invalid := []string{
 22465  		"i..nvalid",
 22466  		"_invalid",
 22467  	}
 22468  
 22469  	invalidWithHostNet := []string{
 22470  		"net.ipv4.conf.enp3s0/200.forwarding",
 22471  		"net/ipv4/conf/enp3s0.200/forwarding",
 22472  	}
 22473  
 22474  	invalidWithHostIPC := []string{
 22475  		"kernel.shmmax",
 22476  		"kernel.msgmax",
 22477  	}
 22478  
 22479  	duplicates := []string{
 22480  		"kernel.shmmax",
 22481  		"kernel.shmmax",
 22482  	}
 22483  	opts := PodValidationOptions{
 22484  		AllowNamespacedSysctlsForHostNetAndHostIPC: false,
 22485  	}
 22486  
 22487  	sysctls := make([]core.Sysctl, len(valid))
 22488  	validSecurityContext := &core.PodSecurityContext{
 22489  		Sysctls: sysctls,
 22490  	}
 22491  	for i, sysctl := range valid {
 22492  		sysctls[i].Name = sysctl
 22493  	}
 22494  	errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts)
 22495  	if len(errs) != 0 {
 22496  		t.Errorf("unexpected validation errors: %v", errs)
 22497  	}
 22498  
 22499  	sysctls = make([]core.Sysctl, len(invalid))
 22500  	for i, sysctl := range invalid {
 22501  		sysctls[i].Name = sysctl
 22502  	}
 22503  	inValidSecurityContext := &core.PodSecurityContext{
 22504  		Sysctls: sysctls,
 22505  	}
 22506  	errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts)
 22507  	if len(errs) != 2 {
 22508  		t.Errorf("expected 2 validation errors. Got: %v", errs)
 22509  	} else {
 22510  		if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) {
 22511  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 22512  		}
 22513  		if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) {
 22514  			t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
 22515  		}
 22516  	}
 22517  
 22518  	sysctls = make([]core.Sysctl, len(duplicates))
 22519  	for i, sysctl := range duplicates {
 22520  		sysctls[i].Name = sysctl
 22521  	}
 22522  	securityContextWithDup := &core.PodSecurityContext{
 22523  		Sysctls: sysctls,
 22524  	}
 22525  	errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts)
 22526  	if len(errs) != 1 {
 22527  		t.Errorf("unexpected validation errors: %v", errs)
 22528  	} else if errs[0].Type != field.ErrorTypeDuplicate {
 22529  		t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
 22530  	}
 22531  
 22532  	sysctls = make([]core.Sysctl, len(invalidWithHostNet))
 22533  	for i, sysctl := range invalidWithHostNet {
 22534  		sysctls[i].Name = sysctl
 22535  	}
 22536  	invalidSecurityContextWithHostNet := &core.PodSecurityContext{
 22537  		Sysctls:     sysctls,
 22538  		HostIPC:     false,
 22539  		HostNetwork: true,
 22540  	}
 22541  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 22542  	if len(errs) != 2 {
 22543  		t.Errorf("unexpected validation errors: %v", errs)
 22544  	}
 22545  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 22546  	errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
 22547  	if len(errs) != 0 {
 22548  		t.Errorf("unexpected validation errors: %v", errs)
 22549  	}
 22550  
 22551  	sysctls = make([]core.Sysctl, len(invalidWithHostIPC))
 22552  	for i, sysctl := range invalidWithHostIPC {
 22553  		sysctls[i].Name = sysctl
 22554  	}
 22555  	invalidSecurityContextWithHostIPC := &core.PodSecurityContext{
 22556  		Sysctls:     sysctls,
 22557  		HostIPC:     true,
 22558  		HostNetwork: false,
 22559  	}
 22560  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false
 22561  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 22562  	if len(errs) != 2 {
 22563  		t.Errorf("unexpected validation errors: %v", errs)
 22564  	}
 22565  	opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
 22566  	errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
 22567  	if len(errs) != 0 {
 22568  		t.Errorf("unexpected validation errors: %v", errs)
 22569  	}
 22570  }
 22571  
 22572  func newNodeNameEndpoint(nodeName string) *core.Endpoints {
 22573  	ep := &core.Endpoints{
 22574  		ObjectMeta: metav1.ObjectMeta{
 22575  			Name:            "foo",
 22576  			Namespace:       metav1.NamespaceDefault,
 22577  			ResourceVersion: "1",
 22578  		},
 22579  		Subsets: []core.EndpointSubset{{
 22580  			NotReadyAddresses: []core.EndpointAddress{},
 22581  			Ports:             []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}},
 22582  			Addresses: []core.EndpointAddress{{
 22583  				IP:       "8.8.8.8",
 22584  				Hostname: "zookeeper1",
 22585  				NodeName: &nodeName}}}}}
 22586  	return ep
 22587  }
 22588  
 22589  func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
 22590  	oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend")
 22591  	updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
 22592  	// Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused.
 22593  	// The same ip will now have a different nodeName.
 22594  	errList := ValidateEndpoints(updatedEndpoint)
 22595  	errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
 22596  	if len(errList) != 0 {
 22597  		t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
 22598  	}
 22599  }
 22600  
 22601  func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
 22602  	// Check NodeName DNS validation
 22603  	endpoint := newNodeNameEndpoint("illegal*.nodename")
 22604  	errList := ValidateEndpoints(endpoint)
 22605  	if len(errList) == 0 {
 22606  		t.Error("Endpoint should reject invalid NodeName")
 22607  	}
 22608  }
 22609  
 22610  func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
 22611  	endpoint := newNodeNameEndpoint("10.10.1.1")
 22612  	errList := ValidateEndpoints(endpoint)
 22613  	if len(errList) != 0 {
 22614  		t.Error("Endpoint should accept a NodeName that is an IP address")
 22615  	}
 22616  }
 22617  
 22618  func TestValidateFlexVolumeSource(t *testing.T) {
 22619  	testcases := map[string]struct {
 22620  		source       *core.FlexVolumeSource
 22621  		expectedErrs map[string]string
 22622  	}{
 22623  		"valid": {
 22624  			source:       &core.FlexVolumeSource{Driver: "foo"},
 22625  			expectedErrs: map[string]string{},
 22626  		},
 22627  		"valid with options": {
 22628  			source:       &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
 22629  			expectedErrs: map[string]string{},
 22630  		},
 22631  		"no driver": {
 22632  			source:       &core.FlexVolumeSource{Driver: ""},
 22633  			expectedErrs: map[string]string{"driver": "Required value"},
 22634  		},
 22635  		"reserved option keys": {
 22636  			source: &core.FlexVolumeSource{
 22637  				Driver: "foo",
 22638  				Options: map[string]string{
 22639  					// valid options
 22640  					"myns.io":               "A",
 22641  					"myns.io/bar":           "A",
 22642  					"myns.io/kubernetes.io": "A",
 22643  
 22644  					// invalid options
 22645  					"KUBERNETES.IO":     "A",
 22646  					"kubernetes.io":     "A",
 22647  					"kubernetes.io/":    "A",
 22648  					"kubernetes.io/foo": "A",
 22649  
 22650  					"alpha.kubernetes.io":     "A",
 22651  					"alpha.kubernetes.io/":    "A",
 22652  					"alpha.kubernetes.io/foo": "A",
 22653  
 22654  					"k8s.io":     "A",
 22655  					"k8s.io/":    "A",
 22656  					"k8s.io/foo": "A",
 22657  
 22658  					"alpha.k8s.io":     "A",
 22659  					"alpha.k8s.io/":    "A",
 22660  					"alpha.k8s.io/foo": "A",
 22661  				},
 22662  			},
 22663  			expectedErrs: map[string]string{
 22664  				"options[KUBERNETES.IO]":           "reserved",
 22665  				"options[kubernetes.io]":           "reserved",
 22666  				"options[kubernetes.io/]":          "reserved",
 22667  				"options[kubernetes.io/foo]":       "reserved",
 22668  				"options[alpha.kubernetes.io]":     "reserved",
 22669  				"options[alpha.kubernetes.io/]":    "reserved",
 22670  				"options[alpha.kubernetes.io/foo]": "reserved",
 22671  				"options[k8s.io]":                  "reserved",
 22672  				"options[k8s.io/]":                 "reserved",
 22673  				"options[k8s.io/foo]":              "reserved",
 22674  				"options[alpha.k8s.io]":            "reserved",
 22675  				"options[alpha.k8s.io/]":           "reserved",
 22676  				"options[alpha.k8s.io/foo]":        "reserved",
 22677  			},
 22678  		},
 22679  	}
 22680  
 22681  	for k, tc := range testcases {
 22682  		errs := validateFlexVolumeSource(tc.source, nil)
 22683  		for _, err := range errs {
 22684  			expectedErr, ok := tc.expectedErrs[err.Field]
 22685  			if !ok {
 22686  				t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
 22687  				continue
 22688  			}
 22689  			if !strings.Contains(err.Error(), expectedErr) {
 22690  				t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
 22691  				continue
 22692  			}
 22693  		}
 22694  		if len(errs) != len(tc.expectedErrs) {
 22695  			t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
 22696  			continue
 22697  		}
 22698  	}
 22699  }
 22700  
 22701  func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
 22702  	successCases := map[string]*core.SessionAffinityConfig{
 22703  		"non-empty config, valid timeout: 1": {
 22704  			ClientIP: &core.ClientIPConfig{
 22705  				TimeoutSeconds: utilpointer.Int32(1),
 22706  			},
 22707  		},
 22708  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": {
 22709  			ClientIP: &core.ClientIPConfig{
 22710  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds - 1),
 22711  			},
 22712  		},
 22713  		"non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": {
 22714  			ClientIP: &core.ClientIPConfig{
 22715  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds),
 22716  			},
 22717  		},
 22718  	}
 22719  
 22720  	for name, test := range successCases {
 22721  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
 22722  			t.Errorf("case: %s, expected success: %v", name, errs)
 22723  		}
 22724  	}
 22725  
 22726  	errorCases := map[string]*core.SessionAffinityConfig{
 22727  		"empty session affinity config": nil,
 22728  		"empty client IP config": {
 22729  			ClientIP: nil,
 22730  		},
 22731  		"empty timeoutSeconds": {
 22732  			ClientIP: &core.ClientIPConfig{
 22733  				TimeoutSeconds: nil,
 22734  			},
 22735  		},
 22736  		"non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": {
 22737  			ClientIP: &core.ClientIPConfig{
 22738  				TimeoutSeconds: utilpointer.Int32(core.MaxClientIPServiceAffinitySeconds + 1),
 22739  			},
 22740  		},
 22741  		"non-empty config, invalid timeout: -1": {
 22742  			ClientIP: &core.ClientIPConfig{
 22743  				TimeoutSeconds: utilpointer.Int32(-1),
 22744  			},
 22745  		},
 22746  		"non-empty config, invalid timeout: 0": {
 22747  			ClientIP: &core.ClientIPConfig{
 22748  				TimeoutSeconds: utilpointer.Int32(0),
 22749  			},
 22750  		},
 22751  	}
 22752  
 22753  	for name, test := range errorCases {
 22754  		if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
 22755  			t.Errorf("case: %v, expected failures: %v", name, errs)
 22756  		}
 22757  	}
 22758  }
 22759  
 22760  func TestValidateWindowsSecurityContextOptions(t *testing.T) {
 22761  	toPtr := func(s string) *string {
 22762  		return &s
 22763  	}
 22764  
 22765  	testCases := []struct {
 22766  		testName string
 22767  
 22768  		windowsOptions         *core.WindowsSecurityContextOptions
 22769  		expectedErrorSubstring string
 22770  	}{{
 22771  		testName: "a nil pointer",
 22772  	}, {
 22773  		testName:       "an empty struct",
 22774  		windowsOptions: &core.WindowsSecurityContextOptions{},
 22775  	}, {
 22776  		testName: "a valid input",
 22777  		windowsOptions: &core.WindowsSecurityContextOptions{
 22778  			GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"),
 22779  			GMSACredentialSpec:     toPtr("dummy-gmsa-crep-spec-contents"),
 22780  		},
 22781  	}, {
 22782  		testName: "a GMSA cred spec name that is not a valid resource name",
 22783  		windowsOptions: &core.WindowsSecurityContextOptions{
 22784  			// invalid because of the underscore
 22785  			GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"),
 22786  		},
 22787  		expectedErrorSubstring: dnsSubdomainLabelErrMsg,
 22788  	}, {
 22789  		testName: "empty GMSA cred spec contents",
 22790  		windowsOptions: &core.WindowsSecurityContextOptions{
 22791  			GMSACredentialSpec: toPtr(""),
 22792  		},
 22793  		expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string",
 22794  	}, {
 22795  		testName: "GMSA cred spec contents that are too long",
 22796  		windowsOptions: &core.WindowsSecurityContextOptions{
 22797  			GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)),
 22798  		},
 22799  		expectedErrorSubstring: "gmsaCredentialSpec size must be under",
 22800  	}, {
 22801  		testName: "RunAsUserName is nil",
 22802  		windowsOptions: &core.WindowsSecurityContextOptions{
 22803  			RunAsUserName: nil,
 22804  		},
 22805  	}, {
 22806  		testName: "a valid RunAsUserName",
 22807  		windowsOptions: &core.WindowsSecurityContextOptions{
 22808  			RunAsUserName: toPtr("Container. User"),
 22809  		},
 22810  	}, {
 22811  		testName: "a valid RunAsUserName with NetBios Domain",
 22812  		windowsOptions: &core.WindowsSecurityContextOptions{
 22813  			RunAsUserName: toPtr("Network Service\\Container. User"),
 22814  		},
 22815  	}, {
 22816  		testName: "a valid RunAsUserName with DNS Domain",
 22817  		windowsOptions: &core.WindowsSecurityContextOptions{
 22818  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"),
 22819  		},
 22820  	}, {
 22821  		testName: "a valid RunAsUserName with DNS Domain with a single character segment",
 22822  		windowsOptions: &core.WindowsSecurityContextOptions{
 22823  			RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"),
 22824  		},
 22825  	}, {
 22826  		testName: "a valid RunAsUserName with a long single segment DNS Domain",
 22827  		windowsOptions: &core.WindowsSecurityContextOptions{
 22828  			RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"),
 22829  		},
 22830  	}, {
 22831  		testName: "an empty RunAsUserName",
 22832  		windowsOptions: &core.WindowsSecurityContextOptions{
 22833  			RunAsUserName: toPtr(""),
 22834  		},
 22835  		expectedErrorSubstring: "runAsUserName cannot be an empty string",
 22836  	}, {
 22837  		testName: "RunAsUserName containing a control character",
 22838  		windowsOptions: &core.WindowsSecurityContextOptions{
 22839  			RunAsUserName: toPtr("Container\tUser"),
 22840  		},
 22841  		expectedErrorSubstring: "runAsUserName cannot contain control characters",
 22842  	}, {
 22843  		testName: "RunAsUserName containing too many backslashes",
 22844  		windowsOptions: &core.WindowsSecurityContextOptions{
 22845  			RunAsUserName: toPtr("Container\\Foo\\Lish"),
 22846  		},
 22847  		expectedErrorSubstring: "runAsUserName cannot contain more than one backslash",
 22848  	}, {
 22849  		testName: "RunAsUserName containing backslash but empty Domain",
 22850  		windowsOptions: &core.WindowsSecurityContextOptions{
 22851  			RunAsUserName: toPtr("\\User"),
 22852  		},
 22853  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 22854  	}, {
 22855  		testName: "RunAsUserName containing backslash but empty User",
 22856  		windowsOptions: &core.WindowsSecurityContextOptions{
 22857  			RunAsUserName: toPtr("Container\\"),
 22858  		},
 22859  		expectedErrorSubstring: "runAsUserName's User cannot be empty",
 22860  	}, {
 22861  		testName: "RunAsUserName's NetBios Domain is too long",
 22862  		windowsOptions: &core.WindowsSecurityContextOptions{
 22863  			RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"),
 22864  		},
 22865  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 22866  	}, {
 22867  		testName: "RunAsUserName's DNS Domain is too long",
 22868  		windowsOptions: &core.WindowsSecurityContextOptions{
 22869  			// even if this tests the max Domain length, the Domain should still be "valid".
 22870  			RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"),
 22871  		},
 22872  		expectedErrorSubstring: "runAsUserName's Domain length must be under",
 22873  	}, {
 22874  		testName: "RunAsUserName's User is too long",
 22875  		windowsOptions: &core.WindowsSecurityContextOptions{
 22876  			RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)),
 22877  		},
 22878  		expectedErrorSubstring: "runAsUserName's User length must not be longer than",
 22879  	}, {
 22880  		testName: "RunAsUserName's User cannot contain only spaces or periods",
 22881  		windowsOptions: &core.WindowsSecurityContextOptions{
 22882  			RunAsUserName: toPtr("... ..."),
 22883  		},
 22884  		expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces",
 22885  	}, {
 22886  		testName: "RunAsUserName's NetBios Domain cannot start with a dot",
 22887  		windowsOptions: &core.WindowsSecurityContextOptions{
 22888  			RunAsUserName: toPtr(".FooLish\\User"),
 22889  		},
 22890  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 22891  	}, {
 22892  		testName: "RunAsUserName's NetBios Domain cannot contain invalid characters",
 22893  		windowsOptions: &core.WindowsSecurityContextOptions{
 22894  			RunAsUserName: toPtr("Foo? Lish?\\User"),
 22895  		},
 22896  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
 22897  	}, {
 22898  		testName: "RunAsUserName's DNS Domain cannot contain invalid characters",
 22899  		windowsOptions: &core.WindowsSecurityContextOptions{
 22900  			RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"),
 22901  		},
 22902  		expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
 22903  	}, {
 22904  		testName: "RunAsUserName's User cannot contain invalid characters",
 22905  		windowsOptions: &core.WindowsSecurityContextOptions{
 22906  			RunAsUserName: toPtr("Container/User"),
 22907  		},
 22908  		expectedErrorSubstring: "runAsUserName's User cannot contain the following characters",
 22909  	},
 22910  	}
 22911  
 22912  	for _, testCase := range testCases {
 22913  		t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) {
 22914  			errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field"))
 22915  
 22916  			switch len(errs) {
 22917  			case 0:
 22918  				if testCase.expectedErrorSubstring != "" {
 22919  					t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring)
 22920  				}
 22921  			case 1:
 22922  				if testCase.expectedErrorSubstring == "" {
 22923  					t.Errorf("didn't expect a failure, got: %q", errs[0].Error())
 22924  				} else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) {
 22925  					t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error())
 22926  				}
 22927  			default:
 22928  				t.Errorf("got %d failures", len(errs))
 22929  				for i, err := range errs {
 22930  					t.Errorf("error %d: %q", i, err.Error())
 22931  				}
 22932  			}
 22933  		})
 22934  	}
 22935  }
 22936  
 22937  func testDataSourceInSpec(name, kind, apiGroup string) *core.PersistentVolumeClaimSpec {
 22938  	scName := "csi-plugin"
 22939  	dataSourceInSpec := core.PersistentVolumeClaimSpec{
 22940  		AccessModes: []core.PersistentVolumeAccessMode{
 22941  			core.ReadOnlyMany,
 22942  		},
 22943  		Resources: core.VolumeResourceRequirements{
 22944  			Requests: core.ResourceList{
 22945  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 22946  			},
 22947  		},
 22948  		StorageClassName: &scName,
 22949  		DataSource: &core.TypedLocalObjectReference{
 22950  			APIGroup: &apiGroup,
 22951  			Kind:     kind,
 22952  			Name:     name,
 22953  		},
 22954  	}
 22955  
 22956  	return &dataSourceInSpec
 22957  }
 22958  
 22959  func TestAlphaVolumePVCDataSource(t *testing.T) {
 22960  	testCases := []struct {
 22961  		testName     string
 22962  		claimSpec    core.PersistentVolumeClaimSpec
 22963  		expectedFail bool
 22964  	}{{
 22965  		testName:  "test create from valid snapshot source",
 22966  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 22967  	}, {
 22968  		testName:  "test create from valid pvc source",
 22969  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 22970  	}, {
 22971  		testName:     "test missing name in snapshot datasource should fail",
 22972  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 22973  		expectedFail: true,
 22974  	}, {
 22975  		testName:     "test missing kind in snapshot datasource should fail",
 22976  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 22977  		expectedFail: true,
 22978  	}, {
 22979  		testName:  "test create from valid generic custom resource source",
 22980  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 22981  	}, {
 22982  		testName:     "test invalid datasource should fail",
 22983  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 22984  		expectedFail: true,
 22985  	},
 22986  	}
 22987  
 22988  	for _, tc := range testCases {
 22989  		opts := PersistentVolumeClaimSpecValidationOptions{}
 22990  		if tc.expectedFail {
 22991  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 22992  				t.Errorf("expected failure: %v", errs)
 22993  			}
 22994  
 22995  		} else {
 22996  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 22997  				t.Errorf("expected success: %v", errs)
 22998  			}
 22999  		}
 23000  	}
 23001  }
 23002  
 23003  func testAnyDataSource(t *testing.T, ds, dsRef bool) {
 23004  	testCases := []struct {
 23005  		testName     string
 23006  		claimSpec    core.PersistentVolumeClaimSpec
 23007  		expectedFail bool
 23008  	}{{
 23009  		testName:  "test create from valid snapshot source",
 23010  		claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23011  	}, {
 23012  		testName:  "test create from valid pvc source",
 23013  		claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
 23014  	}, {
 23015  		testName:     "test missing name in snapshot datasource should fail",
 23016  		claimSpec:    *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
 23017  		expectedFail: true,
 23018  	}, {
 23019  		testName:     "test missing kind in snapshot datasource should fail",
 23020  		claimSpec:    *testDataSourceInSpec("test_snapshot", "", "snapshot.storage.k8s.io"),
 23021  		expectedFail: true,
 23022  	}, {
 23023  		testName:  "test create from valid generic custom resource source",
 23024  		claimSpec: *testDataSourceInSpec("test_generic", "Generic", "generic.storage.k8s.io"),
 23025  	}, {
 23026  		testName:     "test invalid datasource should fail",
 23027  		claimSpec:    *testDataSourceInSpec("test_pod", "Pod", ""),
 23028  		expectedFail: true,
 23029  	},
 23030  	}
 23031  
 23032  	for _, tc := range testCases {
 23033  		if dsRef {
 23034  			tc.claimSpec.DataSourceRef = &core.TypedObjectReference{
 23035  				APIGroup: tc.claimSpec.DataSource.APIGroup,
 23036  				Kind:     tc.claimSpec.DataSource.Kind,
 23037  				Name:     tc.claimSpec.DataSource.Name,
 23038  			}
 23039  		}
 23040  		if !ds {
 23041  			tc.claimSpec.DataSource = nil
 23042  		}
 23043  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23044  		if tc.expectedFail {
 23045  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23046  				t.Errorf("expected failure: %v", errs)
 23047  			}
 23048  		} else {
 23049  			if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23050  				t.Errorf("expected success: %v", errs)
 23051  			}
 23052  		}
 23053  	}
 23054  }
 23055  
 23056  func TestAnyDataSource(t *testing.T) {
 23057  	testAnyDataSource(t, true, false)
 23058  	testAnyDataSource(t, false, true)
 23059  	testAnyDataSource(t, true, false)
 23060  }
 23061  
 23062  func pvcSpecWithCrossNamespaceSource(apiGroup *string, kind string, namespace *string, name string, isDataSourceSet bool) *core.PersistentVolumeClaimSpec {
 23063  	scName := "csi-plugin"
 23064  	spec := core.PersistentVolumeClaimSpec{
 23065  		AccessModes: []core.PersistentVolumeAccessMode{
 23066  			core.ReadOnlyMany,
 23067  		},
 23068  		Resources: core.VolumeResourceRequirements{
 23069  			Requests: core.ResourceList{
 23070  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 23071  			},
 23072  		},
 23073  		StorageClassName: &scName,
 23074  		DataSourceRef: &core.TypedObjectReference{
 23075  			APIGroup:  apiGroup,
 23076  			Kind:      kind,
 23077  			Namespace: namespace,
 23078  			Name:      name,
 23079  		},
 23080  	}
 23081  
 23082  	if isDataSourceSet {
 23083  		spec.DataSource = &core.TypedLocalObjectReference{
 23084  			APIGroup: apiGroup,
 23085  			Kind:     kind,
 23086  			Name:     name,
 23087  		}
 23088  	}
 23089  	return &spec
 23090  }
 23091  
 23092  func TestCrossNamespaceSource(t *testing.T) {
 23093  	snapAPIGroup := "snapshot.storage.k8s.io"
 23094  	coreAPIGroup := ""
 23095  	unsupportedAPIGroup := "unsupported.example.com"
 23096  	snapKind := "VolumeSnapshot"
 23097  	pvcKind := "PersistentVolumeClaim"
 23098  	goodNS := "ns1"
 23099  	badNS := "a*b"
 23100  	emptyNS := ""
 23101  	goodName := "snapshot1"
 23102  
 23103  	testCases := []struct {
 23104  		testName     string
 23105  		expectedFail bool
 23106  		claimSpec    *core.PersistentVolumeClaimSpec
 23107  	}{{
 23108  		testName:     "Feature gate enabled and valid xns DataSourceRef specified",
 23109  		expectedFail: false,
 23110  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, false),
 23111  	}, {
 23112  		testName:     "Feature gate enabled and xns DataSourceRef with PVC source specified",
 23113  		expectedFail: false,
 23114  		claimSpec:    pvcSpecWithCrossNamespaceSource(&coreAPIGroup, pvcKind, &goodNS, goodName, false),
 23115  	}, {
 23116  		testName:     "Feature gate enabled and xns DataSourceRef with unsupported source specified",
 23117  		expectedFail: false,
 23118  		claimSpec:    pvcSpecWithCrossNamespaceSource(&unsupportedAPIGroup, "UnsupportedKind", &goodNS, goodName, false),
 23119  	}, {
 23120  		testName:     "Feature gate enabled and xns DataSourceRef with nil apiGroup",
 23121  		expectedFail: true,
 23122  		claimSpec:    pvcSpecWithCrossNamespaceSource(nil, "UnsupportedKind", &goodNS, goodName, false),
 23123  	}, {
 23124  		testName:     "Feature gate enabled and xns DataSourceRef with invalid namspace specified",
 23125  		expectedFail: true,
 23126  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &badNS, goodName, false),
 23127  	}, {
 23128  		testName:     "Feature gate enabled and xns DataSourceRef with nil namspace specified",
 23129  		expectedFail: false,
 23130  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, nil, goodName, false),
 23131  	}, {
 23132  		testName:     "Feature gate enabled and xns DataSourceRef with empty namspace specified",
 23133  		expectedFail: false,
 23134  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &emptyNS, goodName, false),
 23135  	}, {
 23136  		testName:     "Feature gate enabled and both xns DataSourceRef and DataSource specified",
 23137  		expectedFail: true,
 23138  		claimSpec:    pvcSpecWithCrossNamespaceSource(&snapAPIGroup, snapKind, &goodNS, goodName, true),
 23139  	},
 23140  	}
 23141  
 23142  	for _, tc := range testCases {
 23143  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)()
 23144  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)()
 23145  		opts := PersistentVolumeClaimSpecValidationOptions{}
 23146  		if tc.expectedFail {
 23147  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23148  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 23149  			}
 23150  		} else {
 23151  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23152  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 23153  			}
 23154  		}
 23155  	}
 23156  }
 23157  
 23158  func pvcSpecWithVolumeAttributesClassName(vacName *string) *core.PersistentVolumeClaimSpec {
 23159  	scName := "csi-plugin"
 23160  	spec := core.PersistentVolumeClaimSpec{
 23161  		AccessModes: []core.PersistentVolumeAccessMode{
 23162  			core.ReadOnlyMany,
 23163  		},
 23164  		Resources: core.VolumeResourceRequirements{
 23165  			Requests: core.ResourceList{
 23166  				core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
 23167  			},
 23168  		},
 23169  		StorageClassName:          &scName,
 23170  		VolumeAttributesClassName: vacName,
 23171  	}
 23172  	return &spec
 23173  }
 23174  
 23175  func TestVolumeAttributesClass(t *testing.T) {
 23176  	testCases := []struct {
 23177  		testName                    string
 23178  		expectedFail                bool
 23179  		enableVolumeAttributesClass bool
 23180  		claimSpec                   *core.PersistentVolumeClaimSpec
 23181  	}{
 23182  		{
 23183  			testName:                    "Feature gate enabled and valid no volumeAttributesClassName specified",
 23184  			expectedFail:                false,
 23185  			enableVolumeAttributesClass: true,
 23186  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(nil),
 23187  		},
 23188  		{
 23189  			testName:                    "Feature gate enabled and an empty volumeAttributesClassName specified",
 23190  			expectedFail:                false,
 23191  			enableVolumeAttributesClass: true,
 23192  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("")),
 23193  		},
 23194  		{
 23195  			testName:                    "Feature gate enabled and valid volumeAttributesClassName specified",
 23196  			expectedFail:                false,
 23197  			enableVolumeAttributesClass: true,
 23198  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("foo")),
 23199  		},
 23200  		{
 23201  			testName:                    "Feature gate enabled and invalid volumeAttributesClassName specified",
 23202  			expectedFail:                true,
 23203  			enableVolumeAttributesClass: true,
 23204  			claimSpec:                   pvcSpecWithVolumeAttributesClassName(utilpointer.String("-invalid-")),
 23205  		},
 23206  	}
 23207  	for _, tc := range testCases {
 23208  		opts := PersistentVolumeClaimSpecValidationOptions{
 23209  			EnableVolumeAttributesClass: tc.enableVolumeAttributesClass,
 23210  		}
 23211  		if tc.expectedFail {
 23212  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) == 0 {
 23213  				t.Errorf("%s: expected failure: %v", tc.testName, errs)
 23214  			}
 23215  		} else {
 23216  			if errs := ValidatePersistentVolumeClaimSpec(tc.claimSpec, field.NewPath("spec"), opts); len(errs) != 0 {
 23217  				t.Errorf("%s: expected success: %v", tc.testName, errs)
 23218  			}
 23219  		}
 23220  	}
 23221  }
 23222  
 23223  func TestValidateTopologySpreadConstraints(t *testing.T) {
 23224  	fieldPath := field.NewPath("field")
 23225  	subFldPath0 := fieldPath.Index(0)
 23226  	fieldPathMinDomains := subFldPath0.Child("minDomains")
 23227  	fieldPathMaxSkew := subFldPath0.Child("maxSkew")
 23228  	fieldPathTopologyKey := subFldPath0.Child("topologyKey")
 23229  	fieldPathWhenUnsatisfiable := subFldPath0.Child("whenUnsatisfiable")
 23230  	fieldPathTopologyKeyAndWhenUnsatisfiable := subFldPath0.Child("{topologyKey, whenUnsatisfiable}")
 23231  	fieldPathMatchLabelKeys := subFldPath0.Child("matchLabelKeys")
 23232  	nodeAffinityField := subFldPath0.Child("nodeAffinityPolicy")
 23233  	nodeTaintsField := subFldPath0.Child("nodeTaintsPolicy")
 23234  	labelSelectorField := subFldPath0.Child("labelSelector")
 23235  	unknown := core.NodeInclusionPolicy("Unknown")
 23236  	ignore := core.NodeInclusionPolicyIgnore
 23237  	honor := core.NodeInclusionPolicyHonor
 23238  
 23239  	testCases := []struct {
 23240  		name            string
 23241  		constraints     []core.TopologySpreadConstraint
 23242  		wantFieldErrors field.ErrorList
 23243  		opts            PodValidationOptions
 23244  	}{{
 23245  		name: "all required fields ok",
 23246  		constraints: []core.TopologySpreadConstraint{{
 23247  			MaxSkew:           1,
 23248  			TopologyKey:       "k8s.io/zone",
 23249  			WhenUnsatisfiable: core.DoNotSchedule,
 23250  			MinDomains:        utilpointer.Int32(3),
 23251  		}},
 23252  		wantFieldErrors: field.ErrorList{},
 23253  	}, {
 23254  		name: "missing MaxSkew",
 23255  		constraints: []core.TopologySpreadConstraint{
 23256  			{TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23257  		},
 23258  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(0), isNotPositiveErrorMsg)},
 23259  	}, {
 23260  		name: "negative MaxSkew",
 23261  		constraints: []core.TopologySpreadConstraint{
 23262  			{MaxSkew: -1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23263  		},
 23264  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMaxSkew, int32(-1), isNotPositiveErrorMsg)},
 23265  	}, {
 23266  		name: "can use MinDomains with ScheduleAnyway, when MinDomains = nil",
 23267  		constraints: []core.TopologySpreadConstraint{{
 23268  			MaxSkew:           1,
 23269  			TopologyKey:       "k8s.io/zone",
 23270  			WhenUnsatisfiable: core.ScheduleAnyway,
 23271  			MinDomains:        nil,
 23272  		}},
 23273  		wantFieldErrors: field.ErrorList{},
 23274  	}, {
 23275  		name: "negative minDomains is invalid",
 23276  		constraints: []core.TopologySpreadConstraint{{
 23277  			MaxSkew:           1,
 23278  			TopologyKey:       "k8s.io/zone",
 23279  			WhenUnsatisfiable: core.DoNotSchedule,
 23280  			MinDomains:        utilpointer.Int32(-1),
 23281  		}},
 23282  		wantFieldErrors: []*field.Error{field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg)},
 23283  	}, {
 23284  		name: "cannot use non-nil MinDomains with ScheduleAnyway",
 23285  		constraints: []core.TopologySpreadConstraint{{
 23286  			MaxSkew:           1,
 23287  			TopologyKey:       "k8s.io/zone",
 23288  			WhenUnsatisfiable: core.ScheduleAnyway,
 23289  			MinDomains:        utilpointer.Int32(10),
 23290  		}},
 23291  		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)))},
 23292  	}, {
 23293  		name: "use negative MinDomains with ScheduleAnyway(invalid)",
 23294  		constraints: []core.TopologySpreadConstraint{{
 23295  			MaxSkew:           1,
 23296  			TopologyKey:       "k8s.io/zone",
 23297  			WhenUnsatisfiable: core.ScheduleAnyway,
 23298  			MinDomains:        utilpointer.Int32(-1),
 23299  		}},
 23300  		wantFieldErrors: []*field.Error{
 23301  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), isNotPositiveErrorMsg),
 23302  			field.Invalid(fieldPathMinDomains, utilpointer.Int32(-1), fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", string(core.DoNotSchedule), string(core.ScheduleAnyway))),
 23303  		},
 23304  	}, {
 23305  		name: "missing TopologyKey",
 23306  		constraints: []core.TopologySpreadConstraint{
 23307  			{MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule},
 23308  		},
 23309  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 23310  	}, {
 23311  		name: "missing scheduling mode",
 23312  		constraints: []core.TopologySpreadConstraint{
 23313  			{MaxSkew: 1, TopologyKey: "k8s.io/zone"},
 23314  		},
 23315  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction(""), sets.List(supportedScheduleActions))},
 23316  	}, {
 23317  		name: "unsupported scheduling mode",
 23318  		constraints: []core.TopologySpreadConstraint{
 23319  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")},
 23320  		},
 23321  		wantFieldErrors: []*field.Error{field.NotSupported(fieldPathWhenUnsatisfiable, core.UnsatisfiableConstraintAction("N/A"), sets.List(supportedScheduleActions))},
 23322  	}, {
 23323  		name: "multiple constraints ok with all required fields",
 23324  		constraints: []core.TopologySpreadConstraint{
 23325  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23326  			{MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway},
 23327  		},
 23328  		wantFieldErrors: field.ErrorList{},
 23329  	}, {
 23330  		name: "multiple constraints missing TopologyKey on partial ones",
 23331  		constraints: []core.TopologySpreadConstraint{
 23332  			{MaxSkew: 1, WhenUnsatisfiable: core.ScheduleAnyway},
 23333  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23334  		},
 23335  		wantFieldErrors: []*field.Error{field.Required(fieldPathTopologyKey, "can not be empty")},
 23336  	}, {
 23337  		name: "duplicate constraints",
 23338  		constraints: []core.TopologySpreadConstraint{
 23339  			{MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23340  			{MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
 23341  		},
 23342  		wantFieldErrors: []*field.Error{
 23343  			field.Duplicate(fieldPathTopologyKeyAndWhenUnsatisfiable, fmt.Sprintf("{%v, %v}", "k8s.io/zone", core.DoNotSchedule)),
 23344  		},
 23345  	}, {
 23346  		name: "supported policy name set on NodeAffinityPolicy and NodeTaintsPolicy",
 23347  		constraints: []core.TopologySpreadConstraint{{
 23348  			MaxSkew:            1,
 23349  			TopologyKey:        "k8s.io/zone",
 23350  			WhenUnsatisfiable:  core.DoNotSchedule,
 23351  			NodeAffinityPolicy: &honor,
 23352  			NodeTaintsPolicy:   &ignore,
 23353  		}},
 23354  		wantFieldErrors: []*field.Error{},
 23355  	}, {
 23356  		name: "unsupported policy name set on NodeAffinityPolicy",
 23357  		constraints: []core.TopologySpreadConstraint{{
 23358  			MaxSkew:            1,
 23359  			TopologyKey:        "k8s.io/zone",
 23360  			WhenUnsatisfiable:  core.DoNotSchedule,
 23361  			NodeAffinityPolicy: &unknown,
 23362  			NodeTaintsPolicy:   &ignore,
 23363  		}},
 23364  		wantFieldErrors: []*field.Error{
 23365  			field.NotSupported(nodeAffinityField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 23366  		},
 23367  	}, {
 23368  		name: "unsupported policy name set on NodeTaintsPolicy",
 23369  		constraints: []core.TopologySpreadConstraint{{
 23370  			MaxSkew:            1,
 23371  			TopologyKey:        "k8s.io/zone",
 23372  			WhenUnsatisfiable:  core.DoNotSchedule,
 23373  			NodeAffinityPolicy: &honor,
 23374  			NodeTaintsPolicy:   &unknown,
 23375  		}},
 23376  		wantFieldErrors: []*field.Error{
 23377  			field.NotSupported(nodeTaintsField, &unknown, sets.List(supportedPodTopologySpreadNodePolicies)),
 23378  		},
 23379  	}, {
 23380  		name: "key in MatchLabelKeys isn't correctly defined",
 23381  		constraints: []core.TopologySpreadConstraint{{
 23382  			MaxSkew:           1,
 23383  			TopologyKey:       "k8s.io/zone",
 23384  			LabelSelector:     &metav1.LabelSelector{},
 23385  			WhenUnsatisfiable: core.DoNotSchedule,
 23386  			MatchLabelKeys:    []string{"/simple"},
 23387  		}},
 23388  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "/simple", "prefix part must be non-empty")},
 23389  	}, {
 23390  		name: "key exists in both matchLabelKeys and labelSelector",
 23391  		constraints: []core.TopologySpreadConstraint{{
 23392  			MaxSkew:           1,
 23393  			TopologyKey:       "k8s.io/zone",
 23394  			WhenUnsatisfiable: core.DoNotSchedule,
 23395  			MatchLabelKeys:    []string{"foo"},
 23396  			LabelSelector: &metav1.LabelSelector{
 23397  				MatchExpressions: []metav1.LabelSelectorRequirement{
 23398  					{
 23399  						Key:      "foo",
 23400  						Operator: metav1.LabelSelectorOpNotIn,
 23401  						Values:   []string{"value1", "value2"},
 23402  					},
 23403  				},
 23404  			},
 23405  		}},
 23406  		wantFieldErrors: field.ErrorList{field.Invalid(fieldPathMatchLabelKeys.Index(0), "foo", "exists in both matchLabelKeys and labelSelector")},
 23407  	}, {
 23408  		name: "key in MatchLabelKeys is forbidden to be specified when labelSelector is not set",
 23409  		constraints: []core.TopologySpreadConstraint{{
 23410  			MaxSkew:           1,
 23411  			TopologyKey:       "k8s.io/zone",
 23412  			WhenUnsatisfiable: core.DoNotSchedule,
 23413  			MatchLabelKeys:    []string{"foo"},
 23414  		}},
 23415  		wantFieldErrors: field.ErrorList{field.Forbidden(fieldPathMatchLabelKeys, "must not be specified when labelSelector is not set")},
 23416  	}, {
 23417  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 23418  		constraints: []core.TopologySpreadConstraint{{
 23419  			MaxSkew:           1,
 23420  			TopologyKey:       "k8s.io/zone",
 23421  			WhenUnsatisfiable: core.DoNotSchedule,
 23422  			MinDomains:        nil,
 23423  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 23424  		}},
 23425  		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]')")},
 23426  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 23427  	}, {
 23428  		name: "invalid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is true",
 23429  		constraints: []core.TopologySpreadConstraint{{
 23430  			MaxSkew:           1,
 23431  			TopologyKey:       "k8s.io/zone",
 23432  			WhenUnsatisfiable: core.DoNotSchedule,
 23433  			MinDomains:        nil,
 23434  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "foo"}},
 23435  		}},
 23436  		wantFieldErrors: []*field.Error{},
 23437  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: true},
 23438  	}, {
 23439  		name: "valid matchLabels set on labelSelector when AllowInvalidTopologySpreadConstraintLabelSelector is false",
 23440  		constraints: []core.TopologySpreadConstraint{{
 23441  			MaxSkew:           1,
 23442  			TopologyKey:       "k8s.io/zone",
 23443  			WhenUnsatisfiable: core.DoNotSchedule,
 23444  			MinDomains:        nil,
 23445  			LabelSelector:     &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}},
 23446  		}},
 23447  		wantFieldErrors: []*field.Error{},
 23448  		opts:            PodValidationOptions{AllowInvalidTopologySpreadConstraintLabelSelector: false},
 23449  	},
 23450  	}
 23451  
 23452  	for _, tc := range testCases {
 23453  		t.Run(tc.name, func(t *testing.T) {
 23454  			errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
 23455  			if diff := cmp.Diff(tc.wantFieldErrors, errs); diff != "" {
 23456  				t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
 23457  			}
 23458  		})
 23459  	}
 23460  }
 23461  
 23462  func TestValidateOverhead(t *testing.T) {
 23463  	successCase := []struct {
 23464  		Name     string
 23465  		overhead core.ResourceList
 23466  	}{{
 23467  		Name: "Valid Overhead for CPU + Memory",
 23468  		overhead: core.ResourceList{
 23469  			core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 23470  			core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
 23471  		},
 23472  	},
 23473  	}
 23474  	for _, tc := range successCase {
 23475  		if errs := validateOverhead(tc.overhead, field.NewPath("overheads"), PodValidationOptions{}); len(errs) != 0 {
 23476  			t.Errorf("%q unexpected error: %v", tc.Name, errs)
 23477  		}
 23478  	}
 23479  
 23480  	errorCase := []struct {
 23481  		Name     string
 23482  		overhead core.ResourceList
 23483  	}{{
 23484  		Name: "Invalid Overhead Resources",
 23485  		overhead: core.ResourceList{
 23486  			core.ResourceName("my.org"): resource.MustParse("10m"),
 23487  		},
 23488  	},
 23489  	}
 23490  	for _, tc := range errorCase {
 23491  		if errs := validateOverhead(tc.overhead, field.NewPath("resources"), PodValidationOptions{}); len(errs) == 0 {
 23492  			t.Errorf("%q expected error", tc.Name)
 23493  		}
 23494  	}
 23495  }
 23496  
 23497  // helper creates a pod with name, namespace and IPs
 23498  func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod {
 23499  	return core.Pod{
 23500  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 23501  		Spec: core.PodSpec{
 23502  			Containers: []core.Container{{
 23503  				Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 23504  			}},
 23505  			RestartPolicy: core.RestartPolicyAlways,
 23506  			DNSPolicy:     core.DNSClusterFirst,
 23507  		},
 23508  		Status: core.PodStatus{
 23509  			PodIPs: podIPs,
 23510  		},
 23511  	}
 23512  }
 23513  func TestPodIPsValidation(t *testing.T) {
 23514  	testCases := []struct {
 23515  		pod         core.Pod
 23516  		expectError bool
 23517  	}{{
 23518  		expectError: false,
 23519  		pod:         makePod("nil-ips", "ns", nil),
 23520  	}, {
 23521  		expectError: false,
 23522  		pod:         makePod("empty-podips-list", "ns", []core.PodIP{}),
 23523  	}, {
 23524  		expectError: false,
 23525  		pod:         makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}),
 23526  	}, {
 23527  		expectError: false,
 23528  		pod:         makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}),
 23529  	}, {
 23530  		expectError: false,
 23531  		pod:         makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 23532  	}, {
 23533  		expectError: false,
 23534  		pod:         makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 23535  	},
 23536  		/* failure cases start here */
 23537  		{
 23538  			expectError: true,
 23539  			pod:         makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}),
 23540  		}, {
 23541  			expectError: true,
 23542  			pod:         makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}),
 23543  		}, {
 23544  			expectError: true,
 23545  			pod:         makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 23546  		}, {
 23547  			expectError: true,
 23548  			pod:         makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 23549  		}, {
 23550  			expectError: true,
 23551  			pod:         makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 23552  		},
 23553  
 23554  		{
 23555  			expectError: true,
 23556  			pod:         makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 23557  		}, {
 23558  			expectError: true,
 23559  			pod:         makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 23560  		},
 23561  	}
 23562  
 23563  	for _, testCase := range testCases {
 23564  		t.Run(testCase.pod.Name, func(t *testing.T) {
 23565  			for _, oldTestCase := range testCases {
 23566  				newPod := testCase.pod.DeepCopy()
 23567  				newPod.ResourceVersion = "1"
 23568  
 23569  				oldPod := oldTestCase.pod.DeepCopy()
 23570  				oldPod.ResourceVersion = "1"
 23571  				oldPod.Name = newPod.Name
 23572  
 23573  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 23574  
 23575  				if len(errs) == 0 && testCase.expectError {
 23576  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 23577  				}
 23578  				if len(errs) != 0 && !testCase.expectError {
 23579  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 23580  				}
 23581  			}
 23582  		})
 23583  	}
 23584  }
 23585  
 23586  func makePodWithHostIPs(podName string, podNamespace string, hostIPs []core.HostIP) core.Pod {
 23587  	hostIP := ""
 23588  	if len(hostIPs) > 0 {
 23589  		hostIP = hostIPs[0].IP
 23590  	}
 23591  	return core.Pod{
 23592  		ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
 23593  		Spec: core.PodSpec{
 23594  			Containers: []core.Container{
 23595  				{
 23596  					Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
 23597  				},
 23598  			},
 23599  			RestartPolicy: core.RestartPolicyAlways,
 23600  			DNSPolicy:     core.DNSClusterFirst,
 23601  		},
 23602  		Status: core.PodStatus{
 23603  			HostIP:  hostIP,
 23604  			HostIPs: hostIPs,
 23605  		},
 23606  	}
 23607  }
 23608  
 23609  func TestHostIPsValidation(t *testing.T) {
 23610  	testCases := []struct {
 23611  		pod         core.Pod
 23612  		expectError bool
 23613  	}{
 23614  		{
 23615  			expectError: false,
 23616  			pod:         makePodWithHostIPs("nil-ips", "ns", nil),
 23617  		},
 23618  		{
 23619  			expectError: false,
 23620  			pod:         makePodWithHostIPs("empty-HostIPs-list", "ns", []core.HostIP{}),
 23621  		},
 23622  		{
 23623  			expectError: false,
 23624  			pod:         makePodWithHostIPs("single-ip-family-6", "ns", []core.HostIP{{IP: "::1"}}),
 23625  		},
 23626  		{
 23627  			expectError: false,
 23628  			pod:         makePodWithHostIPs("single-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}}),
 23629  		},
 23630  		{
 23631  			expectError: false,
 23632  			pod:         makePodWithHostIPs("dual-stack-4-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
 23633  		},
 23634  		{
 23635  			expectError: false,
 23636  			pod:         makePodWithHostIPs("dual-stack-6-4", "ns", []core.HostIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
 23637  		},
 23638  		/* failure cases start here */
 23639  		{
 23640  			expectError: true,
 23641  			pod:         makePodWithHostIPs("invalid-pod-ip", "ns", []core.HostIP{{IP: "this-is-not-an-ip"}}),
 23642  		},
 23643  		{
 23644  			expectError: true,
 23645  			pod:         makePodWithHostIPs("dualstack-same-ip-family-6", "ns", []core.HostIP{{IP: "::1"}, {IP: "::2"}}),
 23646  		},
 23647  		{
 23648  			expectError: true,
 23649  			pod:         makePodWithHostIPs("dualstack-same-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
 23650  		},
 23651  		{
 23652  			expectError: true,
 23653  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
 23654  		},
 23655  		{
 23656  			expectError: true,
 23657  			pod:         makePodWithHostIPs("dualstack-repeated-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
 23658  		},
 23659  
 23660  		{
 23661  			expectError: true,
 23662  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-4", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
 23663  		},
 23664  		{
 23665  			expectError: true,
 23666  			pod:         makePodWithHostIPs("dualstack-duplicate-ip-family-6", "ns", []core.HostIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
 23667  		},
 23668  	}
 23669  
 23670  	for _, testCase := range testCases {
 23671  		t.Run(testCase.pod.Name, func(t *testing.T) {
 23672  			for _, oldTestCase := range testCases {
 23673  				newPod := testCase.pod.DeepCopy()
 23674  				newPod.ResourceVersion = "1"
 23675  
 23676  				oldPod := oldTestCase.pod.DeepCopy()
 23677  				oldPod.ResourceVersion = "1"
 23678  				oldPod.Name = newPod.Name
 23679  
 23680  				errs := ValidatePodStatusUpdate(newPod, oldPod, PodValidationOptions{})
 23681  
 23682  				if len(errs) == 0 && testCase.expectError {
 23683  					t.Fatalf("expected failure for %s, but there were none", testCase.pod.Name)
 23684  				}
 23685  				if len(errs) != 0 && !testCase.expectError {
 23686  					t.Fatalf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
 23687  				}
 23688  			}
 23689  		})
 23690  	}
 23691  }
 23692  
 23693  // makes a node with pod cidr and a name
 23694  func makeNode(nodeName string, podCIDRs []string) core.Node {
 23695  	return core.Node{
 23696  		ObjectMeta: metav1.ObjectMeta{
 23697  			Name: nodeName,
 23698  		},
 23699  		Status: core.NodeStatus{
 23700  			Addresses: []core.NodeAddress{
 23701  				{Type: core.NodeExternalIP, Address: "something"},
 23702  			},
 23703  			Capacity: core.ResourceList{
 23704  				core.ResourceName(core.ResourceCPU):    resource.MustParse("10"),
 23705  				core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
 23706  			},
 23707  		},
 23708  		Spec: core.NodeSpec{
 23709  			PodCIDRs: podCIDRs,
 23710  		},
 23711  	}
 23712  }
 23713  func TestValidateNodeCIDRs(t *testing.T) {
 23714  	testCases := []struct {
 23715  		expectError bool
 23716  		node        core.Node
 23717  	}{{
 23718  		expectError: false,
 23719  		node:        makeNode("nil-pod-cidr", nil),
 23720  	}, {
 23721  		expectError: false,
 23722  		node:        makeNode("empty-pod-cidr", []string{}),
 23723  	}, {
 23724  		expectError: false,
 23725  		node:        makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}),
 23726  	}, {
 23727  		expectError: false,
 23728  		node:        makeNode("single-pod-cidr-6", []string{"2000::/10"}),
 23729  	},
 23730  
 23731  		{
 23732  			expectError: false,
 23733  			node:        makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}),
 23734  		}, {
 23735  			expectError: false,
 23736  			node:        makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}),
 23737  		},
 23738  		// error cases starts here
 23739  		{
 23740  			expectError: true,
 23741  			node:        makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}),
 23742  		}, {
 23743  			expectError: true,
 23744  			node:        makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}),
 23745  		}, {
 23746  			expectError: true,
 23747  			node:        makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}),
 23748  		}, {
 23749  			expectError: true,
 23750  			node:        makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}),
 23751  		}, {
 23752  			expectError: true,
 23753  			node:        makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}),
 23754  		}, {
 23755  			expectError: true,
 23756  			node:        makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}),
 23757  		}, {
 23758  			expectError: true,
 23759  			node:        makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}),
 23760  		},
 23761  	}
 23762  	for _, testCase := range testCases {
 23763  		errs := ValidateNode(&testCase.node)
 23764  		if len(errs) == 0 && testCase.expectError {
 23765  			t.Errorf("expected failure for %s, but there were none", testCase.node.Name)
 23766  			return
 23767  		}
 23768  		if len(errs) != 0 && !testCase.expectError {
 23769  			t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs)
 23770  			return
 23771  		}
 23772  	}
 23773  }
 23774  
 23775  func TestValidateSeccompAnnotationAndField(t *testing.T) {
 23776  	const containerName = "container"
 23777  	testProfile := "test"
 23778  
 23779  	for _, test := range []struct {
 23780  		description string
 23781  		pod         *core.Pod
 23782  		validation  func(*testing.T, string, field.ErrorList, *v1.Pod)
 23783  	}{{
 23784  		description: "Field type unconfined and annotation does not match",
 23785  		pod: &core.Pod{
 23786  			ObjectMeta: metav1.ObjectMeta{
 23787  				Annotations: map[string]string{
 23788  					v1.SeccompPodAnnotationKey: "not-matching",
 23789  				},
 23790  			},
 23791  			Spec: core.PodSpec{
 23792  				SecurityContext: &core.PodSecurityContext{
 23793  					SeccompProfile: &core.SeccompProfile{
 23794  						Type: core.SeccompProfileTypeUnconfined,
 23795  					},
 23796  				},
 23797  			},
 23798  		},
 23799  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23800  			require.NotNil(t, allErrs, desc)
 23801  		},
 23802  	}, {
 23803  		description: "Field type default and annotation does not match",
 23804  		pod: &core.Pod{
 23805  			ObjectMeta: metav1.ObjectMeta{
 23806  				Annotations: map[string]string{
 23807  					v1.SeccompPodAnnotationKey: "not-matching",
 23808  				},
 23809  			},
 23810  			Spec: core.PodSpec{
 23811  				SecurityContext: &core.PodSecurityContext{
 23812  					SeccompProfile: &core.SeccompProfile{
 23813  						Type: core.SeccompProfileTypeRuntimeDefault,
 23814  					},
 23815  				},
 23816  			},
 23817  		},
 23818  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23819  			require.NotNil(t, allErrs, desc)
 23820  		},
 23821  	}, {
 23822  		description: "Field type localhost and annotation does not match",
 23823  		pod: &core.Pod{
 23824  			ObjectMeta: metav1.ObjectMeta{
 23825  				Annotations: map[string]string{
 23826  					v1.SeccompPodAnnotationKey: "not-matching",
 23827  				},
 23828  			},
 23829  			Spec: core.PodSpec{
 23830  				SecurityContext: &core.PodSecurityContext{
 23831  					SeccompProfile: &core.SeccompProfile{
 23832  						Type:             core.SeccompProfileTypeLocalhost,
 23833  						LocalhostProfile: &testProfile,
 23834  					},
 23835  				},
 23836  			},
 23837  		},
 23838  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23839  			require.NotNil(t, allErrs, desc)
 23840  		},
 23841  	}, {
 23842  		description: "Field type localhost and localhost/ prefixed annotation does not match",
 23843  		pod: &core.Pod{
 23844  			ObjectMeta: metav1.ObjectMeta{
 23845  				Annotations: map[string]string{
 23846  					v1.SeccompPodAnnotationKey: "localhost/not-matching",
 23847  				},
 23848  			},
 23849  			Spec: core.PodSpec{
 23850  				SecurityContext: &core.PodSecurityContext{
 23851  					SeccompProfile: &core.SeccompProfile{
 23852  						Type:             core.SeccompProfileTypeLocalhost,
 23853  						LocalhostProfile: &testProfile,
 23854  					},
 23855  				},
 23856  			},
 23857  		},
 23858  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23859  			require.NotNil(t, allErrs, desc)
 23860  		},
 23861  	}, {
 23862  		description: "Field type unconfined and annotation does not match (container)",
 23863  		pod: &core.Pod{
 23864  			ObjectMeta: metav1.ObjectMeta{
 23865  				Annotations: map[string]string{
 23866  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 23867  				},
 23868  			},
 23869  			Spec: core.PodSpec{
 23870  				Containers: []core.Container{{
 23871  					Name: containerName,
 23872  					SecurityContext: &core.SecurityContext{
 23873  						SeccompProfile: &core.SeccompProfile{
 23874  							Type: core.SeccompProfileTypeUnconfined,
 23875  						},
 23876  					},
 23877  				}},
 23878  			},
 23879  		},
 23880  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23881  			require.NotNil(t, allErrs, desc)
 23882  		},
 23883  	}, {
 23884  		description: "Field type default and annotation does not match (container)",
 23885  		pod: &core.Pod{
 23886  			ObjectMeta: metav1.ObjectMeta{
 23887  				Annotations: map[string]string{
 23888  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 23889  				},
 23890  			},
 23891  			Spec: core.PodSpec{
 23892  				Containers: []core.Container{{
 23893  					Name: containerName,
 23894  					SecurityContext: &core.SecurityContext{
 23895  						SeccompProfile: &core.SeccompProfile{
 23896  							Type: core.SeccompProfileTypeRuntimeDefault,
 23897  						},
 23898  					},
 23899  				}},
 23900  			},
 23901  		},
 23902  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23903  			require.NotNil(t, allErrs, desc)
 23904  		},
 23905  	}, {
 23906  		description: "Field type localhost and annotation does not match (container)",
 23907  		pod: &core.Pod{
 23908  			ObjectMeta: metav1.ObjectMeta{
 23909  				Annotations: map[string]string{
 23910  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
 23911  				},
 23912  			},
 23913  			Spec: core.PodSpec{
 23914  				Containers: []core.Container{{
 23915  					Name: containerName,
 23916  					SecurityContext: &core.SecurityContext{
 23917  						SeccompProfile: &core.SeccompProfile{
 23918  							Type:             core.SeccompProfileTypeLocalhost,
 23919  							LocalhostProfile: &testProfile,
 23920  						},
 23921  					},
 23922  				}},
 23923  			},
 23924  		},
 23925  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23926  			require.NotNil(t, allErrs, desc)
 23927  		},
 23928  	}, {
 23929  		description: "Field type localhost and localhost/ prefixed annotation does not match (container)",
 23930  		pod: &core.Pod{
 23931  			ObjectMeta: metav1.ObjectMeta{
 23932  				Annotations: map[string]string{
 23933  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 23934  				},
 23935  			},
 23936  			Spec: core.PodSpec{
 23937  				Containers: []core.Container{{
 23938  					Name: containerName,
 23939  					SecurityContext: &core.SecurityContext{
 23940  						SeccompProfile: &core.SeccompProfile{
 23941  							Type:             core.SeccompProfileTypeLocalhost,
 23942  							LocalhostProfile: &testProfile,
 23943  						},
 23944  					},
 23945  				}},
 23946  			},
 23947  		},
 23948  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23949  			require.NotNil(t, allErrs, desc)
 23950  		},
 23951  	}, {
 23952  		description: "Nil errors must not be appended (pod)",
 23953  		pod: &core.Pod{
 23954  			ObjectMeta: metav1.ObjectMeta{
 23955  				Annotations: map[string]string{
 23956  					v1.SeccompPodAnnotationKey: "localhost/anyprofile",
 23957  				},
 23958  			},
 23959  			Spec: core.PodSpec{
 23960  				SecurityContext: &core.PodSecurityContext{
 23961  					SeccompProfile: &core.SeccompProfile{
 23962  						Type: "Abc",
 23963  					},
 23964  				},
 23965  				Containers: []core.Container{{
 23966  					Name: containerName,
 23967  				}},
 23968  			},
 23969  		},
 23970  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23971  			require.Empty(t, allErrs, desc)
 23972  		},
 23973  	}, {
 23974  		description: "Nil errors must not be appended (container)",
 23975  		pod: &core.Pod{
 23976  			ObjectMeta: metav1.ObjectMeta{
 23977  				Annotations: map[string]string{
 23978  					v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
 23979  				},
 23980  			},
 23981  			Spec: core.PodSpec{
 23982  				Containers: []core.Container{{
 23983  					SecurityContext: &core.SecurityContext{
 23984  						SeccompProfile: &core.SeccompProfile{
 23985  							Type: "Abc",
 23986  						},
 23987  					},
 23988  					Name: containerName,
 23989  				}},
 23990  			},
 23991  		},
 23992  		validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
 23993  			require.Empty(t, allErrs, desc)
 23994  		},
 23995  	},
 23996  	} {
 23997  		output := &v1.Pod{
 23998  			ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
 23999  		}
 24000  		for i, ctr := range test.pod.Spec.Containers {
 24001  			output.Spec.Containers = append(output.Spec.Containers, v1.Container{})
 24002  			if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
 24003  				output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{
 24004  					SeccompProfile: &v1.SeccompProfile{
 24005  						Type:             v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type),
 24006  						LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile,
 24007  					},
 24008  				}
 24009  			}
 24010  		}
 24011  		errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath(""))
 24012  		test.validation(t, test.description, errList, output)
 24013  	}
 24014  }
 24015  
 24016  func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) {
 24017  	rootFld := field.NewPath("")
 24018  	tests := []struct {
 24019  		description     string
 24020  		annotationValue string
 24021  		seccompField    *core.SeccompProfile
 24022  		fldPath         *field.Path
 24023  		expectedErr     *field.Error
 24024  	}{{
 24025  		description: "seccompField nil should return empty",
 24026  		expectedErr: nil,
 24027  	}, {
 24028  		description:     "unconfined annotation and SeccompProfileTypeUnconfined should return empty",
 24029  		annotationValue: "unconfined",
 24030  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 24031  		expectedErr:     nil,
 24032  	}, {
 24033  		description:     "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24034  		annotationValue: "runtime/default",
 24035  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24036  		expectedErr:     nil,
 24037  	}, {
 24038  		description:     "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
 24039  		annotationValue: "docker/default",
 24040  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24041  		expectedErr:     nil,
 24042  	}, {
 24043  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty",
 24044  		annotationValue: "localhost/test.json",
 24045  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("test.json")},
 24046  		expectedErr:     nil,
 24047  	}, {
 24048  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error",
 24049  		annotationValue: "localhost/test.json",
 24050  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost},
 24051  		fldPath:         rootFld,
 24052  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 24053  	}, {
 24054  		description:     "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error",
 24055  		annotationValue: "localhost/test.json",
 24056  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.String("different.json")},
 24057  		fldPath:         rootFld,
 24058  		expectedErr:     field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
 24059  	}, {
 24060  		description:     "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error",
 24061  		annotationValue: "localhost/test.json",
 24062  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
 24063  		fldPath:         rootFld,
 24064  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 24065  	}, {
 24066  		description:     "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error",
 24067  		annotationValue: "localhost/test.json",
 24068  		seccompField:    &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
 24069  		fldPath:         rootFld,
 24070  		expectedErr:     field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
 24071  	},
 24072  	}
 24073  
 24074  	for i, test := range tests {
 24075  		err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath)
 24076  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 24077  	}
 24078  }
 24079  
 24080  func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
 24081  	rootFld := field.NewPath("template")
 24082  	tests := []struct {
 24083  		description string
 24084  		spec        *core.PodTemplateSpec
 24085  		fldPath     *field.Path
 24086  		expectedErr field.ErrorList
 24087  	}{{
 24088  		description: "seccomp field and container annotation must match",
 24089  		fldPath:     rootFld,
 24090  		expectedErr: field.ErrorList{
 24091  			field.Forbidden(
 24092  				rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"),
 24093  				"seccomp type in annotation and field must match"),
 24094  		},
 24095  		spec: &core.PodTemplateSpec{
 24096  			ObjectMeta: metav1.ObjectMeta{
 24097  				Annotations: map[string]string{
 24098  					"container.seccomp.security.alpha.kubernetes.io/test2": "unconfined",
 24099  				},
 24100  			},
 24101  			Spec: core.PodSpec{
 24102  				Containers: []core.Container{{
 24103  					Name:                     "test1",
 24104  					Image:                    "alpine",
 24105  					ImagePullPolicy:          core.PullAlways,
 24106  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24107  				}, {
 24108  					SecurityContext: &core.SecurityContext{
 24109  						SeccompProfile: &core.SeccompProfile{
 24110  							Type: core.SeccompProfileTypeRuntimeDefault,
 24111  						},
 24112  					},
 24113  					Name:                     "test2",
 24114  					Image:                    "alpine",
 24115  					ImagePullPolicy:          core.PullAlways,
 24116  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24117  				}},
 24118  				RestartPolicy: core.RestartPolicyAlways,
 24119  				DNSPolicy:     core.DNSDefault,
 24120  			},
 24121  		},
 24122  	}, {
 24123  		description: "seccomp field and pod annotation must match",
 24124  		fldPath:     rootFld,
 24125  		expectedErr: field.ErrorList{
 24126  			field.Forbidden(
 24127  				rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"),
 24128  				"seccomp type in annotation and field must match"),
 24129  		},
 24130  		spec: &core.PodTemplateSpec{
 24131  			ObjectMeta: metav1.ObjectMeta{
 24132  				Annotations: map[string]string{
 24133  					"seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
 24134  				},
 24135  			},
 24136  			Spec: core.PodSpec{
 24137  				SecurityContext: &core.PodSecurityContext{
 24138  					SeccompProfile: &core.SeccompProfile{
 24139  						Type: core.SeccompProfileTypeUnconfined,
 24140  					},
 24141  				},
 24142  				Containers: []core.Container{{
 24143  					Name:                     "test",
 24144  					Image:                    "alpine",
 24145  					ImagePullPolicy:          core.PullAlways,
 24146  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24147  				}},
 24148  				RestartPolicy: core.RestartPolicyAlways,
 24149  				DNSPolicy:     core.DNSDefault,
 24150  			},
 24151  		},
 24152  	}, {
 24153  		description: "init seccomp field and container annotation must match",
 24154  		fldPath:     rootFld,
 24155  		expectedErr: field.ErrorList{
 24156  			field.Forbidden(
 24157  				rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"),
 24158  				"seccomp type in annotation and field must match"),
 24159  		},
 24160  		spec: &core.PodTemplateSpec{
 24161  			ObjectMeta: metav1.ObjectMeta{
 24162  				Annotations: map[string]string{
 24163  					"container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined",
 24164  				},
 24165  			},
 24166  			Spec: core.PodSpec{
 24167  				Containers: []core.Container{{
 24168  					Name:                     "test",
 24169  					Image:                    "alpine",
 24170  					ImagePullPolicy:          core.PullAlways,
 24171  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24172  				}},
 24173  				InitContainers: []core.Container{{
 24174  					Name: "init-test",
 24175  					SecurityContext: &core.SecurityContext{
 24176  						SeccompProfile: &core.SeccompProfile{
 24177  							Type: core.SeccompProfileTypeRuntimeDefault,
 24178  						},
 24179  					},
 24180  					Image:                    "alpine",
 24181  					ImagePullPolicy:          core.PullAlways,
 24182  					TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 24183  				}},
 24184  				RestartPolicy: core.RestartPolicyAlways,
 24185  				DNSPolicy:     core.DNSDefault,
 24186  			},
 24187  		},
 24188  	},
 24189  	}
 24190  
 24191  	for i, test := range tests {
 24192  		err := ValidatePodTemplateSpec(test.spec, rootFld, PodValidationOptions{})
 24193  		assert.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 24194  	}
 24195  }
 24196  
 24197  func TestValidateResourceRequirements(t *testing.T) {
 24198  	path := field.NewPath("resources")
 24199  	tests := []struct {
 24200  		name         string
 24201  		requirements core.ResourceRequirements
 24202  		opts         PodValidationOptions
 24203  	}{{
 24204  		name: "limits and requests of hugepage resource are equal",
 24205  		requirements: core.ResourceRequirements{
 24206  			Limits: core.ResourceList{
 24207  				core.ResourceCPU: resource.MustParse("10"),
 24208  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 24209  			},
 24210  			Requests: core.ResourceList{
 24211  				core.ResourceCPU: resource.MustParse("10"),
 24212  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 24213  			},
 24214  		},
 24215  		opts: PodValidationOptions{},
 24216  	}, {
 24217  		name: "limits and requests of memory resource are equal",
 24218  		requirements: core.ResourceRequirements{
 24219  			Limits: core.ResourceList{
 24220  				core.ResourceMemory: resource.MustParse("2Mi"),
 24221  			},
 24222  			Requests: core.ResourceList{
 24223  				core.ResourceMemory: resource.MustParse("2Mi"),
 24224  			},
 24225  		},
 24226  		opts: PodValidationOptions{},
 24227  	}, {
 24228  		name: "limits and requests of cpu resource are equal",
 24229  		requirements: core.ResourceRequirements{
 24230  			Limits: core.ResourceList{
 24231  				core.ResourceCPU: resource.MustParse("10"),
 24232  			},
 24233  			Requests: core.ResourceList{
 24234  				core.ResourceCPU: resource.MustParse("10"),
 24235  			},
 24236  		},
 24237  		opts: PodValidationOptions{},
 24238  	},
 24239  	}
 24240  
 24241  	for _, tc := range tests {
 24242  		t.Run(tc.name, func(t *testing.T) {
 24243  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 {
 24244  				t.Errorf("unexpected errors: %v", errs)
 24245  			}
 24246  		})
 24247  	}
 24248  
 24249  	errTests := []struct {
 24250  		name         string
 24251  		requirements core.ResourceRequirements
 24252  		opts         PodValidationOptions
 24253  	}{{
 24254  		name: "hugepage resource without cpu or memory",
 24255  		requirements: core.ResourceRequirements{
 24256  			Limits: core.ResourceList{
 24257  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 24258  			},
 24259  			Requests: core.ResourceList{
 24260  				core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"),
 24261  			},
 24262  		},
 24263  		opts: PodValidationOptions{},
 24264  	},
 24265  	}
 24266  
 24267  	for _, tc := range errTests {
 24268  		t.Run(tc.name, func(t *testing.T) {
 24269  			if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 {
 24270  				t.Error("expected errors")
 24271  			}
 24272  		})
 24273  	}
 24274  }
 24275  
 24276  func TestValidateNonSpecialIP(t *testing.T) {
 24277  	fp := field.NewPath("ip")
 24278  
 24279  	// Valid values.
 24280  	for _, tc := range []struct {
 24281  		desc string
 24282  		ip   string
 24283  	}{
 24284  		{"ipv4", "10.1.2.3"},
 24285  		{"ipv4 class E", "244.1.2.3"},
 24286  		{"ipv6", "2000::1"},
 24287  	} {
 24288  		t.Run(tc.desc, func(t *testing.T) {
 24289  			errs := ValidateNonSpecialIP(tc.ip, fp)
 24290  			if len(errs) != 0 {
 24291  				t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs)
 24292  			}
 24293  		})
 24294  	}
 24295  	// Invalid cases
 24296  	for _, tc := range []struct {
 24297  		desc string
 24298  		ip   string
 24299  	}{
 24300  		{"ipv4 unspecified", "0.0.0.0"},
 24301  		{"ipv6 unspecified", "::0"},
 24302  		{"ipv4 localhost", "127.0.0.0"},
 24303  		{"ipv4 localhost", "127.255.255.255"},
 24304  		{"ipv6 localhost", "::1"},
 24305  		{"ipv6 link local", "fe80::"},
 24306  		{"ipv6 local multicast", "ff02::"},
 24307  	} {
 24308  		t.Run(tc.desc, func(t *testing.T) {
 24309  			errs := ValidateNonSpecialIP(tc.ip, fp)
 24310  			if len(errs) == 0 {
 24311  				t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip)
 24312  			}
 24313  		})
 24314  	}
 24315  }
 24316  
 24317  func TestValidateHostUsers(t *testing.T) {
 24318  	falseVar := false
 24319  	trueVar := true
 24320  
 24321  	cases := []struct {
 24322  		name    string
 24323  		success bool
 24324  		spec    *core.PodSpec
 24325  	}{{
 24326  		name:    "empty",
 24327  		success: true,
 24328  		spec:    &core.PodSpec{},
 24329  	}, {
 24330  		name:    "hostUsers unset",
 24331  		success: true,
 24332  		spec: &core.PodSpec{
 24333  			SecurityContext: &core.PodSecurityContext{},
 24334  		},
 24335  	}, {
 24336  		name:    "hostUsers=false",
 24337  		success: true,
 24338  		spec: &core.PodSpec{
 24339  			SecurityContext: &core.PodSecurityContext{
 24340  				HostUsers: &falseVar,
 24341  			},
 24342  		},
 24343  	}, {
 24344  		name:    "hostUsers=true",
 24345  		success: true,
 24346  		spec: &core.PodSpec{
 24347  			SecurityContext: &core.PodSecurityContext{
 24348  				HostUsers: &trueVar,
 24349  			},
 24350  		},
 24351  	}, {
 24352  		name:    "hostUsers=false & volumes",
 24353  		success: true,
 24354  		spec: &core.PodSpec{
 24355  			SecurityContext: &core.PodSecurityContext{
 24356  				HostUsers: &falseVar,
 24357  			},
 24358  			Volumes: []core.Volume{{
 24359  				Name: "configmap",
 24360  				VolumeSource: core.VolumeSource{
 24361  					ConfigMap: &core.ConfigMapVolumeSource{
 24362  						LocalObjectReference: core.LocalObjectReference{Name: "configmap"},
 24363  					},
 24364  				},
 24365  			}, {
 24366  				Name: "secret",
 24367  				VolumeSource: core.VolumeSource{
 24368  					Secret: &core.SecretVolumeSource{
 24369  						SecretName: "secret",
 24370  					},
 24371  				},
 24372  			}, {
 24373  				Name: "downward-api",
 24374  				VolumeSource: core.VolumeSource{
 24375  					DownwardAPI: &core.DownwardAPIVolumeSource{},
 24376  				},
 24377  			}, {
 24378  				Name: "proj",
 24379  				VolumeSource: core.VolumeSource{
 24380  					Projected: &core.ProjectedVolumeSource{},
 24381  				},
 24382  			}, {
 24383  				Name: "empty-dir",
 24384  				VolumeSource: core.VolumeSource{
 24385  					EmptyDir: &core.EmptyDirVolumeSource{},
 24386  				},
 24387  			}},
 24388  		},
 24389  	}, {
 24390  		name:    "hostUsers=false - stateful volume",
 24391  		success: true,
 24392  		spec: &core.PodSpec{
 24393  			SecurityContext: &core.PodSecurityContext{
 24394  				HostUsers: &falseVar,
 24395  			},
 24396  			Volumes: []core.Volume{{
 24397  				Name: "host-path",
 24398  				VolumeSource: core.VolumeSource{
 24399  					HostPath: &core.HostPathVolumeSource{},
 24400  				},
 24401  			}},
 24402  		},
 24403  	}, {
 24404  		name:    "hostUsers=true - unsupported volume",
 24405  		success: true,
 24406  		spec: &core.PodSpec{
 24407  			SecurityContext: &core.PodSecurityContext{
 24408  				HostUsers: &trueVar,
 24409  			},
 24410  			Volumes: []core.Volume{{
 24411  				Name: "host-path",
 24412  				VolumeSource: core.VolumeSource{
 24413  					HostPath: &core.HostPathVolumeSource{},
 24414  				},
 24415  			}},
 24416  		},
 24417  	}, {
 24418  		name:    "hostUsers=false & HostNetwork",
 24419  		success: false,
 24420  		spec: &core.PodSpec{
 24421  			SecurityContext: &core.PodSecurityContext{
 24422  				HostUsers:   &falseVar,
 24423  				HostNetwork: true,
 24424  			},
 24425  		},
 24426  	}, {
 24427  		name:    "hostUsers=false & HostPID",
 24428  		success: false,
 24429  		spec: &core.PodSpec{
 24430  			SecurityContext: &core.PodSecurityContext{
 24431  				HostUsers: &falseVar,
 24432  				HostPID:   true,
 24433  			},
 24434  		},
 24435  	}, {
 24436  		name:    "hostUsers=false & HostIPC",
 24437  		success: false,
 24438  		spec: &core.PodSpec{
 24439  			SecurityContext: &core.PodSecurityContext{
 24440  				HostUsers: &falseVar,
 24441  				HostIPC:   true,
 24442  			},
 24443  		},
 24444  	},
 24445  	}
 24446  
 24447  	for _, tc := range cases {
 24448  		t.Run(tc.name, func(t *testing.T) {
 24449  			fPath := field.NewPath("spec")
 24450  
 24451  			allErrs := validateHostUsers(tc.spec, fPath)
 24452  			if !tc.success && len(allErrs) == 0 {
 24453  				t.Errorf("Unexpected success")
 24454  			}
 24455  			if tc.success && len(allErrs) != 0 {
 24456  				t.Errorf("Unexpected error(s): %v", allErrs)
 24457  			}
 24458  		})
 24459  	}
 24460  }
 24461  
 24462  func TestValidateWindowsHostProcessPod(t *testing.T) {
 24463  	const containerName = "container"
 24464  	falseVar := false
 24465  	trueVar := true
 24466  
 24467  	testCases := []struct {
 24468  		name            string
 24469  		expectError     bool
 24470  		allowPrivileged bool
 24471  		podSpec         *core.PodSpec
 24472  	}{{
 24473  		name:            "Spec with feature enabled, pod-wide HostProcess=true, and HostNetwork unset should not validate",
 24474  		expectError:     true,
 24475  		allowPrivileged: true,
 24476  		podSpec: &core.PodSpec{
 24477  			SecurityContext: &core.PodSecurityContext{
 24478  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24479  					HostProcess: &trueVar,
 24480  				},
 24481  			},
 24482  			Containers: []core.Container{{
 24483  				Name: containerName,
 24484  			}},
 24485  		},
 24486  	}, {
 24487  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, and HostNetwork set should validate",
 24488  		expectError:     false,
 24489  		allowPrivileged: true,
 24490  		podSpec: &core.PodSpec{
 24491  			SecurityContext: &core.PodSecurityContext{
 24492  				HostNetwork: true,
 24493  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24494  					HostProcess: &trueVar,
 24495  				},
 24496  			},
 24497  			Containers: []core.Container{{
 24498  				Name: containerName,
 24499  			}},
 24500  		},
 24501  	}, {
 24502  		name:            "Spec with feature enabled, pod-wide HostProcess=ture, HostNetwork set, and containers setting HostProcess=true should validate",
 24503  		expectError:     false,
 24504  		allowPrivileged: true,
 24505  		podSpec: &core.PodSpec{
 24506  			SecurityContext: &core.PodSecurityContext{
 24507  				HostNetwork: true,
 24508  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24509  					HostProcess: &trueVar,
 24510  				},
 24511  			},
 24512  			Containers: []core.Container{{
 24513  				Name: containerName,
 24514  				SecurityContext: &core.SecurityContext{
 24515  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24516  						HostProcess: &trueVar,
 24517  					},
 24518  				},
 24519  			}},
 24520  			InitContainers: []core.Container{{
 24521  				Name: containerName,
 24522  				SecurityContext: &core.SecurityContext{
 24523  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24524  						HostProcess: &trueVar,
 24525  					},
 24526  				},
 24527  			}},
 24528  		},
 24529  	}, {
 24530  		name:            "Spec with feature enabled, pod-wide HostProcess=nil, HostNetwork set, and all containers setting HostProcess=true should validate",
 24531  		expectError:     false,
 24532  		allowPrivileged: true,
 24533  		podSpec: &core.PodSpec{
 24534  			SecurityContext: &core.PodSecurityContext{
 24535  				HostNetwork: true,
 24536  			},
 24537  			Containers: []core.Container{{
 24538  				Name: containerName,
 24539  				SecurityContext: &core.SecurityContext{
 24540  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24541  						HostProcess: &trueVar,
 24542  					},
 24543  				},
 24544  			}},
 24545  			InitContainers: []core.Container{{
 24546  				Name: containerName,
 24547  				SecurityContext: &core.SecurityContext{
 24548  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24549  						HostProcess: &trueVar,
 24550  					},
 24551  				},
 24552  			}},
 24553  		},
 24554  	}, {
 24555  		name:            "Pods with feature enabled, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 24556  		expectError:     true,
 24557  		allowPrivileged: true,
 24558  		podSpec: &core.PodSpec{
 24559  			SecurityContext: &core.PodSecurityContext{
 24560  				HostNetwork: true,
 24561  			},
 24562  			Containers: []core.Container{{
 24563  				Name: containerName,
 24564  				SecurityContext: &core.SecurityContext{
 24565  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24566  						HostProcess: &trueVar,
 24567  					},
 24568  				},
 24569  			}},
 24570  			InitContainers: []core.Container{{
 24571  				Name: containerName,
 24572  				SecurityContext: &core.SecurityContext{
 24573  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24574  						HostProcess: &falseVar,
 24575  					},
 24576  				},
 24577  			}},
 24578  		},
 24579  	}, {
 24580  		name:            "Spec with feature enabled, some containers setting HostProcess=true, and other leaving HostProcess unset should not validate",
 24581  		expectError:     true,
 24582  		allowPrivileged: true,
 24583  		podSpec: &core.PodSpec{
 24584  			SecurityContext: &core.PodSecurityContext{
 24585  				HostNetwork: true,
 24586  			},
 24587  			Containers: []core.Container{{
 24588  				Name: containerName,
 24589  				SecurityContext: &core.SecurityContext{
 24590  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24591  						HostProcess: &trueVar,
 24592  					},
 24593  				},
 24594  			}},
 24595  			InitContainers: []core.Container{{
 24596  				Name: containerName,
 24597  			}},
 24598  		},
 24599  	}, {
 24600  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and init containers setting HostProcess=false should not validate",
 24601  		expectError:     true,
 24602  		allowPrivileged: true,
 24603  		podSpec: &core.PodSpec{
 24604  			SecurityContext: &core.PodSecurityContext{
 24605  				HostNetwork: true,
 24606  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24607  					HostProcess: &trueVar,
 24608  				},
 24609  			},
 24610  			Containers: []core.Container{{
 24611  				Name: containerName,
 24612  				SecurityContext: &core.SecurityContext{
 24613  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24614  						HostProcess: &trueVar,
 24615  					},
 24616  				},
 24617  			}},
 24618  			InitContainers: []core.Container{{
 24619  				Name: containerName,
 24620  				SecurityContext: &core.SecurityContext{
 24621  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24622  						HostProcess: &falseVar,
 24623  					},
 24624  				},
 24625  			}},
 24626  		},
 24627  	}, {
 24628  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others setting HostProcess=false should not validate",
 24629  		expectError:     true,
 24630  		allowPrivileged: true,
 24631  		podSpec: &core.PodSpec{
 24632  			SecurityContext: &core.PodSecurityContext{
 24633  				HostNetwork: true,
 24634  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24635  					HostProcess: &trueVar,
 24636  				},
 24637  			},
 24638  			Containers: []core.Container{{
 24639  				Name: containerName,
 24640  				SecurityContext: &core.SecurityContext{
 24641  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24642  						HostProcess: &trueVar,
 24643  					},
 24644  				},
 24645  			}, {
 24646  				Name: containerName,
 24647  				SecurityContext: &core.SecurityContext{
 24648  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24649  						HostProcess: &falseVar,
 24650  					},
 24651  				},
 24652  			}},
 24653  		},
 24654  	}, {
 24655  		name:            "Spec with feature enabled, pod-wide HostProcess=true, some containers setting HostProcess=true, and others leaving HostProcess=nil should validate",
 24656  		expectError:     false,
 24657  		allowPrivileged: true,
 24658  		podSpec: &core.PodSpec{
 24659  			SecurityContext: &core.PodSecurityContext{
 24660  				HostNetwork: true,
 24661  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24662  					HostProcess: &trueVar,
 24663  				},
 24664  			},
 24665  			Containers: []core.Container{{
 24666  				Name: containerName,
 24667  				SecurityContext: &core.SecurityContext{
 24668  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24669  						HostProcess: &trueVar,
 24670  					},
 24671  				},
 24672  			}},
 24673  			InitContainers: []core.Container{{
 24674  				Name: containerName,
 24675  			}},
 24676  		},
 24677  	}, {
 24678  		name:            "Spec with feature enabled, pod-wide HostProcess=false, some contaienrs setting HostProccess=true should not validate",
 24679  		expectError:     true,
 24680  		allowPrivileged: true,
 24681  		podSpec: &core.PodSpec{
 24682  			SecurityContext: &core.PodSecurityContext{
 24683  				HostNetwork: true,
 24684  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24685  					HostProcess: &falseVar,
 24686  				},
 24687  			},
 24688  			Containers: []core.Container{{
 24689  				Name: containerName,
 24690  				SecurityContext: &core.SecurityContext{
 24691  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24692  						HostProcess: &trueVar,
 24693  					},
 24694  				},
 24695  			}},
 24696  			InitContainers: []core.Container{{
 24697  				Name: containerName,
 24698  			}},
 24699  		},
 24700  	}, {
 24701  		name:            "Pod's HostProcess set to true but all containers override to false should not validate",
 24702  		expectError:     true,
 24703  		allowPrivileged: true,
 24704  		podSpec: &core.PodSpec{
 24705  			SecurityContext: &core.PodSecurityContext{
 24706  				HostNetwork: true,
 24707  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24708  					HostProcess: &trueVar,
 24709  				},
 24710  			},
 24711  			Containers: []core.Container{{
 24712  				Name: containerName,
 24713  				SecurityContext: &core.SecurityContext{
 24714  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24715  						HostProcess: &falseVar,
 24716  					},
 24717  				},
 24718  			}},
 24719  		},
 24720  	}, {
 24721  		name:            "Valid HostProcess pod should spec should not validate if allowPrivileged is not set",
 24722  		expectError:     true,
 24723  		allowPrivileged: false,
 24724  		podSpec: &core.PodSpec{
 24725  			SecurityContext: &core.PodSecurityContext{
 24726  				HostNetwork: true,
 24727  			},
 24728  			Containers: []core.Container{{
 24729  				Name: containerName,
 24730  				SecurityContext: &core.SecurityContext{
 24731  					WindowsOptions: &core.WindowsSecurityContextOptions{
 24732  						HostProcess: &trueVar,
 24733  					},
 24734  				},
 24735  			}},
 24736  		},
 24737  	}, {
 24738  		name:            "Non-HostProcess ephemeral container in HostProcess pod should not validate",
 24739  		expectError:     true,
 24740  		allowPrivileged: true,
 24741  		podSpec: &core.PodSpec{
 24742  			SecurityContext: &core.PodSecurityContext{
 24743  				HostNetwork: true,
 24744  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24745  					HostProcess: &trueVar,
 24746  				},
 24747  			},
 24748  			Containers: []core.Container{{
 24749  				Name: containerName,
 24750  			}},
 24751  			EphemeralContainers: []core.EphemeralContainer{{
 24752  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 24753  					SecurityContext: &core.SecurityContext{
 24754  						WindowsOptions: &core.WindowsSecurityContextOptions{
 24755  							HostProcess: &falseVar,
 24756  						},
 24757  					},
 24758  				},
 24759  			}},
 24760  		},
 24761  	}, {
 24762  		name:            "HostProcess ephemeral container in HostProcess pod should validate",
 24763  		expectError:     false,
 24764  		allowPrivileged: true,
 24765  		podSpec: &core.PodSpec{
 24766  			SecurityContext: &core.PodSecurityContext{
 24767  				HostNetwork: true,
 24768  				WindowsOptions: &core.WindowsSecurityContextOptions{
 24769  					HostProcess: &trueVar,
 24770  				},
 24771  			},
 24772  			Containers: []core.Container{{
 24773  				Name: containerName,
 24774  			}},
 24775  			EphemeralContainers: []core.EphemeralContainer{{
 24776  				EphemeralContainerCommon: core.EphemeralContainerCommon{},
 24777  			}},
 24778  		},
 24779  	}, {
 24780  		name:            "Non-HostProcess ephemeral container in Non-HostProcess pod should validate",
 24781  		expectError:     false,
 24782  		allowPrivileged: true,
 24783  		podSpec: &core.PodSpec{
 24784  			Containers: []core.Container{{
 24785  				Name: containerName,
 24786  			}},
 24787  			EphemeralContainers: []core.EphemeralContainer{{
 24788  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 24789  					SecurityContext: &core.SecurityContext{
 24790  						WindowsOptions: &core.WindowsSecurityContextOptions{
 24791  							HostProcess: &falseVar,
 24792  						},
 24793  					},
 24794  				},
 24795  			}},
 24796  		},
 24797  	}, {
 24798  		name:            "HostProcess ephemeral container in Non-HostProcess pod should not validate",
 24799  		expectError:     true,
 24800  		allowPrivileged: true,
 24801  		podSpec: &core.PodSpec{
 24802  			Containers: []core.Container{{
 24803  				Name: containerName,
 24804  			}},
 24805  			EphemeralContainers: []core.EphemeralContainer{{
 24806  				EphemeralContainerCommon: core.EphemeralContainerCommon{
 24807  					SecurityContext: &core.SecurityContext{
 24808  						WindowsOptions: &core.WindowsSecurityContextOptions{
 24809  							HostProcess: &trueVar,
 24810  						},
 24811  					},
 24812  				},
 24813  			}},
 24814  		},
 24815  	},
 24816  	}
 24817  
 24818  	for _, testCase := range testCases {
 24819  		t.Run(testCase.name, func(t *testing.T) {
 24820  
 24821  			capabilities.SetForTests(capabilities.Capabilities{
 24822  				AllowPrivileged: testCase.allowPrivileged,
 24823  			})
 24824  
 24825  			errs := validateWindowsHostProcessPod(testCase.podSpec, field.NewPath("spec"))
 24826  			if testCase.expectError && len(errs) == 0 {
 24827  				t.Errorf("Unexpected success")
 24828  			}
 24829  			if !testCase.expectError && len(errs) != 0 {
 24830  				t.Errorf("Unexpected error(s): %v", errs)
 24831  			}
 24832  		})
 24833  	}
 24834  }
 24835  
 24836  func TestValidateOS(t *testing.T) {
 24837  	testCases := []struct {
 24838  		name        string
 24839  		expectError bool
 24840  		podSpec     *core.PodSpec
 24841  	}{{
 24842  		name:        "no OS field, featuregate",
 24843  		expectError: false,
 24844  		podSpec:     &core.PodSpec{OS: nil},
 24845  	}, {
 24846  		name:        "empty OS field, featuregate",
 24847  		expectError: true,
 24848  		podSpec:     &core.PodSpec{OS: &core.PodOS{}},
 24849  	}, {
 24850  		name:        "OS field, featuregate, valid OS",
 24851  		expectError: false,
 24852  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Linux}},
 24853  	}, {
 24854  		name:        "OS field, featuregate, valid OS",
 24855  		expectError: false,
 24856  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: core.Windows}},
 24857  	}, {
 24858  		name:        "OS field, featuregate, empty OS",
 24859  		expectError: true,
 24860  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: ""}},
 24861  	}, {
 24862  		name:        "OS field, featuregate, invalid OS",
 24863  		expectError: true,
 24864  		podSpec:     &core.PodSpec{OS: &core.PodOS{Name: "dummyOS"}},
 24865  	},
 24866  	}
 24867  	for _, testCase := range testCases {
 24868  		t.Run(testCase.name, func(t *testing.T) {
 24869  			errs := validateOS(testCase.podSpec, field.NewPath("spec"), PodValidationOptions{})
 24870  			if testCase.expectError && len(errs) == 0 {
 24871  				t.Errorf("Unexpected success")
 24872  			}
 24873  			if !testCase.expectError && len(errs) != 0 {
 24874  				t.Errorf("Unexpected error(s): %v", errs)
 24875  			}
 24876  		})
 24877  	}
 24878  }
 24879  
 24880  func TestValidateAppArmorProfileFormat(t *testing.T) {
 24881  	tests := []struct {
 24882  		profile     string
 24883  		expectValid bool
 24884  	}{
 24885  		{"", true},
 24886  		{v1.AppArmorBetaProfileRuntimeDefault, true},
 24887  		{v1.AppArmorBetaProfileNameUnconfined, true},
 24888  		{"baz", false}, // Missing local prefix.
 24889  		{v1.AppArmorBetaProfileNamePrefix + "/usr/sbin/ntpd", true},
 24890  		{v1.AppArmorBetaProfileNamePrefix + "foo-bar", true},
 24891  	}
 24892  
 24893  	for _, test := range tests {
 24894  		err := ValidateAppArmorProfileFormat(test.profile)
 24895  		if test.expectValid {
 24896  			assert.NoError(t, err, "Profile %s should be valid", test.profile)
 24897  		} else {
 24898  			assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
 24899  		}
 24900  	}
 24901  }
 24902  
 24903  func TestValidateDownwardAPIHostIPs(t *testing.T) {
 24904  	testCases := []struct {
 24905  		name           string
 24906  		expectError    bool
 24907  		featureEnabled bool
 24908  		fieldSel       *core.ObjectFieldSelector
 24909  	}{
 24910  		{
 24911  			name:           "has no hostIPs field, featuregate enabled",
 24912  			expectError:    false,
 24913  			featureEnabled: true,
 24914  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
 24915  		},
 24916  		{
 24917  			name:           "has hostIPs field, featuregate enabled",
 24918  			expectError:    false,
 24919  			featureEnabled: true,
 24920  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
 24921  		},
 24922  		{
 24923  			name:           "has no hostIPs field, featuregate disabled",
 24924  			expectError:    false,
 24925  			featureEnabled: false,
 24926  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
 24927  		},
 24928  		{
 24929  			name:           "has hostIPs field, featuregate disabled",
 24930  			expectError:    true,
 24931  			featureEnabled: false,
 24932  			fieldSel:       &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
 24933  		},
 24934  	}
 24935  	for _, testCase := range testCases {
 24936  		t.Run(testCase.name, func(t *testing.T) {
 24937  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)()
 24938  
 24939  			errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled})
 24940  			if testCase.expectError && len(errs) == 0 {
 24941  				t.Errorf("Unexpected success")
 24942  			}
 24943  			if !testCase.expectError && len(errs) != 0 {
 24944  				t.Errorf("Unexpected error(s): %v", errs)
 24945  			}
 24946  		})
 24947  	}
 24948  }
 24949  
 24950  func TestValidatePVSecretReference(t *testing.T) {
 24951  	rootFld := field.NewPath("name")
 24952  	type args struct {
 24953  		secretRef *core.SecretReference
 24954  		fldPath   *field.Path
 24955  	}
 24956  	tests := []struct {
 24957  		name          string
 24958  		args          args
 24959  		expectError   bool
 24960  		expectedError string
 24961  	}{{
 24962  		name:          "invalid secret ref name",
 24963  		args:          args{&core.SecretReference{Name: "$%^&*#", Namespace: "default"}, rootFld},
 24964  		expectError:   true,
 24965  		expectedError: "name.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
 24966  	}, {
 24967  		name:          "invalid secret ref namespace",
 24968  		args:          args{&core.SecretReference{Name: "valid", Namespace: "$%^&*#"}, rootFld},
 24969  		expectError:   true,
 24970  		expectedError: "name.namespace: Invalid value: \"$%^&*#\": " + dnsLabelErrMsg,
 24971  	}, {
 24972  		name:          "invalid secret: missing namespace",
 24973  		args:          args{&core.SecretReference{Name: "valid"}, rootFld},
 24974  		expectError:   true,
 24975  		expectedError: "name.namespace: Required value",
 24976  	}, {
 24977  		name:          "invalid secret : missing name",
 24978  		args:          args{&core.SecretReference{Namespace: "default"}, rootFld},
 24979  		expectError:   true,
 24980  		expectedError: "name.name: Required value",
 24981  	}, {
 24982  		name:          "valid secret",
 24983  		args:          args{&core.SecretReference{Name: "valid", Namespace: "default"}, rootFld},
 24984  		expectError:   false,
 24985  		expectedError: "",
 24986  	},
 24987  	}
 24988  	for _, tt := range tests {
 24989  		t.Run(tt.name, func(t *testing.T) {
 24990  			errs := validatePVSecretReference(tt.args.secretRef, tt.args.fldPath)
 24991  			if tt.expectError && len(errs) == 0 {
 24992  				t.Errorf("Unexpected success")
 24993  			}
 24994  			if tt.expectError && len(errs) != 0 {
 24995  				str := errs[0].Error()
 24996  				if str != "" && !strings.Contains(str, tt.expectedError) {
 24997  					t.Errorf("%s: expected error detail either empty or %q, got %q", tt.name, tt.expectedError, str)
 24998  				}
 24999  			}
 25000  			if !tt.expectError && len(errs) != 0 {
 25001  				t.Errorf("Unexpected error(s): %v", errs)
 25002  			}
 25003  		})
 25004  	}
 25005  }
 25006  
 25007  func TestValidateDynamicResourceAllocation(t *testing.T) {
 25008  	externalClaimName := "some-claim"
 25009  	externalClaimTemplateName := "some-claim-template"
 25010  	goodClaimSource := core.ClaimSource{
 25011  		ResourceClaimName: &externalClaimName,
 25012  	}
 25013  	shortPodName := &metav1.ObjectMeta{
 25014  		Name: "some-pod",
 25015  	}
 25016  	brokenPodName := &metav1.ObjectMeta{
 25017  		Name: ".dot.com",
 25018  	}
 25019  	goodClaimTemplate := core.PodSpec{
 25020  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-template"}}}}},
 25021  		RestartPolicy: core.RestartPolicyAlways,
 25022  		DNSPolicy:     core.DNSClusterFirst,
 25023  		ResourceClaims: []core.PodResourceClaim{{
 25024  			Name: "my-claim-template",
 25025  			Source: core.ClaimSource{
 25026  				ResourceClaimTemplateName: &externalClaimTemplateName,
 25027  			},
 25028  		}},
 25029  	}
 25030  	goodClaimReference := core.PodSpec{
 25031  		Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim-reference"}}}}},
 25032  		RestartPolicy: core.RestartPolicyAlways,
 25033  		DNSPolicy:     core.DNSClusterFirst,
 25034  		ResourceClaims: []core.PodResourceClaim{{
 25035  			Name: "my-claim-reference",
 25036  			Source: core.ClaimSource{
 25037  				ResourceClaimName: &externalClaimName,
 25038  			},
 25039  		}},
 25040  	}
 25041  
 25042  	successCases := map[string]core.PodSpec{
 25043  		"resource claim reference": goodClaimTemplate,
 25044  		"resource claim template":  goodClaimTemplate,
 25045  		"multiple claims": {
 25046  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}},
 25047  			RestartPolicy: core.RestartPolicyAlways,
 25048  			DNSPolicy:     core.DNSClusterFirst,
 25049  			ResourceClaims: []core.PodResourceClaim{{
 25050  				Name:   "my-claim",
 25051  				Source: goodClaimSource,
 25052  			}, {
 25053  				Name:   "another-claim",
 25054  				Source: goodClaimSource,
 25055  			}},
 25056  		},
 25057  		"init container": {
 25058  			Containers:     []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25059  			InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25060  			RestartPolicy:  core.RestartPolicyAlways,
 25061  			DNSPolicy:      core.DNSClusterFirst,
 25062  			ResourceClaims: []core.PodResourceClaim{{
 25063  				Name:   "my-claim",
 25064  				Source: goodClaimSource,
 25065  			}},
 25066  		},
 25067  	}
 25068  	for k, v := range successCases {
 25069  		t.Run(k, func(t *testing.T) {
 25070  			if errs := ValidatePodSpec(&v, shortPodName, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
 25071  				t.Errorf("expected success: %v", errs)
 25072  			}
 25073  		})
 25074  	}
 25075  
 25076  	failureCases := map[string]core.PodSpec{
 25077  		"pod claim name with prefix": {
 25078  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25079  			RestartPolicy: core.RestartPolicyAlways,
 25080  			DNSPolicy:     core.DNSClusterFirst,
 25081  			ResourceClaims: []core.PodResourceClaim{{
 25082  				Name:   "../my-claim",
 25083  				Source: goodClaimSource,
 25084  			}},
 25085  		},
 25086  		"pod claim name with path": {
 25087  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25088  			RestartPolicy: core.RestartPolicyAlways,
 25089  			DNSPolicy:     core.DNSClusterFirst,
 25090  			ResourceClaims: []core.PodResourceClaim{{
 25091  				Name:   "my/claim",
 25092  				Source: goodClaimSource,
 25093  			}},
 25094  		},
 25095  		"pod claim name empty": {
 25096  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25097  			RestartPolicy: core.RestartPolicyAlways,
 25098  			DNSPolicy:     core.DNSClusterFirst,
 25099  			ResourceClaims: []core.PodResourceClaim{{
 25100  				Name:   "",
 25101  				Source: goodClaimSource,
 25102  			}},
 25103  		},
 25104  		"duplicate pod claim entries": {
 25105  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
 25106  			RestartPolicy: core.RestartPolicyAlways,
 25107  			DNSPolicy:     core.DNSClusterFirst,
 25108  			ResourceClaims: []core.PodResourceClaim{{
 25109  				Name:   "my-claim",
 25110  				Source: goodClaimSource,
 25111  			}, {
 25112  				Name:   "my-claim",
 25113  				Source: goodClaimSource,
 25114  			}},
 25115  		},
 25116  		"resource claim source empty": {
 25117  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25118  			RestartPolicy: core.RestartPolicyAlways,
 25119  			DNSPolicy:     core.DNSClusterFirst,
 25120  			ResourceClaims: []core.PodResourceClaim{{
 25121  				Name:   "my-claim",
 25122  				Source: core.ClaimSource{},
 25123  			}},
 25124  		},
 25125  		"resource claim reference and template": {
 25126  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25127  			RestartPolicy: core.RestartPolicyAlways,
 25128  			DNSPolicy:     core.DNSClusterFirst,
 25129  			ResourceClaims: []core.PodResourceClaim{{
 25130  				Name: "my-claim",
 25131  				Source: core.ClaimSource{
 25132  					ResourceClaimName:         &externalClaimName,
 25133  					ResourceClaimTemplateName: &externalClaimTemplateName,
 25134  				},
 25135  			}},
 25136  		},
 25137  		"claim not found": {
 25138  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}},
 25139  			RestartPolicy: core.RestartPolicyAlways,
 25140  			DNSPolicy:     core.DNSClusterFirst,
 25141  			ResourceClaims: []core.PodResourceClaim{{
 25142  				Name:   "my-claim",
 25143  				Source: goodClaimSource,
 25144  			}},
 25145  		},
 25146  		"claim name empty": {
 25147  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}},
 25148  			RestartPolicy: core.RestartPolicyAlways,
 25149  			DNSPolicy:     core.DNSClusterFirst,
 25150  			ResourceClaims: []core.PodResourceClaim{{
 25151  				Name:   "my-claim",
 25152  				Source: goodClaimSource,
 25153  			}},
 25154  		},
 25155  		"pod claim name duplicates": {
 25156  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}},
 25157  			RestartPolicy: core.RestartPolicyAlways,
 25158  			DNSPolicy:     core.DNSClusterFirst,
 25159  			ResourceClaims: []core.PodResourceClaim{{
 25160  				Name:   "my-claim",
 25161  				Source: goodClaimSource,
 25162  			}},
 25163  		},
 25164  		"no claims defined": {
 25165  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25166  			RestartPolicy: core.RestartPolicyAlways,
 25167  			DNSPolicy:     core.DNSClusterFirst,
 25168  		},
 25169  		"duplicate pod claim name": {
 25170  			Containers:    []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25171  			RestartPolicy: core.RestartPolicyAlways,
 25172  			DNSPolicy:     core.DNSClusterFirst,
 25173  			ResourceClaims: []core.PodResourceClaim{{
 25174  				Name:   "my-claim",
 25175  				Source: goodClaimSource,
 25176  			}, {
 25177  				Name:   "my-claim",
 25178  				Source: goodClaimSource,
 25179  			}},
 25180  		},
 25181  		"ephemeral container don't support resource requirements": {
 25182  			Containers:          []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
 25183  			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"}},
 25184  			RestartPolicy:       core.RestartPolicyAlways,
 25185  			DNSPolicy:           core.DNSClusterFirst,
 25186  			ResourceClaims: []core.PodResourceClaim{{
 25187  				Name:   "my-claim",
 25188  				Source: goodClaimSource,
 25189  			}},
 25190  		},
 25191  		"invalid claim template name": func() core.PodSpec {
 25192  			spec := goodClaimTemplate.DeepCopy()
 25193  			notLabel := ".foo_bar"
 25194  			spec.ResourceClaims[0].Source.ResourceClaimTemplateName = &notLabel
 25195  			return *spec
 25196  		}(),
 25197  		"invalid claim reference name": func() core.PodSpec {
 25198  			spec := goodClaimReference.DeepCopy()
 25199  			notLabel := ".foo_bar"
 25200  			spec.ResourceClaims[0].Source.ResourceClaimName = &notLabel
 25201  			return *spec
 25202  		}(),
 25203  	}
 25204  	for k, v := range failureCases {
 25205  		if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
 25206  			t.Errorf("expected failure for %q", k)
 25207  		}
 25208  	}
 25209  
 25210  	t.Run("generated-claim-name", func(t *testing.T) {
 25211  		for _, spec := range []*core.PodSpec{&goodClaimTemplate, &goodClaimReference} {
 25212  			claimName := spec.ResourceClaims[0].Name
 25213  			t.Run(claimName, func(t *testing.T) {
 25214  				for _, podMeta := range []*metav1.ObjectMeta{shortPodName, brokenPodName} {
 25215  					t.Run(podMeta.Name, func(t *testing.T) {
 25216  						errs := ValidatePodSpec(spec, podMeta, field.NewPath("field"), PodValidationOptions{})
 25217  						// Only one out of the four combinations fails.
 25218  						expectError := spec == &goodClaimTemplate && podMeta == brokenPodName
 25219  						if expectError && len(errs) == 0 {
 25220  							t.Error("did not get the expected failure")
 25221  						}
 25222  						if !expectError && len(errs) > 0 {
 25223  							t.Errorf("unexpected failures: %+v", errs)
 25224  						}
 25225  					})
 25226  				}
 25227  			})
 25228  		}
 25229  	})
 25230  }
 25231  
 25232  func TestValidateLoadBalancerStatus(t *testing.T) {
 25233  	ipModeVIP := core.LoadBalancerIPModeVIP
 25234  	ipModeProxy := core.LoadBalancerIPModeProxy
 25235  	ipModeDummy := core.LoadBalancerIPMode("dummy")
 25236  
 25237  	testCases := []struct {
 25238  		name          string
 25239  		ipModeEnabled bool
 25240  		nonLBAllowed  bool
 25241  		tweakLBStatus func(s *core.LoadBalancerStatus)
 25242  		tweakSvcSpec  func(s *core.ServiceSpec)
 25243  		numErrs       int
 25244  	}{
 25245  		{
 25246  			name:         "type is not LB",
 25247  			nonLBAllowed: false,
 25248  			tweakSvcSpec: func(s *core.ServiceSpec) {
 25249  				s.Type = core.ServiceTypeClusterIP
 25250  			},
 25251  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25252  				s.Ingress = []core.LoadBalancerIngress{{
 25253  					IP: "1.2.3.4",
 25254  				}}
 25255  			},
 25256  			numErrs: 1,
 25257  		}, {
 25258  			name:         "type is not LB. back-compat",
 25259  			nonLBAllowed: true,
 25260  			tweakSvcSpec: func(s *core.ServiceSpec) {
 25261  				s.Type = core.ServiceTypeClusterIP
 25262  			},
 25263  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25264  				s.Ingress = []core.LoadBalancerIngress{{
 25265  					IP: "1.2.3.4",
 25266  				}}
 25267  			},
 25268  			numErrs: 0,
 25269  		}, {
 25270  			name:          "valid vip ipMode",
 25271  			ipModeEnabled: true,
 25272  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25273  				s.Ingress = []core.LoadBalancerIngress{{
 25274  					IP:     "1.2.3.4",
 25275  					IPMode: &ipModeVIP,
 25276  				}}
 25277  			},
 25278  			numErrs: 0,
 25279  		}, {
 25280  			name:          "valid proxy ipMode",
 25281  			ipModeEnabled: true,
 25282  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25283  				s.Ingress = []core.LoadBalancerIngress{{
 25284  					IP:     "1.2.3.4",
 25285  					IPMode: &ipModeProxy,
 25286  				}}
 25287  			},
 25288  			numErrs: 0,
 25289  		}, {
 25290  			name:          "invalid ipMode",
 25291  			ipModeEnabled: true,
 25292  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25293  				s.Ingress = []core.LoadBalancerIngress{{
 25294  					IP:     "1.2.3.4",
 25295  					IPMode: &ipModeDummy,
 25296  				}}
 25297  			},
 25298  			numErrs: 1,
 25299  		}, {
 25300  			name:          "missing ipMode with LoadbalancerIPMode enabled",
 25301  			ipModeEnabled: true,
 25302  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25303  				s.Ingress = []core.LoadBalancerIngress{{
 25304  					IP: "1.2.3.4",
 25305  				}}
 25306  			},
 25307  			numErrs: 1,
 25308  		}, {
 25309  			name:          "missing ipMode with LoadbalancerIPMode disabled",
 25310  			ipModeEnabled: false,
 25311  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25312  				s.Ingress = []core.LoadBalancerIngress{{
 25313  					IP: "1.2.3.4",
 25314  				}}
 25315  			},
 25316  			numErrs: 0,
 25317  		}, {
 25318  			name:          "missing ip with ipMode present",
 25319  			ipModeEnabled: true,
 25320  			tweakLBStatus: func(s *core.LoadBalancerStatus) {
 25321  				s.Ingress = []core.LoadBalancerIngress{{
 25322  					IPMode: &ipModeProxy,
 25323  				}}
 25324  			},
 25325  			numErrs: 1,
 25326  		},
 25327  	}
 25328  	for _, tc := range testCases {
 25329  		t.Run(tc.name, func(t *testing.T) {
 25330  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)()
 25331  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowServiceLBStatusOnNonLB, tc.nonLBAllowed)()
 25332  			status := core.LoadBalancerStatus{}
 25333  			tc.tweakLBStatus(&status)
 25334  			spec := core.ServiceSpec{Type: core.ServiceTypeLoadBalancer}
 25335  			if tc.tweakSvcSpec != nil {
 25336  				tc.tweakSvcSpec(&spec)
 25337  			}
 25338  			errs := ValidateLoadBalancerStatus(&status, field.NewPath("status"), &spec)
 25339  			if len(errs) != tc.numErrs {
 25340  				t.Errorf("Unexpected error list for case %q(expected:%v got %v) - Errors:\n %v", tc.name, tc.numErrs, len(errs), errs.ToAggregate())
 25341  			}
 25342  		})
 25343  	}
 25344  }
 25345  
 25346  func TestValidateSleepAction(t *testing.T) {
 25347  	fldPath := field.NewPath("root")
 25348  	getInvalidStr := func(gracePeriod int64) string {
 25349  		return fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
 25350  	}
 25351  
 25352  	testCases := []struct {
 25353  		name        string
 25354  		action      *core.SleepAction
 25355  		gracePeriod int64
 25356  		expectErr   field.ErrorList
 25357  	}{
 25358  		{
 25359  			name: "valid setting",
 25360  			action: &core.SleepAction{
 25361  				Seconds: 5,
 25362  			},
 25363  			gracePeriod: 30,
 25364  		},
 25365  		{
 25366  			name: "negative seconds",
 25367  			action: &core.SleepAction{
 25368  				Seconds: -1,
 25369  			},
 25370  			gracePeriod: 30,
 25371  			expectErr:   field.ErrorList{field.Invalid(fldPath, -1, getInvalidStr(30))},
 25372  		},
 25373  		{
 25374  			name: "longer than gracePeriod",
 25375  			action: &core.SleepAction{
 25376  				Seconds: 5,
 25377  			},
 25378  			gracePeriod: 3,
 25379  			expectErr:   field.ErrorList{field.Invalid(fldPath, 5, getInvalidStr(3))},
 25380  		},
 25381  	}
 25382  
 25383  	for _, tc := range testCases {
 25384  		t.Run(tc.name, func(t *testing.T) {
 25385  			errs := validateSleepAction(tc.action, tc.gracePeriod, fldPath)
 25386  
 25387  			if len(tc.expectErr) > 0 && len(errs) == 0 {
 25388  				t.Errorf("Unexpected success")
 25389  			} else if len(tc.expectErr) == 0 && len(errs) != 0 {
 25390  				t.Errorf("Unexpected error(s): %v", errs)
 25391  			} else if len(tc.expectErr) > 0 {
 25392  				if tc.expectErr[0].Error() != errs[0].Error() {
 25393  					t.Errorf("Unexpected error(s): %v", errs)
 25394  				}
 25395  			}
 25396  		})
 25397  	}
 25398  }