Add support to resize Portworx volume

Closes #62305

Signed-off-by: Harsh Desai <harsh@portworx.com>

update comment and variable references to GiB

Signed-off-by: Harsh Desai <harsh@portworx.com>

explicitly check volume size after resize and fix size volume spec

Signed-off-by: Harsh Desai <harsh@portworx.com>

If Portworx volume is already greater than new size, skip resize

Signed-off-by: Harsh Desai <harsh@portworx.com>

Allow updated volume to be greater than requested size

Signed-off-by: Harsh Desai <harsh@portworx.com>
pull/8/head
Harsh Desai 2018-03-16 14:36:21 -07:00
parent 4761788b2a
commit adc71854e2
5 changed files with 90 additions and 10 deletions

View File

@ -15,6 +15,7 @@ go_test(
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],

View File

@ -50,6 +50,7 @@ var _ volume.VolumePlugin = &portworxVolumePlugin{}
var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{}
var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{}
var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{}
var _ volume.ExpandableVolumePlugin = &portworxVolumePlugin{}
const (
portworxVolumePluginName = "kubernetes.io/portworx-volume"
@ -171,6 +172,24 @@ func (plugin *portworxVolumePlugin) newProvisionerInternal(options volume.Volume
}, nil
}
func (plugin *portworxVolumePlugin) RequiresFSResize() bool {
return false
}
func (plugin *portworxVolumePlugin) ExpandVolumeDevice(
spec *volume.Spec,
newSize resource.Quantity,
oldSize resource.Quantity) (resource.Quantity, error) {
glog.V(4).Infof("Expanding: %s from %v to %v", spec.Name(), oldSize, newSize)
err := plugin.util.ResizeVolume(spec, newSize, plugin.host)
if err != nil {
return oldSize, err
}
glog.V(4).Infof("Successfully resized %s to %v", spec.Name(), newSize)
return newSize, nil
}
func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
portworxVolume := &v1.Volume{
Name: volumeName,
@ -206,7 +225,7 @@ func getVolumeSource(
// Abstract interface to PD operations.
type portworxManager interface {
// Creates a volume
CreateVolume(provisioner *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int, labels map[string]string, err error)
CreateVolume(provisioner *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error)
// Deletes a volume
DeleteVolume(deleter *portworxVolumeDeleter) error
// Attach a volume
@ -217,6 +236,8 @@ type portworxManager interface {
MountVolume(mounter *portworxVolumeMounter, mountDir string) error
// Unmount a volume
UnmountVolume(unmounter *portworxVolumeUnmounter, mountDir string) error
// Resize a volume
ResizeVolume(spec *volume.Spec, newSize resource.Quantity, host volume.VolumeHost) error
}
// portworxVolume volumes are portworx block devices
@ -362,7 +383,7 @@ 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())
}
volumeID, sizeGB, labels, err := c.manager.CreateVolume(c)
volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c)
if err != nil {
return nil, err
}
@ -379,7 +400,7 @@ func (c *portworxVolumeProvisioner) Provision() (*v1.PersistentVolume, error) {
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
AccessModes: c.options.PVC.Spec.AccessModes,
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGiB)),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
PortworxVolume: &v1.PortworxVolumeSource{

View File

@ -23,6 +23,7 @@ import (
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/util/mount"
@ -106,7 +107,7 @@ func (fake *fakePortworxManager) UnmountVolume(c *portworxVolumeUnmounter, mount
return nil
}
func (fake *fakePortworxManager) CreateVolume(c *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int, labels map[string]string, err error) {
func (fake *fakePortworxManager) CreateVolume(c *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error) {
labels = make(map[string]string)
labels["fakeportworxmanager"] = "yes"
return PortworxTestVolume, 100, labels, nil
@ -119,6 +120,10 @@ func (fake *fakePortworxManager) DeleteVolume(cd *portworxVolumeDeleter) error {
return nil
}
func (fake *fakePortworxManager) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error {
return nil
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
if err != nil {

View File

@ -17,6 +17,8 @@ limitations under the License.
package portworx
import (
"fmt"
"github.com/golang/glog"
osdapi "github.com/libopenstorage/openstorage/api"
osdclient "github.com/libopenstorage/openstorage/api/client"
@ -24,6 +26,7 @@ import (
osdspec "github.com/libopenstorage/openstorage/api/spec"
volumeapi "github.com/libopenstorage/openstorage/volume"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/volume"
@ -45,7 +48,7 @@ type PortworxVolumeUtil struct {
}
// CreateVolume creates a Portworx volume.
func (util *PortworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (string, int, map[string]string, error) {
func (util *PortworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (string, int64, map[string]string, error) {
driver, err := util.getPortworxDriver(p.plugin.host, false /*localOnly*/)
if err != nil || driver == nil {
glog.Errorf("Failed to get portworx driver. Err: %v", err)
@ -55,8 +58,8 @@ func (util *PortworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (stri
glog.Infof("Creating Portworx volume for PVC: %v", p.options.PVC.Name)
capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
// Portworx Volumes are specified in GB
requestGB := int(volutil.RoundUpSize(capacity.Value(), 1024*1024*1024))
// Portworx Volumes are specified in GiB
requestGiB := volutil.RoundUpToGiB(capacity)
// Perform a best-effort parsing of parameters. Portworx 1.2.9 and later parses volume parameters from
// spec.VolumeLabels. So even if below SpecFromOpts() fails to parse certain parameters or
@ -71,7 +74,8 @@ func (util *PortworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (stri
// Pass all parameters as volume labels for Portworx server-side processing.
spec.VolumeLabels = p.options.Parameters
// Update the requested size in the spec
spec.Size = uint64(requestGB * 1024 * 1024 * 1024)
spec.Size = uint64(requestGiB * volutil.GIB)
// Change the Portworx Volume name to PV name
if locator == nil {
locator = &osdapi.VolumeLocator{
@ -99,7 +103,7 @@ func (util *PortworxVolumeUtil) CreateVolume(p *portworxVolumeProvisioner) (stri
}
glog.Infof("Successfully created Portworx volume for PVC: %v", p.options.PVC.Name)
return volumeID, requestGB, nil, err
return volumeID, requestGiB, nil, err
}
// DeleteVolume deletes a Portworx volume
@ -182,6 +186,55 @@ func (util *PortworxVolumeUtil) UnmountVolume(u *portworxVolumeUnmounter, mountP
return nil
}
func (util *PortworxVolumeUtil) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error {
driver, err := util.getPortworxDriver(volumeHost, false /*localOnly*/)
if err != nil || driver == nil {
glog.Errorf("Failed to get portworx driver. Err: %v", err)
return err
}
vols, err := driver.Inspect([]string{spec.Name()})
if err != nil {
return err
}
if len(vols) != 1 {
return fmt.Errorf("failed to inspect Portworx volume: %s. Found: %d volumes", spec.Name(), len(vols))
}
vol := vols[0]
newSizeInBytes := uint64(volutil.RoundUpToGiB(newSize) * volutil.GIB)
if vol.Spec.Size >= newSizeInBytes {
glog.Infof("Portworx volume: %s already at size: %d greater than or equal to new "+
"requested size: %d. Skipping resize.", vol.Spec.Size, newSizeInBytes)
return nil
}
vol.Spec.Size = newSizeInBytes
err = driver.Set(spec.Name(), vol.Locator, vol.Spec)
if err != nil {
return err
}
// check if the volume's size actually got updated
vols, err = driver.Inspect([]string{spec.Name()})
if err != nil {
return err
}
if len(vols) != 1 {
return fmt.Errorf("failed to inspect resized Portworx volume: %s. Found: %d volumes", spec.Name(), len(vols))
}
updatedVol := vols[0]
if updatedVol.Spec.Size < vol.Spec.Size {
return fmt.Errorf("Portworx volume: %s doesn't match expected size after resize. expected:%v actual:%v",
spec.Name(), vol.Spec.Size, updatedVol.Spec.Size)
}
return nil
}
func isClientValid(client *osdclient.Client) (bool, error) {
if client == nil {
return false, nil

View File

@ -149,7 +149,7 @@ func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.Persistent
// checkVolumePlugin checks whether the volume plugin supports resize
func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool {
if pv.Spec.Glusterfs != nil || pv.Spec.Cinder != nil || pv.Spec.RBD != nil {
if pv.Spec.Glusterfs != nil || pv.Spec.Cinder != nil || pv.Spec.RBD != nil || pv.Spec.PortworxVolume != nil {
return true
}