diff --git a/pkg/volume/aws_ebs/BUILD b/pkg/volume/aws_ebs/BUILD index 5354b9e31b1..506240f6764 100644 --- a/pkg/volume/aws_ebs/BUILD +++ b/pkg/volume/aws_ebs/BUILD @@ -19,6 +19,7 @@ go_library( deps = [ "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/aws:go_default_library", + "//pkg/features:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/volume:go_default_library", @@ -29,6 +30,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/volume/aws_ebs/aws_ebs.go b/pkg/volume/aws_ebs/aws_ebs.go index 71531245371..2dedb9aa4af 100644 --- a/pkg/volume/aws_ebs/aws_ebs.go +++ b/pkg/volume/aws_ebs/aws_ebs.go @@ -28,7 +28,9 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" kstrings "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" @@ -507,5 +509,9 @@ func (c *awsElasticBlockStoreProvisioner) Provision() (*v1.PersistentVolume, err } } + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pv.Spec.VolumeMode = c.options.PVC.Spec.VolumeMode + } + return pv, nil } diff --git a/pkg/volume/azure_dd/BUILD b/pkg/volume/azure_dd/BUILD index c4913bfba6e..cb1ccc3bf09 100644 --- a/pkg/volume/azure_dd/BUILD +++ b/pkg/volume/azure_dd/BUILD @@ -56,6 +56,7 @@ go_library( "//pkg/apis/core:go_default_library", "//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider/providers/azure:go_default_library", + "//pkg/features:go_default_library", "//pkg/util/keymutex:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/strings:go_default_library", @@ -71,6 +72,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/volume/azure_dd/azure_provision.go b/pkg/volume/azure_dd/azure_provision.go index bf7eae37db4..2beac4b11eb 100644 --- a/pkg/volume/azure_dd/azure_provision.go +++ b/pkg/volume/azure_dd/azure_provision.go @@ -23,6 +23,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" ) @@ -187,5 +189,10 @@ func (p *azureDiskProvisioner) Provision() (*v1.PersistentVolume, error) { MountOptions: p.options.MountOptions, }, } + + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pv.Spec.VolumeMode = p.options.PVC.Spec.VolumeMode + } + return pv, nil } diff --git a/pkg/volume/azure_file/azure_provision.go b/pkg/volume/azure_file/azure_provision.go index dc155334ff9..ca344c7061e 100644 --- a/pkg/volume/azure_file/azure_provision.go +++ b/pkg/volume/azure_file/azure_provision.go @@ -135,6 +135,9 @@ func (a *azureFileProvisioner) Provision() (*v1.PersistentVolume, error) { if !util.AccessModesContainedInAll(a.plugin.GetAccessModes(), a.options.PVC.Spec.AccessModes) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", a.options.PVC.Spec.AccessModes, a.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(a.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", a.plugin.GetPluginName()) + } var sku, location, account string diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go index 121972c4613..5216c8dee91 100644 --- a/pkg/volume/cinder/cinder.go +++ b/pkg/volume/cinder/cinder.go @@ -508,6 +508,10 @@ func (c *cinderVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) + } + volumeID, sizeGB, labels, fstype, err := c.manager.CreateVolume(c) if err != nil { return nil, err diff --git a/pkg/volume/flocker/flocker_volume.go b/pkg/volume/flocker/flocker_volume.go index d7f245d15a2..ef81dd7cc26 100644 --- a/pkg/volume/flocker/flocker_volume.go +++ b/pkg/volume/flocker/flocker_volume.go @@ -67,6 +67,10 @@ func (c *flockerVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { return nil, fmt.Errorf("Provisioning failed: Specified unsupported selector") } + if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) + } + datasetUUID, sizeGB, labels, err := c.manager.CreateVolume(c) if err != nil { return nil, err diff --git a/pkg/volume/gce_pd/gce_pd.go b/pkg/volume/gce_pd/gce_pd.go index 8c78c6754af..9c2be1c8133 100644 --- a/pkg/volume/gce_pd/gce_pd.go +++ b/pkg/volume/gce_pd/gce_pd.go @@ -27,6 +27,8 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" kstrings "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" @@ -448,5 +450,9 @@ func (c *gcePersistentDiskProvisioner) Provision() (*v1.PersistentVolume, error) } } + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pv.Spec.VolumeMode = c.options.PVC.Spec.VolumeMode + } + return pv, nil } diff --git a/pkg/volume/glusterfs/glusterfs.go b/pkg/volume/glusterfs/glusterfs.go index 28a7104aa1d..573d93d359c 100644 --- a/pkg/volume/glusterfs/glusterfs.go +++ b/pkg/volume/glusterfs/glusterfs.go @@ -673,6 +673,11 @@ func (p *glusterfsVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { glog.V(4).Infof("not able to parse your claim Selector") return nil, fmt.Errorf("not able to parse your claim Selector") } + + if volutil.CheckPersistentVolumeClaimModeBlock(p.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", p.plugin.GetPluginName()) + } + glog.V(4).Infof("Provision VolumeOptions %v", p.options) scName := v1helper.GetPersistentVolumeClaimClass(p.options.PVC) cfg, err := parseClassParameters(p.options.Parameters, p.plugin.host.GetKubeClient()) diff --git a/pkg/volume/host_path/host_path.go b/pkg/volume/host_path/host_path.go index 4bde9891cdf..9ae641d6b09 100644 --- a/pkg/volume/host_path/host_path.go +++ b/pkg/volume/host_path/host_path.go @@ -266,6 +266,10 @@ type hostPathProvisioner struct { // Create for hostPath simply creates a local /tmp/hostpath_pv/%s directory as a new PersistentVolume. // This Provisioner is meant for development and testing only and WILL NOT WORK in a multi-node cluster. func (r *hostPathProvisioner) Provision() (*v1.PersistentVolume, error) { + if util.CheckPersistentVolumeClaimModeBlock(r.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", r.plugin.GetPluginName()) + } + fullpath := fmt.Sprintf("/tmp/hostpath_pv/%s", uuid.NewUUID()) capacity := r.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] diff --git a/pkg/volume/photon_pd/photon_pd.go b/pkg/volume/photon_pd/photon_pd.go index 25ca23928d0..3f615ed7f2f 100644 --- a/pkg/volume/photon_pd/photon_pd.go +++ b/pkg/volume/photon_pd/photon_pd.go @@ -345,6 +345,10 @@ func (p *photonPersistentDiskProvisioner) Provision() (*v1.PersistentVolume, err return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(p.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", p.plugin.GetPluginName()) + } + pdID, sizeGB, fstype, err := p.manager.CreateVolume(p) if err != nil { return nil, err diff --git a/pkg/volume/portworx/portworx.go b/pkg/volume/portworx/portworx.go index c0cde9fde9e..2378bb32fbc 100644 --- a/pkg/volume/portworx/portworx.go +++ b/pkg/volume/portworx/portworx.go @@ -383,6 +383,10 @@ func (c *portworxVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) + } + volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c) if err != nil { return nil, err diff --git a/pkg/volume/quobyte/quobyte.go b/pkg/volume/quobyte/quobyte.go index 0a5990b2900..f919166a322 100644 --- a/pkg/volume/quobyte/quobyte.go +++ b/pkg/volume/quobyte/quobyte.go @@ -359,6 +359,10 @@ func (provisioner *quobyteVolumeProvisioner) Provision() (*v1.PersistentVolume, return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(provisioner.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", provisioner.plugin.GetPluginName()) + } + if provisioner.options.PVC.Spec.Selector != nil { return nil, fmt.Errorf("claim Selector is not supported") } diff --git a/pkg/volume/rbd/BUILD b/pkg/volume/rbd/BUILD index 65042418cd4..e6b7fe8aead 100644 --- a/pkg/volume/rbd/BUILD +++ b/pkg/volume/rbd/BUILD @@ -17,6 +17,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/volume/rbd", deps = [ + "//pkg/features:go_default_library", "//pkg/util/file:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/util/node:go_default_library", @@ -33,6 +34,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/pkg/volume/rbd/rbd.go b/pkg/volume/rbd/rbd.go index 615b89554ab..283403f273c 100644 --- a/pkg/volume/rbd/rbd.go +++ b/pkg/volume/rbd/rbd.go @@ -30,7 +30,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/uuid" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/volume" @@ -698,6 +700,11 @@ func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dMi", sizeMB)), } pv.Spec.MountOptions = r.options.MountOptions + + if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) { + pv.Spec.VolumeMode = r.options.PVC.Spec.VolumeMode + } + return pv, nil } diff --git a/pkg/volume/scaleio/sio_volume.go b/pkg/volume/scaleio/sio_volume.go index b7f02701118..3c1cb2da8b5 100644 --- a/pkg/volume/scaleio/sio_volume.go +++ b/pkg/volume/scaleio/sio_volume.go @@ -259,6 +259,10 @@ func (v *sioVolume) Provision() (*api.PersistentVolume, error) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(v.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", v.plugin.GetPluginName()) + } + // setup volume attrributes genName := v.generateName("k8svol", 11) var oneGig int64 = 1024 * 1024 * 1024 diff --git a/pkg/volume/storageos/storageos.go b/pkg/volume/storageos/storageos.go index a814c438dd1..42a5df7f050 100644 --- a/pkg/volume/storageos/storageos.go +++ b/pkg/volume/storageos/storageos.go @@ -564,6 +564,9 @@ func (c *storageosProvisioner) Provision() (*v1.PersistentVolume, error) { if !util.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName()) + } var adminSecretName, adminSecretNamespace string diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index 663d8d62946..18956ef89b8 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -718,6 +718,12 @@ func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) { return true, nil } +// CheckPersistentVolumeClaimModeBlock checks VolumeMode. +// If the mode is Block, return true otherwise return false. +func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool { + return utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock +} + // MakeAbsolutePath convert path to absolute path according to GOOS func MakeAbsolutePath(goos, path string) string { if goos != "windows" { diff --git a/pkg/volume/vsphere_volume/vsphere_volume.go b/pkg/volume/vsphere_volume/vsphere_volume.go index 7b1e611df54..377622c7a3a 100644 --- a/pkg/volume/vsphere_volume/vsphere_volume.go +++ b/pkg/volume/vsphere_volume/vsphere_volume.go @@ -352,6 +352,9 @@ func (v *vsphereVolumeProvisioner) Provision() (*v1.PersistentVolume, error) { if !util.AccessModesContainedInAll(v.plugin.GetAccessModes(), v.options.PVC.Spec.AccessModes) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes()) } + if util.CheckPersistentVolumeClaimModeBlock(v.options.PVC) { + return nil, fmt.Errorf("%s does not support block volume provisioning", v.plugin.GetPluginName()) + } volSpec, err := v.manager.CreateVolume(v) if err != nil { diff --git a/test/e2e/storage/volume_provisioning.go b/test/e2e/storage/volume_provisioning.go index 8dd4a4cffcd..7b936ee3606 100644 --- a/test/e2e/storage/volume_provisioning.go +++ b/test/e2e/storage/volume_provisioning.go @@ -57,6 +57,7 @@ type storageClassTest struct { pvCheck func(volume *v1.PersistentVolume) error nodeName string attach bool + volumeMode *v1.PersistentVolumeMode } const ( @@ -120,6 +121,10 @@ func testDynamicProvisioning(t storageClassTest, client clientset.Interface, cla Expect(pv.Spec.PersistentVolumeReclaimPolicy).To(Equal(*class.ReclaimPolicy)) Expect(pv.Spec.MountOptions).To(Equal(class.MountOptions)) } + if t.volumeMode != nil { + Expect(pv.Spec.VolumeMode).NotTo(BeNil()) + Expect(*pv.Spec.VolumeMode).To(Equal(*t.volumeMode)) + } // Run the checker if t.pvCheck != nil { @@ -818,6 +823,34 @@ var _ = utils.SIGDescribe("Dynamic Provisioning", func() { testDynamicProvisioning(test, c, claim, class) }) }) + + Describe("Block volume provisioning [Feature:BlockVolume]", func() { + It("should create and delete block persistent volumes", func() { + + // TODO: add openstack once Cinder volume plugin supports block volumes + framework.SkipUnlessProviderIs("gce", "aws", "gke", "vsphere", "azure") + + By("creating a claim with default class") + block := v1.PersistentVolumeBlock + test := storageClassTest{ + name: "default", + claimSize: "2Gi", + expectedSize: "2Gi", + volumeMode: &block, + } + // gce or gke + if getDefaultPluginName() == "kubernetes.io/gce-pd" { + // using GB not GiB as e2e test unit since gce-pd returns GB, + // or expectedSize may be greater than claimSize. + test.claimSize = "2G" + test.expectedSize = "2G" + } + + claim := newClaim(test, ns, "default") + claim.Spec.VolumeMode = &block + testDynamicProvisioning(test, c, claim, nil) + }) + }) }) func getDefaultStorageClassName(c clientset.Interface) string {