mirror of https://github.com/k3s-io/k3s
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
parent
4761788b2a
commit
adc71854e2
|
@ -15,6 +15,7 @@ go_test(
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/testing:go_default_library",
|
"//pkg/volume/testing:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1: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/apimachinery/pkg/types:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
||||||
],
|
],
|
||||||
|
|
|
@ -50,6 +50,7 @@ var _ volume.VolumePlugin = &portworxVolumePlugin{}
|
||||||
var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{}
|
var _ volume.PersistentVolumePlugin = &portworxVolumePlugin{}
|
||||||
var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{}
|
var _ volume.DeletableVolumePlugin = &portworxVolumePlugin{}
|
||||||
var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{}
|
var _ volume.ProvisionableVolumePlugin = &portworxVolumePlugin{}
|
||||||
|
var _ volume.ExpandableVolumePlugin = &portworxVolumePlugin{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
portworxVolumePluginName = "kubernetes.io/portworx-volume"
|
portworxVolumePluginName = "kubernetes.io/portworx-volume"
|
||||||
|
@ -171,6 +172,24 @@ func (plugin *portworxVolumePlugin) newProvisionerInternal(options volume.Volume
|
||||||
}, nil
|
}, 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) {
|
func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||||
portworxVolume := &v1.Volume{
|
portworxVolume := &v1.Volume{
|
||||||
Name: volumeName,
|
Name: volumeName,
|
||||||
|
@ -206,7 +225,7 @@ func getVolumeSource(
|
||||||
// Abstract interface to PD operations.
|
// Abstract interface to PD operations.
|
||||||
type portworxManager interface {
|
type portworxManager interface {
|
||||||
// Creates a volume
|
// 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
|
// Deletes a volume
|
||||||
DeleteVolume(deleter *portworxVolumeDeleter) error
|
DeleteVolume(deleter *portworxVolumeDeleter) error
|
||||||
// Attach a volume
|
// Attach a volume
|
||||||
|
@ -217,6 +236,8 @@ type portworxManager interface {
|
||||||
MountVolume(mounter *portworxVolumeMounter, mountDir string) error
|
MountVolume(mounter *portworxVolumeMounter, mountDir string) error
|
||||||
// Unmount a volume
|
// Unmount a volume
|
||||||
UnmountVolume(unmounter *portworxVolumeUnmounter, mountDir string) error
|
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
|
// 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())
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -379,7 +400,7 @@ func (c *portworxVolumeProvisioner) Provision() (*v1.PersistentVolume, error) {
|
||||||
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
|
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
|
||||||
AccessModes: c.options.PVC.Spec.AccessModes,
|
AccessModes: c.options.PVC.Spec.AccessModes,
|
||||||
Capacity: v1.ResourceList{
|
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{
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||||
PortworxVolume: &v1.PortworxVolumeSource{
|
PortworxVolume: &v1.PortworxVolumeSource{
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utiltesting "k8s.io/client-go/util/testing"
|
utiltesting "k8s.io/client-go/util/testing"
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
@ -106,7 +107,7 @@ func (fake *fakePortworxManager) UnmountVolume(c *portworxVolumeUnmounter, mount
|
||||||
return nil
|
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 = make(map[string]string)
|
||||||
labels["fakeportworxmanager"] = "yes"
|
labels["fakeportworxmanager"] = "yes"
|
||||||
return PortworxTestVolume, 100, labels, nil
|
return PortworxTestVolume, 100, labels, nil
|
||||||
|
@ -119,6 +120,10 @@ func (fake *fakePortworxManager) DeleteVolume(cd *portworxVolumeDeleter) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fake *fakePortworxManager) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
|
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
package portworx
|
package portworx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
osdapi "github.com/libopenstorage/openstorage/api"
|
osdapi "github.com/libopenstorage/openstorage/api"
|
||||||
osdclient "github.com/libopenstorage/openstorage/api/client"
|
osdclient "github.com/libopenstorage/openstorage/api/client"
|
||||||
|
@ -24,6 +26,7 @@ import (
|
||||||
osdspec "github.com/libopenstorage/openstorage/api/spec"
|
osdspec "github.com/libopenstorage/openstorage/api/spec"
|
||||||
volumeapi "github.com/libopenstorage/openstorage/volume"
|
volumeapi "github.com/libopenstorage/openstorage/volume"
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
@ -45,7 +48,7 @@ type PortworxVolumeUtil struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVolume creates a Portworx volume.
|
// 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*/)
|
driver, err := util.getPortworxDriver(p.plugin.host, false /*localOnly*/)
|
||||||
if err != nil || driver == nil {
|
if err != nil || driver == nil {
|
||||||
glog.Errorf("Failed to get portworx driver. Err: %v", err)
|
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)
|
glog.Infof("Creating Portworx volume for PVC: %v", p.options.PVC.Name)
|
||||||
|
|
||||||
capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
||||||
// Portworx Volumes are specified in GB
|
// Portworx Volumes are specified in GiB
|
||||||
requestGB := int(volutil.RoundUpSize(capacity.Value(), 1024*1024*1024))
|
requestGiB := volutil.RoundUpToGiB(capacity)
|
||||||
|
|
||||||
// Perform a best-effort parsing of parameters. Portworx 1.2.9 and later parses volume parameters from
|
// 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
|
// 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.
|
// Pass all parameters as volume labels for Portworx server-side processing.
|
||||||
spec.VolumeLabels = p.options.Parameters
|
spec.VolumeLabels = p.options.Parameters
|
||||||
// Update the requested size in the spec
|
// 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
|
// Change the Portworx Volume name to PV name
|
||||||
if locator == nil {
|
if locator == nil {
|
||||||
locator = &osdapi.VolumeLocator{
|
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)
|
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
|
// DeleteVolume deletes a Portworx volume
|
||||||
|
@ -182,6 +186,55 @@ func (util *PortworxVolumeUtil) UnmountVolume(u *portworxVolumeUnmounter, mountP
|
||||||
return nil
|
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) {
|
func isClientValid(client *osdclient.Client) (bool, error) {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.Persistent
|
||||||
|
|
||||||
// checkVolumePlugin checks whether the volume plugin supports resize
|
// checkVolumePlugin checks whether the volume plugin supports resize
|
||||||
func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue