Unverified Commit b18d86d5 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #51498 from NickrenREN/pvc-resize-cinder

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Implement volume resize for cinder **What this PR does / why we need it**: resize for cinder xref: [resize proposal](https://github.com/kubernetes/community/pull/657) **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: xref https://github.com/kubernetes/community/pull/657 Follow up: #49727 **Special notes for your reviewer**: **Release note**: ```release-note Implement volume resize for cinder ``` wip, assign to myself first /assign @NickrenREN
parents afb26b93 af108fb9
...@@ -1574,107 +1574,111 @@ ...@@ -1574,107 +1574,111 @@
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud", "ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack", "ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions",
"Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions", "ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces", "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach", "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", "ImportPath": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports", "ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/ports",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/pagination", "ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gorilla/context", "ImportPath": "github.com/gorilla/context",
......
...@@ -29,6 +29,7 @@ go_library( ...@@ -29,6 +29,7 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/gophercloud/gophercloud: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: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/v1/volumes:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/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", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces:go_default_library",
...@@ -51,6 +52,7 @@ go_library( ...@@ -51,6 +52,7 @@ go_library(
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/gopkg.in/gcfg.v1: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/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
......
...@@ -24,9 +24,11 @@ import ( ...@@ -24,9 +24,11 @@ import (
"strings" "strings"
"time" "time"
"k8s.io/apimachinery/pkg/api/resource"
k8s_volume "k8s.io/kubernetes/pkg/volume" k8s_volume "k8s.io/kubernetes/pkg/volume"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
...@@ -39,6 +41,7 @@ type volumeService interface { ...@@ -39,6 +41,7 @@ type volumeService interface {
createVolume(opts VolumeCreateOpts) (string, string, error) createVolume(opts VolumeCreateOpts) (string, string, error)
getVolume(volumeID string) (Volume, error) getVolume(volumeID string) (Volume, error)
deleteVolume(volumeName string) error deleteVolume(volumeName string) error
expandVolume(volumeID string, newSize int) error
} }
// Volumes implementation for v1 // Volumes implementation for v1
...@@ -64,6 +67,8 @@ type Volume struct { ...@@ -64,6 +67,8 @@ type Volume struct {
Name string Name string
// Current status of the volume. // Current status of the volume.
Status string Status string
// Volume size in GB
Size int
} }
type VolumeCreateOpts struct { type VolumeCreateOpts struct {
...@@ -139,6 +144,7 @@ func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) { ...@@ -139,6 +144,7 @@ func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) {
ID: volumeV1.ID, ID: volumeV1.ID,
Name: volumeV1.Name, Name: volumeV1.Name,
Status: volumeV1.Status, Status: volumeV1.Status,
Size: volumeV1.Size,
} }
if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil { if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil {
...@@ -162,6 +168,7 @@ func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) { ...@@ -162,6 +168,7 @@ func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) {
ID: volumeV2.ID, ID: volumeV2.ID,
Name: volumeV2.Name, Name: volumeV2.Name,
Status: volumeV2.Status, Status: volumeV2.Status,
Size: volumeV2.Size,
} }
if len(volumeV2.Attachments) > 0 { if len(volumeV2.Attachments) > 0 {
...@@ -188,6 +195,30 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error { ...@@ -188,6 +195,30 @@ func (volumes *VolumesV2) deleteVolume(volumeID string) error {
return err 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) { func (os *OpenStack) OperationPending(diskName string) (bool, string, error) {
volume, err := os.getVolume(diskName) volume, err := os.getVolume(diskName)
if err != nil { if err != nil {
...@@ -274,6 +305,39 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error { ...@@ -274,6 +305,39 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error {
return nil 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. // getVolume retrieves Volume by its ID.
func (os *OpenStack) getVolume(volumeID string) (Volume, error) { func (os *OpenStack) getVolume(volumeID string) (Volume, error) {
volumes, err := os.volumeService("") volumes, err := os.volumeService("")
......
...@@ -52,6 +52,7 @@ go_test( ...@@ -52,6 +52,7 @@ go_test(
"//pkg/volume/testing:go_default_library", "//pkg/volume/testing:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog: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",
], ],
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"testing" "testing"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing" volumetest "k8s.io/kubernetes/pkg/volume/testing"
...@@ -583,6 +584,10 @@ func (testcase *testcase) InstanceID() (string, error) { ...@@ -583,6 +584,10 @@ func (testcase *testcase) InstanceID() (string, error) {
return testcase.instanceID, nil 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 { func (testcase *testcase) DeleteVolume(volumeID string) error {
return errors.New("Not implemented") return errors.New("Not implemented")
} }
......
...@@ -55,6 +55,7 @@ type CinderProvider interface { ...@@ -55,6 +55,7 @@ type CinderProvider interface {
DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error)
ShouldTrustDevicePath() bool ShouldTrustDevicePath() bool
Instances() (cloudprovider.Instances, bool) Instances() (cloudprovider.Instances, bool)
ExpandVolume(volumeID string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error)
} }
type cinderPlugin struct { type cinderPlugin struct {
...@@ -227,6 +228,31 @@ func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (* ...@@ -227,6 +228,31 @@ func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*
return volume.NewSpecFromVolume(cinderVolume), nil 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. // Abstract interface to PD operations.
type cdManager interface { type cdManager interface {
// Attaches the disk to the kubelet's host machine. // Attaches the disk to the kubelet's host machine.
......
...@@ -152,6 +152,9 @@ func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVol ...@@ -152,6 +152,9 @@ func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVol
if pv.Spec.Glusterfs != nil { if pv.Spec.Glusterfs != nil {
return true return true
} }
if pv.Spec.Cinder != nil {
return true
}
return false return false
} }
...@@ -408,31 +408,31 @@ ...@@ -408,31 +408,31 @@
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud", "ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack", "ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/pagination", "ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gregjones/httpcache", "ImportPath": "github.com/gregjones/httpcache",
......
...@@ -172,31 +172,31 @@ ...@@ -172,31 +172,31 @@
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud", "ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack", "ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", "ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils", "ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gophercloud/gophercloud/pagination", "ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "0b6b13c4dd9e07a89f83cbe4617c13ad646d6362" "Rev": "54086d6c81b90e91e1d52b866ee726baf5cbe2b1"
}, },
{ {
"ImportPath": "github.com/gregjones/httpcache", "ImportPath": "github.com/gregjones/httpcache",
......
...@@ -30,6 +30,7 @@ filegroup( ...@@ -30,6 +30,7 @@ filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes:all-srcs", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:all-srcs", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:all-srcs",
"//vendor/github.com/gophercloud/gophercloud/openstack/common/extensions:all-srcs", "//vendor/github.com/gophercloud/gophercloud/openstack/common/extensions:all-srcs",
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
importpath = "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/gophercloud/gophercloud:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
Package volumeactions provides information and interaction with volumes in the
OpenStack Block Storage service. A volume is a detachable block storage
device, akin to a USB hard drive.
Example of Attaching a Volume to an Instance
attachOpts := volumeactions.AttachOpts{
MountPoint: "/mnt",
Mode: "rw",
InstanceUUID: server.ID,
}
err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr()
if err != nil {
panic(err)
}
detachOpts := volumeactions.DetachOpts{
AttachmentID: volume.Attachments[0].AttachmentID,
}
err = volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr()
if err != nil {
panic(err)
}
Example of Creating an Image from a Volume
uploadImageOpts := volumeactions.UploadImageOpts{
ImageName: "my_vol",
Force: true,
}
volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", volumeImage)
Example of Extending a Volume's Size
extendOpts := volumeactions.ExtendSizeOpts{
NewSize: 100,
}
err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr()
if err != nil {
panic(err)
}
Example of Initializing a Volume Connection
connectOpts := &volumeactions.InitializeConnectionOpts{
IP: "127.0.0.1",
Host: "stack",
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
Multipath: gophercloud.Disabled,
Platform: "x86_64",
OSType: "linux2",
}
connectionInfo, err := volumeactions.InitializeConnection(client, volume.ID, connectOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", connectionInfo["data"])
terminateOpts := &volumeactions.InitializeConnectionOpts{
IP: "127.0.0.1",
Host: "stack",
Initiator: "iqn.1994-05.com.redhat:17cf566367d2",
Multipath: gophercloud.Disabled,
Platform: "x86_64",
OSType: "linux2",
}
err = volumeactions.TerminateConnection(client, volume.ID, terminateOpts).ExtractErr()
if err != nil {
panic(err)
}
*/
package volumeactions
package volumeactions
import (
"github.com/gophercloud/gophercloud"
)
// AttachOptsBuilder allows extensions to add additional parameters to the
// Attach request.
type AttachOptsBuilder interface {
ToVolumeAttachMap() (map[string]interface{}, error)
}
// AttachMode describes the attachment mode for volumes.
type AttachMode string
// These constants determine how a volume is attached.
const (
ReadOnly AttachMode = "ro"
ReadWrite AttachMode = "rw"
)
// AttachOpts contains options for attaching a Volume.
type AttachOpts struct {
// The mountpoint of this volume.
MountPoint string `json:"mountpoint,omitempty"`
// The nova instance ID, can't set simultaneously with HostName.
InstanceUUID string `json:"instance_uuid,omitempty"`
// The hostname of baremetal host, can't set simultaneously with InstanceUUID.
HostName string `json:"host_name,omitempty"`
// Mount mode of this volume.
Mode AttachMode `json:"mode,omitempty"`
}
// ToVolumeAttachMap assembles a request body based on the contents of a
// AttachOpts.
func (opts AttachOpts) ToVolumeAttachMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-attach")
}
// Attach will attach a volume based on the values in AttachOpts.
func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder) (r AttachResult) {
b, err := opts.ToVolumeAttachMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(attachURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// BeginDetach will mark the volume as detaching.
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
_, r.Err = client.Post(beginDetachingURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// DetachOptsBuilder allows extensions to add additional parameters to the
// Detach request.
type DetachOptsBuilder interface {
ToVolumeDetachMap() (map[string]interface{}, error)
}
// DetachOpts contains options for detaching a Volume.
type DetachOpts struct {
// AttachmentID is the ID of the attachment between a volume and instance.
AttachmentID string `json:"attachment_id,omitempty"`
}
// ToVolumeDetachMap assembles a request body based on the contents of a
// DetachOpts.
func (opts DetachOpts) ToVolumeDetachMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-detach")
}
// Detach will detach a volume based on volume ID.
func Detach(client *gophercloud.ServiceClient, id string, opts DetachOptsBuilder) (r DetachResult) {
b, err := opts.ToVolumeDetachMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(detachURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// Reserve will reserve a volume based on volume ID.
func Reserve(client *gophercloud.ServiceClient, id string) (r ReserveResult) {
b := map[string]interface{}{"os-reserve": make(map[string]interface{})}
_, r.Err = client.Post(reserveURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return
}
// Unreserve will unreserve a volume based on volume ID.
func Unreserve(client *gophercloud.ServiceClient, id string) (r UnreserveResult) {
b := map[string]interface{}{"os-unreserve": make(map[string]interface{})}
_, r.Err = client.Post(unreserveURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return
}
// InitializeConnectionOptsBuilder allows extensions to add additional parameters to the
// InitializeConnection request.
type InitializeConnectionOptsBuilder interface {
ToVolumeInitializeConnectionMap() (map[string]interface{}, error)
}
// InitializeConnectionOpts hosts options for InitializeConnection.
// The fields are specific to the storage driver in use and the destination
// attachment.
type InitializeConnectionOpts struct {
IP string `json:"ip,omitempty"`
Host string `json:"host,omitempty"`
Initiator string `json:"initiator,omitempty"`
Wwpns []string `json:"wwpns,omitempty"`
Wwnns string `json:"wwnns,omitempty"`
Multipath *bool `json:"multipath,omitempty"`
Platform string `json:"platform,omitempty"`
OSType string `json:"os_type,omitempty"`
}
// ToVolumeInitializeConnectionMap assembles a request body based on the contents of a
// InitializeConnectionOpts.
func (opts InitializeConnectionOpts) ToVolumeInitializeConnectionMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "connector")
return map[string]interface{}{"os-initialize_connection": b}, err
}
// InitializeConnection initializes an iSCSI connection by volume ID.
func InitializeConnection(client *gophercloud.ServiceClient, id string, opts InitializeConnectionOptsBuilder) (r InitializeConnectionResult) {
b, err := opts.ToVolumeInitializeConnectionMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(initializeConnectionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return
}
// TerminateConnectionOptsBuilder allows extensions to add additional parameters to the
// TerminateConnection request.
type TerminateConnectionOptsBuilder interface {
ToVolumeTerminateConnectionMap() (map[string]interface{}, error)
}
// TerminateConnectionOpts hosts options for TerminateConnection.
type TerminateConnectionOpts struct {
IP string `json:"ip,omitempty"`
Host string `json:"host,omitempty"`
Initiator string `json:"initiator,omitempty"`
Wwpns []string `json:"wwpns,omitempty"`
Wwnns string `json:"wwnns,omitempty"`
Multipath *bool `json:"multipath,omitempty"`
Platform string `json:"platform,omitempty"`
OSType string `json:"os_type,omitempty"`
}
// ToVolumeTerminateConnectionMap assembles a request body based on the contents of a
// TerminateConnectionOpts.
func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "connector")
return map[string]interface{}{"os-terminate_connection": b}, err
}
// TerminateConnection terminates an iSCSI connection by volume ID.
func TerminateConnection(client *gophercloud.ServiceClient, id string, opts TerminateConnectionOptsBuilder) (r TerminateConnectionResult) {
b, err := opts.ToVolumeTerminateConnectionMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(teminateConnectionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// ExtendSizeOptsBuilder allows extensions to add additional parameters to the
// ExtendSize request.
type ExtendSizeOptsBuilder interface {
ToVolumeExtendSizeMap() (map[string]interface{}, error)
}
// ExtendSizeOpts contains options for extending the size of an existing Volume.
// This object is passed to the volumes.ExtendSize function.
type ExtendSizeOpts struct {
// NewSize is the new size of the volume, in GB.
NewSize int `json:"new_size" required:"true"`
}
// ToVolumeExtendSizeMap assembles a request body based on the contents of an
// ExtendSizeOpts.
func (opts ExtendSizeOpts) ToVolumeExtendSizeMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-extend")
}
// ExtendSize will extend the size of the volume based on the provided information.
// This operation does not return a response body.
func ExtendSize(client *gophercloud.ServiceClient, id string, opts ExtendSizeOptsBuilder) (r ExtendSizeResult) {
b, err := opts.ToVolumeExtendSizeMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(extendSizeURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// UploadImageOptsBuilder allows extensions to add additional parameters to the
// UploadImage request.
type UploadImageOptsBuilder interface {
ToVolumeUploadImageMap() (map[string]interface{}, error)
}
// UploadImageOpts contains options for uploading a Volume to image storage.
type UploadImageOpts struct {
// Container format, may be bare, ofv, ova, etc.
ContainerFormat string `json:"container_format,omitempty"`
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
DiskFormat string `json:"disk_format,omitempty"`
// The name of image that will be stored in glance.
ImageName string `json:"image_name,omitempty"`
// Force image creation, usable if volume attached to instance.
Force bool `json:"force,omitempty"`
}
// ToVolumeUploadImageMap assembles a request body based on the contents of a
// UploadImageOpts.
func (opts UploadImageOpts) ToVolumeUploadImageMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-volume_upload_image")
}
// UploadImage will upload an image based on the values in UploadImageOptsBuilder.
func UploadImage(client *gophercloud.ServiceClient, id string, opts UploadImageOptsBuilder) (r UploadImageResult) {
b, err := opts.ToVolumeUploadImageMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(uploadURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
package volumeactions
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
)
// AttachResult contains the response body and error from an Attach request.
type AttachResult struct {
gophercloud.ErrResult
}
// BeginDetachingResult contains the response body and error from a BeginDetach
// request.
type BeginDetachingResult struct {
gophercloud.ErrResult
}
// DetachResult contains the response body and error from a Detach request.
type DetachResult struct {
gophercloud.ErrResult
}
// UploadImageResult contains the response body and error from an UploadImage
// request.
type UploadImageResult struct {
gophercloud.Result
}
// ReserveResult contains the response body and error from a Reserve request.
type ReserveResult struct {
gophercloud.ErrResult
}
// UnreserveResult contains the response body and error from an Unreserve
// request.
type UnreserveResult struct {
gophercloud.ErrResult
}
// TerminateConnectionResult contains the response body and error from a
// TerminateConnection request.
type TerminateConnectionResult struct {
gophercloud.ErrResult
}
// InitializeConnectionResult contains the response body and error from an
// InitializeConnection request.
type InitializeConnectionResult struct {
gophercloud.Result
}
// ExtendSizeResult contains the response body and error from an ExtendSize request.
type ExtendSizeResult struct {
gophercloud.ErrResult
}
// Extract will get the connection information out of the
// InitializeConnectionResult object.
//
// This will be a generic map[string]interface{} and the results will be
// dependent on the type of connection made.
func (r InitializeConnectionResult) Extract() (map[string]interface{}, error) {
var s struct {
ConnectionInfo map[string]interface{} `json:"connection_info"`
}
err := r.ExtractInto(&s)
return s.ConnectionInfo, err
}
// ImageVolumeType contains volume type information obtained from UploadImage
// action.
type ImageVolumeType struct {
// The ID of a volume type.
ID string `json:"id"`
// Human-readable display name for the volume type.
Name string `json:"name"`
// Human-readable description for the volume type.
Description string `json:"display_description"`
// Flag for public access.
IsPublic bool `json:"is_public"`
// Extra specifications for volume type.
ExtraSpecs map[string]interface{} `json:"extra_specs"`
// ID of quality of service specs.
QosSpecsID string `json:"qos_specs_id"`
// Flag for deletion status of volume type.
Deleted bool `json:"deleted"`
// The date when volume type was deleted.
DeletedAt time.Time `json:"-"`
// The date when volume type was created.
CreatedAt time.Time `json:"-"`
// The date when this volume was last updated.
UpdatedAt time.Time `json:"-"`
}
func (r *ImageVolumeType) UnmarshalJSON(b []byte) error {
type tmp ImageVolumeType
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
DeletedAt gophercloud.JSONRFC3339MilliNoZ `json:"deleted_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = ImageVolumeType(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
r.DeletedAt = time.Time(s.DeletedAt)
return err
}
// VolumeImage contains information about volume uploaded to an image service.
type VolumeImage struct {
// The ID of a volume an image is created from.
VolumeID string `json:"id"`
// Container format, may be bare, ofv, ova, etc.
ContainerFormat string `json:"container_format"`
// Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc.
DiskFormat string `json:"disk_format"`
// Human-readable description for the volume.
Description string `json:"display_description"`
// The ID of the created image.
ImageID string `json:"image_id"`
// Human-readable display name for the image.
ImageName string `json:"image_name"`
// Size of the volume in GB.
Size int `json:"size"`
// Current status of the volume.
Status string `json:"status"`
// The date when this volume was last updated.
UpdatedAt time.Time `json:"-"`
// Volume type object of used volume.
VolumeType ImageVolumeType `json:"volume_type"`
}
func (r *VolumeImage) UnmarshalJSON(b []byte) error {
type tmp VolumeImage
var s struct {
tmp
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = VolumeImage(s.tmp)
r.UpdatedAt = time.Time(s.UpdatedAt)
return err
}
// Extract will get an object with info about the uploaded image out of the
// UploadImageResult object.
func (r UploadImageResult) Extract() (VolumeImage, error) {
var s struct {
VolumeImage VolumeImage `json:"os-volume_upload_image"`
}
err := r.ExtractInto(&s)
return s.VolumeImage, err
}
package volumeactions
import "github.com/gophercloud/gophercloud"
func attachURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("volumes", id, "action")
}
func beginDetachingURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func detachURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func uploadURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func reserveURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func unreserveURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func initializeConnectionURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func teminateConnectionURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
func extendSizeURL(c *gophercloud.ServiceClient, id string) string {
return attachURL(c, id)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment