Skip to content

Commit

Permalink
GenericEphemeralVolume: feature gate, API, documentation
Browse files Browse the repository at this point in the history
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".
  • Loading branch information
pohly committed Jul 9, 2020
1 parent 896da22 commit c05c8e9
Show file tree
Hide file tree
Showing 15 changed files with 743 additions and 151 deletions.
11 changes: 11 additions & 0 deletions pkg/api/pod/util.go
Expand Up @@ -431,6 +431,7 @@ func dropDisabledFields(
dropDisabledProcMountField(podSpec, oldPodSpec)

dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec)
dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec)

if !utilfeature.DefaultFeatureGate.Enabled(features.NonPreemptingPriority) &&
!podPriorityInUse(oldPodSpec) {
Expand Down Expand Up @@ -499,6 +500,16 @@ func dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
}
}

// dropDisabledEphemeralVolumeSourceAlphaFields removes disabled alpha fields from []EphemeralVolumeSource.
// This should be called from PrepareForCreate/PrepareForUpdate for all pod specs resources containing a EphemeralVolumeSource
func dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) && !csiInUse(oldPodSpec) {
for i := range podSpec.Volumes {
podSpec.Volumes[i].Ephemeral = nil
}
}
}

func ephemeralContainersInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/testing/backward_compatibility_test.go
Expand Up @@ -159,7 +159,7 @@ func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
}

validator := func(obj runtime.Object) field.ErrorList {
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), field.NewPath("spec"))
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), &(obj.(*api.Pod).ObjectMeta), field.NewPath("spec"))
}

for _, tc := range cases {
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/core/fuzzer/fuzzer.go
Expand Up @@ -263,6 +263,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
i.ISCSIInterface = "default"
}
},
func(i *core.PersistentVolumeClaimSpec, c fuzz.Continue) {
// Match defaulting in pkg/apis/core/v1/defaults.go.
volumeMode := core.PersistentVolumeMode(c.RandString())
if volumeMode == "" {
volumeMode = core.PersistentVolumeFilesystem
}
i.VolumeMode = &volumeMode
},
func(d *core.DNSPolicy, c fuzz.Continue) {
policies := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault}
*d = policies[c.Rand.Intn(len(policies))]
Expand Down
74 changes: 74 additions & 0 deletions pkg/apis/core/types.go
Expand Up @@ -157,6 +157,33 @@ type VolumeSource struct {
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).
// +optional
CSI *CSIVolumeSource
// Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature).
// The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,
// and deleted when the pod is removed.
//
// Use this if:
// a) the volume is only needed while the pod runs,
// b) features of normal volumes like restoring from snapshot or capacity
// tracking are needed,
// c) the storage driver is specified through a storage class, and
// d) the storage driver supports dynamic volume provisioning through
// a PersistentVolumeClaim (see EphemeralVolumeSource for more
// information on the connection between this volume type
// and PersistentVolumeClaim).
//
// Use PersistentVolumeClaim or one of the vendor-specific
// APIs for volumes that persist for longer than the lifecycle
// of an individual pod.
//
// Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to
// be used that way - see the documentation of the driver for
// more information.
//
// A pod can use both types of ephemeral volumes and
// persistent volumes at the same time.
//
// +optional
Ephemeral *EphemeralVolumeSource
}

// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs.
Expand Down Expand Up @@ -1670,6 +1697,53 @@ type CSIVolumeSource struct {
NodePublishSecretRef *LocalObjectReference
}

// EphemeralVolumeSource represents an ephemeral volume that is handled by a normal storage driver.
type EphemeralVolumeSource struct {
// VolumeClaimTemplate will be used to create a stand-alone PVC to provision the volume.
// The pod in which this EphemeralVolumeSource is embedded will be the
// owner of the PVC, i.e. the PVC will be deleted together with the
// pod. The name of the PVC will be `<pod name>-<volume name>` where
// `<volume name>` is the name from the `PodSpec.Volumes` array
// entry. Pod validation will reject the pod if the concatenated name
// is not valid for a PVC (for example, too long).
//
// An existing PVC with that name that is not owned by the pod
// will *not* be used for the pod to avoid using an unrelated
// volume by mistake. Starting the pod is then blocked until
// the unrelated PVC is removed. If such a pre-created PVC is
// meant to be used by the pod, the PVC has to updated with an
// owner reference to the pod once the pod exists. Normally
// this should not be necessary, but it may be useful when
// manually reconstructing a broken cluster.
//
// This field is read-only and no changes will be made by Kubernetes
// to the PVC after it has been created.
//
// Required, must not be nil.
VolumeClaimTemplate *PersistentVolumeClaimTemplate

// ReadOnly specifies a read-only configuration for the volume.
// Defaults to false (read/write).
// +optional
ReadOnly bool
}

// PersistentVolumeClaimTemplate is used to produce
// PersistentVolumeClaim objects as part of an EphemeralVolumeSource.
type PersistentVolumeClaimTemplate struct {
// ObjectMeta may contain labels and annotations that will be copied into the PVC
// when creating it. No other fields are allowed and will be rejected during
// validation.
// +optional
metav1.ObjectMeta

// Spec for the PersistentVolumeClaim. The entire content is
// copied unchanged into the PVC that gets created from this
// template. The same fields as in a PersistentVolumeClaim
// are also valid here.
Spec PersistentVolumeClaimSpec
}

// ContainerPort represents a network port in a single container
type ContainerPort struct {
// Optional: If specified, this must be an IANA_SVC_NAME Each named port
Expand Down
8 changes: 5 additions & 3 deletions pkg/apis/core/v1/defaults.go
Expand Up @@ -285,9 +285,11 @@ func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) {
if obj.Status.Phase == "" {
obj.Status.Phase = v1.ClaimPending
}
if obj.Spec.VolumeMode == nil {
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode)
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem
}
func SetDefaults_PersistentVolumeClaimSpec(obj *v1.PersistentVolumeClaimSpec) {
if obj.VolumeMode == nil {
obj.VolumeMode = new(v1.PersistentVolumeMode)
*obj.VolumeMode = v1.PersistentVolumeFilesystem
}
}
func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) {
Expand Down
54 changes: 54 additions & 0 deletions pkg/apis/core/v1/defaults_test.go
Expand Up @@ -142,6 +142,7 @@ func TestWorkloadDefaults(t *testing.T) {
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
Expand Down Expand Up @@ -265,6 +266,7 @@ func TestPodDefaults(t *testing.T) {
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
Expand Down Expand Up @@ -1375,6 +1377,58 @@ func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
}
}

func TestSetDefaultEphemeral(t *testing.T) {
fsMode := v1.PersistentVolumeFilesystem
blockMode := v1.PersistentVolumeBlock

tests := []struct {
name string
volumeMode *v1.PersistentVolumeMode
expectedVolumeMode v1.PersistentVolumeMode
}{
{
name: "volume mode nil",
volumeMode: nil,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode filesystem",
volumeMode: &fsMode,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode block",
volumeMode: &blockMode,
expectedVolumeMode: v1.PersistentVolumeBlock,
},
}

for _, test := range tests {
pod := &v1.Pod{
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
VolumeSource: v1.VolumeSource{
Ephemeral: &v1.EphemeralVolumeSource{
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
Spec: v1.PersistentVolumeClaimSpec{
VolumeMode: test.volumeMode,
},
},
},
},
},
},
},
}
obj1 := roundTrip(t, runtime.Object(pod))
pod1 := obj1.(*v1.Pod)
if *pod1.Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode != test.expectedVolumeMode {
t.Errorf("Test %s failed, Expected VolumeMode: %v, but got %v", test.name, test.volumeMode, *pod1.Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode)
}
}
}

func TestSetDefaultEndpointsProtocol(t *testing.T) {
in := &v1.Endpoints{Subsets: []v1.EndpointSubset{
{Ports: []v1.EndpointPort{{}, {Protocol: "UDP"}, {}}},
Expand Down

0 comments on commit c05c8e9

Please sign in to comment.