diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 42237dd5d6a..bf64ecd502c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1567,10 +1567,10 @@ "ImportPath": "github.com/gophercloud/gophercloud/openstack", "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1" }, - { - "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions", - "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1" - }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions", + "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1" + }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1" diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index 756b145eeb9..3b04ef8c048 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -29,6 +29,7 @@ go_library( "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces:go_default_library", @@ -51,6 +52,7 @@ go_library( "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/gopkg.in/gcfg.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/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/cloudprovider/providers/openstack/openstack_volumes.go b/pkg/cloudprovider/providers/openstack/openstack_volumes.go index 8d308fe547f..aa3ec6f90c2 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_volumes.go +++ b/pkg/cloudprovider/providers/openstack/openstack_volumes.go @@ -24,9 +24,11 @@ import ( "strings" "time" + "k8s.io/apimachinery/pkg/api/resource" k8s_volume "k8s.io/kubernetes/pkg/volume" "github.com/gophercloud/gophercloud" + volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" @@ -39,6 +41,7 @@ type volumeService interface { createVolume(opts VolumeCreateOpts) (string, string, error) getVolume(volumeID string) (Volume, error) deleteVolume(volumeName string) error + expandVolume(volumeID string, newSize int) error } // Volumes implementation for v1 @@ -64,6 +67,8 @@ type Volume struct { Name string // Current status of the volume. Status string + // Volume size in GB + Size int } type VolumeCreateOpts struct { @@ -139,6 +144,7 @@ func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) { ID: volumeV1.ID, Name: volumeV1.Name, Status: volumeV1.Status, + Size: volumeV1.Size, } if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil { @@ -162,6 +168,7 @@ func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { ID: volumeV2.ID, Name: volumeV2.Name, Status: volumeV2.Status, + Size: volumeV2.Size, } if len(volumeV2.Attachments) > 0 { @@ -188,6 +195,30 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error { return err } +func (volumes *VolumesV1) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + create_opts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, create_opts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + + return err +} + +func (volumes *VolumesV2) expandVolume(volumeID string, newSize int) error { + startTime := time.Now() + create_opts := volumeexpand.ExtendSizeOpts{ + NewSize: newSize, + } + err := volumeexpand.ExtendSize(volumes.blockstorage, volumeID, create_opts).ExtractErr() + timeTaken := time.Since(startTime).Seconds() + recordOpenstackOperationMetric("expand_volume", timeTaken, err) + + return err +} + func (os *OpenStack) OperationPending(diskName string) (bool, string, error) { volume, err := os.getVolume(diskName) if err != nil { @@ -274,6 +305,39 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { return nil } +// ExpandVolume expands the size of specific cinder volume (in GiB) +func (os *OpenStack) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + volume, err := os.getVolume(volumeID) + if err != nil { + return oldSize, err + } + if volume.Status != VolumeAvailableStatus { + // cinder volume can not be expanded if its status is not available + return oldSize, fmt.Errorf("volume status is not available") + } + + volSizeBytes := newSize.Value() + // Cinder works with gigabytes, convert to GiB with rounding up + volSizeGB := int(k8s_volume.RoundUpSize(volSizeBytes, 1024*1024*1024)) + newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", volSizeGB)) + + // if volume size equals to or greater than the newSize, return nil + if volume.Size >= volSizeGB { + return newSizeQuant, nil + } + + volumes, err := os.volumeService("") + if err != nil { + return oldSize, err + } + + err = volumes.expandVolume(volumeID, volSizeGB) + if err != nil { + return oldSize, err + } + return newSizeQuant, nil +} + // getVolume retrieves Volume by its ID. func (os *OpenStack) getVolume(volumeID string) (Volume, error) { volumes, err := os.volumeService("") diff --git a/pkg/volume/cinder/BUILD b/pkg/volume/cinder/BUILD index 278368682e7..35ef7a2a21f 100644 --- a/pkg/volume/cinder/BUILD +++ b/pkg/volume/cinder/BUILD @@ -52,6 +52,7 @@ go_test( "//pkg/volume/testing:go_default_library", "//vendor/github.com/golang/glog: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", ], diff --git a/pkg/volume/cinder/attacher_test.go b/pkg/volume/cinder/attacher_test.go index 19a156f4202..f868db675bf 100644 --- a/pkg/volume/cinder/attacher_test.go +++ b/pkg/volume/cinder/attacher_test.go @@ -22,6 +22,7 @@ import ( "testing" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/volume" volumetest "k8s.io/kubernetes/pkg/volume/testing" @@ -583,6 +584,10 @@ func (testcase *testcase) InstanceID() (string, error) { return testcase.instanceID, nil } +func (testcase *testcase) ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { + return resource.Quantity{}, nil +} + func (testcase *testcase) DeleteVolume(volumeID string) error { return errors.New("Not implemented") } diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go index cef425b2499..c5b785cd0ab 100644 --- a/pkg/volume/cinder/cinder.go +++ b/pkg/volume/cinder/cinder.go @@ -55,6 +55,7 @@ type CinderProvider interface { DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) ShouldTrustDevicePath() bool Instances() (cloudprovider.Instances, bool) + ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) } type cinderPlugin struct { @@ -227,6 +228,31 @@ func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (* return volume.NewSpecFromVolume(cinderVolume), nil } +var _ volume.ExpandableVolumePlugin = &cinderPlugin{} + +func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { + cinder, _, err := getVolumeSource(spec) + if err != nil { + return oldSize, err + } + cloud, err := plugin.getCloudProvider() + if err != nil { + return oldSize, err + } + + expandedSize, err := cloud.ExpandVolume(cinder.VolumeID, oldSize, newSize) + if err != nil { + return oldSize, err + } + + glog.V(2).Infof("volume %s expanded to new size %d successfully", cinder.VolumeID, int(newSize.Value())) + return expandedSize, nil +} + +func (plugin *cinderPlugin) RequiresFSResize() bool { + return true +} + // Abstract interface to PD operations. type cdManager interface { // Attaches the disk to the kubelet's host machine. diff --git a/plugin/pkg/admission/persistentvolume/resize/admission.go b/plugin/pkg/admission/persistentvolume/resize/admission.go index 97f33fbe998..f5327ecf636 100644 --- a/plugin/pkg/admission/persistentvolume/resize/admission.go +++ b/plugin/pkg/admission/persistentvolume/resize/admission.go @@ -152,6 +152,9 @@ func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVol if pv.Spec.Glusterfs != nil { return true } + if pv.Spec.Cinder != nil { + return true + } return false }