GenericEphemeralVolume: feature gate, API, documentation

As explained in
https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/1698-generic-ephemeral-volumes,
CSI inline volumes are not suitable for more "normal" kinds of storage
systems. For those a new approach is needed: "generic ephemeral inline
volumes".
This commit is contained in:
Patrick Ohly
2020-05-26 21:32:36 +02:00
parent 896da2253c
commit c05c8e915b
15 changed files with 743 additions and 151 deletions

View File

@@ -932,42 +932,195 @@ func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn
}
}
func TestValidatePersistentVolumeClaim(t *testing.T) {
func testValidatePVC(t *testing.T, ephemeral bool) {
invalidClassName := "-invalid-"
validClassName := "valid"
invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
validMode := core.PersistentVolumeFilesystem
goodName := "foo"
goodNS := "ns"
if ephemeral {
// Must be empty for ephemeral inline volumes.
goodName = ""
goodNS = ""
}
goodClaimSpec := core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key2",
Operator: "Exists",
},
},
},
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
StorageClassName: &validClassName,
VolumeMode: &validMode,
}
now := metav1.Now()
ten := int64(10)
scenarios := map[string]struct {
isExpectedFailure bool
claim *core.PersistentVolumeClaim
}{
"good-claim": {
isExpectedFailure: false,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key2",
Operator: "Exists",
},
claim: testVolumeClaim(goodName, goodNS, goodClaimSpec),
},
"missing-name": {
isExpectedFailure: !ephemeral,
claim: testVolumeClaim("", goodNS, goodClaimSpec),
},
"missing-namespace": {
isExpectedFailure: !ephemeral,
claim: testVolumeClaim(goodName, "", goodClaimSpec),
},
"with-generate-name": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.GenerateName = "pvc-"
return claim
}(),
},
"with-uid": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
return claim
}(),
},
"with-resource-version": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ResourceVersion = "1"
return claim
}(),
},
"with-generation": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Generation = 100
return claim
}(),
},
"with-creation-timestamp": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.CreationTimestamp = now
return claim
}(),
},
"with-deletion-grace-period-seconds": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.DeletionGracePeriodSeconds = &ten
return claim
}(),
},
"with-owner-references": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "pod",
Name: "foo",
UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
},
},
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
}
return claim
}(),
},
"with-finalizers": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Finalizers = []string{
"example.com/foo",
}
return claim
}(),
},
"with-cluster-name": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ClusterName = "foo"
return claim
}(),
},
"with-managed-fields": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ManagedFields = []metav1.ManagedFieldsEntry{
{
FieldsType: "FieldsV1",
Operation: "Apply",
APIVersion: "apps/v1",
Manager: "foo",
},
},
StorageClassName: &validClassName,
VolumeMode: &validMode,
}),
}
return claim
}(),
},
"with-good-labels": {
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"apps.kubernetes.io/name": "test",
}
return claim
}(),
},
"with-bad-labels": {
isExpectedFailure: true,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"hello-world": "hyphen not allowed",
}
return claim
}(),
},
"with-good-annotations": {
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"foo": "bar",
}
return claim
}(),
},
"with-bad-annotations": {
isExpectedFailure: true,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"hello-world": "hyphen not allowed",
}
return claim
}(),
},
"invalid-claim-zero-capacity": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
@@ -990,7 +1143,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"invalid-label-selector": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
@@ -1013,7 +1166,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"invalid-accessmode": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
@@ -1022,23 +1175,9 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
}),
},
"missing-namespace": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}),
},
"no-access-modes": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
@@ -1048,7 +1187,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"no-resource-requests": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
@@ -1056,7 +1195,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"invalid-resource-requests": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
@@ -1069,7 +1208,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"negative-storage-request": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
@@ -1091,7 +1230,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"zero-storage-request": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
@@ -1113,7 +1252,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"invalid-storage-class-name": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
@@ -1136,7 +1275,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
"invalid-volume-mode": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
@@ -1153,17 +1292,43 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
errs := ValidatePersistentVolumeClaim(scenario.claim)
var errs field.ErrorList
if ephemeral {
volumes := []core.Volume{
{
Name: "foo",
VolumeSource: core.VolumeSource{
Ephemeral: &core.EphemeralVolumeSource{
VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
ObjectMeta: scenario.claim.ObjectMeta,
Spec: scenario.claim.Spec,
},
},
},
},
}
_, errs = ValidateVolumes(volumes, nil, field.NewPath(""))
} else {
errs = ValidatePersistentVolumeClaim(scenario.claim)
}
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name)
t.Error("Unexpected success for scenario")
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
t.Errorf("Unexpected failure: %+v", errs)
}
})
}
}
func TestValidatePersistentVolumeClaim(t *testing.T) {
testValidatePVC(t, false)
}
func TestValidateEphemeralVolume(t *testing.T) {
testValidatePVC(t, true)
}
func TestAlphaPVVolumeModeUpdate(t *testing.T) {
block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem
@@ -3825,7 +3990,7 @@ func TestValidateVolumes(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
names, errs := ValidateVolumes([]core.Volume{tc.vol}, field.NewPath("field"))
names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"))
if len(errs) != len(tc.errs) {
t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
}
@@ -3851,7 +4016,7 @@ func TestValidateVolumes(t *testing.T) {
{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
}
_, errs := ValidateVolumes(dupsCase, field.NewPath("field"))
_, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"))
if len(errs) == 0 {
t.Errorf("expected error")
} else if len(errs) != 1 {
@@ -3864,7 +4029,7 @@ func TestValidateVolumes(t *testing.T) {
hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
// Enable HugePages
if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working"); len(errs) != 0 {
if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil); len(errs) != 0 {
t.Errorf("Unexpected error when HugePages feature is enabled.")
}
@@ -4194,7 +4359,7 @@ func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
}
for _, tc := range testCases {
if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) != 0 {
if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
@@ -4937,7 +5102,7 @@ func TestValidateVolumeMounts(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err)
return
@@ -5000,7 +5165,7 @@ func TestValidateDisabledSubpath(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err)
return
@@ -5062,7 +5227,7 @@ func TestValidateSubpathMutuallyExclusive(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err)
return
@@ -5143,7 +5308,7 @@ func TestValidateDisabledSubpathExpr(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err)
return
@@ -5337,7 +5502,7 @@ func TestValidateMountPropagation(t *testing.T) {
volumes := []core.Volume{
{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols2, v2err := ValidateVolumes(volumes, field.NewPath("field"))
vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v2err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v2err)
return
@@ -5360,7 +5525,7 @@ func TestAlphaValidateVolumeDevices(t *testing.T) {
{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volumes - expected success %v", v1err)
return
@@ -6560,14 +6725,14 @@ func TestValidatePodSpec(t *testing.T) {
badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
successCases := []core.PodSpec{
{ // Populate basic fields, leave defaults for most.
successCases := map[string]core.PodSpec{
"populate basic fields, leave defaults for most": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate all fields.
"populate all fields": {
Volumes: []core.Volume{
{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
},
@@ -6582,7 +6747,7 @@ func TestValidatePodSpec(t *testing.T) {
ActiveDeadlineSeconds: &activeDeadlineSeconds,
ServiceAccountName: "acct",
},
{ // Populate all fields with larger active deadline.
"populate all fields with larger active deadline": {
Volumes: []core.Volume{
{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
},
@@ -6597,7 +6762,7 @@ func TestValidatePodSpec(t *testing.T) {
ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
ServiceAccountName: "acct",
},
{ // Populate HostNetwork.
"populate HostNetwork": {
Containers: []core.Container{
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
Ports: []core.ContainerPort{
@@ -6610,7 +6775,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate RunAsUser SupplementalGroups FSGroup with minID 0
"populate RunAsUser SupplementalGroups FSGroup with minID 0": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{
SupplementalGroups: []int64{minGroupID},
@@ -6620,7 +6785,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647
"populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{
SupplementalGroups: []int64{maxGroupID},
@@ -6630,7 +6795,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate HostIPC.
"populate HostIPC": {
SecurityContext: &core.PodSecurityContext{
HostIPC: true,
},
@@ -6639,7 +6804,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate HostPID.
"populate HostPID": {
SecurityContext: &core.PodSecurityContext{
HostPID: true,
},
@@ -6648,27 +6813,27 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate Affinity.
"populate Affinity": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate HostAliases.
"populate HostAliases": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate HostAliases with `foo.bar` hostnames.
"populate HostAliases with `foo.bar` hostnames": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate HostAliases with HostNetwork.
"populate HostAliases with HostNetwork": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{
@@ -6677,14 +6842,14 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
{ // Populate PriorityClassName.
"populate PriorityClassName": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
PriorityClassName: "valid-name",
},
{ // Populate ShareProcessNamespace
"populate ShareProcessNamespace": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
@@ -6693,20 +6858,20 @@ func TestValidatePodSpec(t *testing.T) {
ShareProcessNamespace: &[]bool{true}[0],
},
},
{ // Populate RuntimeClassName
"populate RuntimeClassName": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
},
{ // Populate Overhead
"populate Overhead": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
Overhead: core.ResourceList{},
},
{
"populate DNSPolicy": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{
FSGroupChangePolicy: &goodfsGroupChangePolicy,
@@ -6715,10 +6880,12 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
}
for i := range successCases {
if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
for k, v := range successCases {
t.Run(k, func(t *testing.T) {
if errs := ValidatePodSpec(&v, nil, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
})
}
activeDeadlineSeconds = int64(0)
@@ -6919,7 +7086,7 @@ func TestValidatePodSpec(t *testing.T) {
},
}
for k, v := range failureCases {
if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 {
if errs := ValidatePodSpec(&v, nil, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %q", k)
}
}
@@ -6946,9 +7113,24 @@ func TestValidatePod(t *testing.T) {
}
return spec
}
validPVCSpec := core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}
validPVCTemplate := core.PersistentVolumeClaimTemplate{
Spec: validPVCSpec,
}
longPodName := strings.Repeat("a", 200)
longVolName := strings.Repeat("b", 60)
successCases := []core.Pod{
{ // Basic fields.
successCases := map[string]core.Pod{
"basic fields": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
@@ -6957,7 +7139,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
},
{ // Just about everything.
"just about everything": {
ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
@@ -6972,7 +7154,7 @@ func TestValidatePod(t *testing.T) {
NodeName: "foobar",
},
},
{ // Serialized node affinity requirements.
"serialized node affinity requirements": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7032,7 +7214,7 @@ func TestValidatePod(t *testing.T) {
},
),
},
{ // Serialized node affinity requirements.
"serialized node affinity requirements, II": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7073,7 +7255,7 @@ func TestValidatePod(t *testing.T) {
},
),
},
{ // Serialized pod affinity in affinity requirements in annotations.
"serialized pod affinity in affinity requirements in annotations": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7129,7 +7311,7 @@ func TestValidatePod(t *testing.T) {
},
}),
},
{ // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations.
"serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7183,63 +7365,63 @@ func TestValidatePod(t *testing.T) {
},
}),
},
{ // populate forgiveness tolerations with exists operator in annotations.
"populate forgiveness tolerations with exists operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
},
{ // populate forgiveness tolerations with equal operator in annotations.
"populate forgiveness tolerations with equal operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
},
{ // populate tolerations equal operator in annotations.
"populate tolerations equal operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
},
{ // populate tolerations exists operator in annotations.
"populate tolerations exists operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: validPodSpec(nil),
},
{ // empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.
"empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
},
{ // empty operator is OK for toleration, defaults to Equal.
"empty operator is OK for toleration, defaults to Equal.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
},
{ // empty effect is OK for toleration, empty toleration effect means match all taint effects.
"empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
},
{ // negative tolerationSeconds is OK for toleration.
"negative tolerationSeconds is OK for toleration.": {
ObjectMeta: metav1.ObjectMeta{
Name: "pod-forgiveness-invalid",
Namespace: "ns",
},
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
},
{ // runtime default seccomp profile
"runtime default seccomp profile": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7249,7 +7431,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // docker default seccomp profile
"docker default seccomp profile": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7259,7 +7441,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // unconfined seccomp profile
"unconfined seccomp profile": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7269,7 +7451,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // localhost seccomp profile
"localhost seccomp profile": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7279,7 +7461,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // localhost seccomp profile for a container
"localhost seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7289,7 +7471,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // runtime default seccomp profile for a pod
"runtime default seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7305,7 +7487,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
{ // runtime default seccomp profile for a container
"runtime default seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7322,7 +7504,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault,
},
},
{ // unconfined seccomp profile for a pod
"unconfined seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7338,7 +7520,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
{ // unconfined seccomp profile for a container
"unconfined seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7355,7 +7537,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault,
},
},
{ // localhost seccomp profile for a pod
"localhost seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7372,7 +7554,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
{ // localhost seccomp profile for a container
"localhost seccomp profile for a container, II": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7390,7 +7572,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault,
},
},
{ // default AppArmor profile for a container
"default AppArmor profile for a container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7400,7 +7582,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // default AppArmor profile for an init container
"default AppArmor profile for an init container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7415,7 +7597,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
},
{ // localhost AppArmor profile for a container
"localhost AppArmor profile for a container": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7425,7 +7607,7 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec(nil),
},
{ // syntactically valid sysctls
"syntactically valid sysctls": {
ObjectMeta: metav1.ObjectMeta{
Name: "123",
Namespace: "ns",
@@ -7452,7 +7634,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
{ // valid extended resources for init container
"valid extended resources for init container": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{
InitContainers: []core.Container{
@@ -7476,7 +7658,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
},
{ // valid extended resources for regular container
"valid extended resources for regular container": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{
InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
@@ -7500,7 +7682,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst,
},
},
{ // valid serviceaccount token projected volume with serviceaccount name specified
"valid serviceaccount token projected volume with serviceaccount name specified": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{
ServiceAccountName: "some-service-account",
@@ -7527,11 +7709,25 @@ func TestValidatePod(t *testing.T) {
},
},
},
"ephemeral volume + PVC, no conflict between them": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
}
for _, pod := range successCases {
if errs := ValidatePodCreate(&pod, PodValidationOptions{}); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
for k, v := range successCases {
t.Run(k, func(t *testing.T) {
if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
})
}
errorCases := map[string]struct {
@@ -8421,15 +8617,47 @@ func TestValidatePod(t *testing.T) {
},
},
},
"final PVC name for ephemeral volume must be valid": {
expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
{Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
},
"PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
{Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
},
}
for k, v := range errorCases {
if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
t.Errorf("expected failure for %q", k)
} else if v.expectedError == "" {
t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error())
} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
t.Errorf("expected error for %q to contain %q, got %q", k, v.expectedError, actualError)
}
t.Run(k, func(t *testing.T) {
if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
t.Errorf("expected failure")
} else if v.expectedError == "" {
t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
}
})
}
}