From 2c05bb533635cce090ab4e6499bfd1e724a71b52 Mon Sep 17 00:00:00 2001 From: Maxym Kutsevol Date: Thu, 12 Jan 2017 21:20:20 +0200 Subject: [PATCH 1/2] Support for v1/v2/autoprobe openstack cinder blockstorage Support for cinder v1/v2 api with the new gophercloud/gophercloud library. API version is configurable and defaulting autodetection. --- pkg/cloudprovider/providers/openstack/BUILD | 1 + .../providers/openstack/openstack.go | 114 +++++- .../providers/openstack/openstack_test.go | 54 ++- .../providers/openstack/openstack_volumes.go | 368 ++++++++++++------ 4 files changed, 415 insertions(+), 122 deletions(-) diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index 8500007745a..0cc537dcd00 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -72,6 +72,7 @@ go_test( "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", "//vendor:github.com/gophercloud/gophercloud", + "//vendor:github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions", "//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/servers", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/types", diff --git a/pkg/cloudprovider/providers/openstack/openstack.go b/pkg/cloudprovider/providers/openstack/openstack.go index 438b7e97ea9..9183baf1348 100644 --- a/pkg/cloudprovider/providers/openstack/openstack.go +++ b/pkg/cloudprovider/providers/openstack/openstack.go @@ -24,11 +24,13 @@ import ( "io/ioutil" "net/http" "regexp" + "sort" "strings" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" + apiversions_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts" tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" @@ -89,7 +91,8 @@ type LoadBalancerOpts struct { } type BlockStorageOpts struct { - TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128 + BSVersion string `gcfg:"bs-version"` // overrides autodetection. v1 or v2. Defaults to auto + TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128 } type RouterOpts struct { @@ -173,6 +176,7 @@ func readConfig(config io.Reader) (Config, error) { var cfg Config // Set default values for config params + cfg.BlockStorage.BSVersion = "auto" cfg.BlockStorage.TrustDevicePath = false err := gcfg.ReadInto(&cfg, config) @@ -535,3 +539,111 @@ func (os *OpenStack) Routes() (cloudprovider.Routes, bool) { return r, true } + +// Implementation of sort interface for blockstorage version probing +type APIVersionsByID []apiversions_v1.APIVersion + +func (apiVersions APIVersionsByID) Len() int { + return len(apiVersions) +} + +func (apiVersions APIVersionsByID) Swap(i, j int) { + apiVersions[i], apiVersions[j] = apiVersions[j], apiVersions[i] +} + +func (apiVersions APIVersionsByID) Less(i, j int) bool { + return apiVersions[i].ID > apiVersions[j].ID +} + +func autoVersionSelector(apiVersion *apiversions_v1.APIVersion) string { + switch strings.ToLower(apiVersion.ID) { + case "v2.0": + return "v2" + case "v1.0": + return "v1" + default: + return "" + } +} + +func doBsApiVersionAutodetect(availableApiVersions []apiversions_v1.APIVersion) string { + sort.Sort(APIVersionsByID(availableApiVersions)) + for _, status := range []string{"CURRENT", "SUPPORTED"} { + for _, version := range availableApiVersions { + if strings.ToUpper(version.Status) == status { + if detectedApiVersion := autoVersionSelector(&version); detectedApiVersion != "" { + glog.V(3).Infof("Blockstorage API version probing has found a suitable %s api version: %s", status, detectedApiVersion) + return detectedApiVersion + } + } + } + } + + return "" + +} + +func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) { + bsVersion := "" + if forceVersion == "" { + bsVersion = os.bsOpts.BSVersion + } else { + bsVersion = forceVersion + } + + switch bsVersion { + case "v1": + sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return nil, err + } + return &VolumesV1{sClient, os.bsOpts}, nil + case "v2": + sClient, err := openstack.NewBlockStorageV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder v2 client for region: %s", os.region) + return nil, err + } + return &VolumesV2{sClient, os.bsOpts}, nil + case "auto": + sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return nil, err + } + availableApiVersions := []apiversions_v1.APIVersion{} + err = apiversions_v1.List(sClient).EachPage(func(page pagination.Page) (bool, error) { + // returning false from this handler stops page iteration, error is propagated to the upper function + apiversions, err := apiversions_v1.ExtractAPIVersions(page) + if err != nil { + glog.Errorf("Unable to extract api versions from page: %v", err) + return false, err + } + availableApiVersions = append(availableApiVersions, apiversions...) + return true, nil + }) + + if err != nil { + glog.Errorf("Error when retrieving list of supported blockstorage api versions: %v", err) + return nil, err + } + if autodetectedVersion := doBsApiVersionAutodetect(availableApiVersions); autodetectedVersion != "" { + return os.volumeService(autodetectedVersion) + } else { + // Nothing suitable found, failed autodetection + return nil, errors.New("BS API version autodetection failed.") + } + + default: + err_txt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion) + glog.Warningf(err_txt) + return nil, errors.New(err_txt) + } +} diff --git a/pkg/cloudprovider/providers/openstack/openstack_test.go b/pkg/cloudprovider/providers/openstack/openstack_test.go index 6916e6c43b8..2f91722b652 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_test.go +++ b/pkg/cloudprovider/providers/openstack/openstack_test.go @@ -17,6 +17,10 @@ limitations under the License. package openstack import ( + "fmt" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "os" "reflect" "sort" @@ -24,9 +28,6 @@ import ( "testing" "time" - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/kubernetes/pkg/api/v1" @@ -81,7 +82,9 @@ func TestReadConfig(t *testing.T) { monitor-timeout = 30s monitor-max-retries = 3 [BlockStorage] + bs-version = auto trust-device-path = yes + `)) if err != nil { t.Fatalf("Should succeed when a valid config is provided: %s", err) @@ -105,6 +108,9 @@ func TestReadConfig(t *testing.T) { if cfg.BlockStorage.TrustDevicePath != true { t.Errorf("incorrect bs.trustdevicepath: %v", cfg.BlockStorage.TrustDevicePath) } + if cfg.BlockStorage.BSVersion != "auto" { + t.Errorf("incorrect bs.bs-version: %v", cfg.BlockStorage.BSVersion) + } } func TestToAuthOptions(t *testing.T) { @@ -385,3 +391,45 @@ func TestVolumes(t *testing.T) { t.Logf("Volume (%s) deleted\n", vol) } + +func TestCinderAutoDetectApiVersion(t *testing.T) { + updated := "" // not relevant to this test, can be set to any value + status_current := "CURRENT" + status_supported := "SUPpORTED" // lowercase to test regression resitance if api returns different case + status_deprecated := "DEPRECATED" + + var result_version, api_version [4]string + + for ver := 0; ver <= 3; ver++ { + api_version[ver] = fmt.Sprintf("v%d.0", ver) + result_version[ver] = fmt.Sprintf("v%d", ver) + } + result_version[0] = "" + api_current_v1 := apiversions.APIVersion{ID: api_version[1], Status: status_current, Updated: updated} + api_current_v2 := apiversions.APIVersion{ID: api_version[2], Status: status_current, Updated: updated} + api_current_v3 := apiversions.APIVersion{ID: api_version[3], Status: status_current, Updated: updated} + + api_supported_v1 := apiversions.APIVersion{ID: api_version[1], Status: status_supported, Updated: updated} + api_supported_v2 := apiversions.APIVersion{ID: api_version[2], Status: status_supported, Updated: updated} + + api_deprecated_v1 := apiversions.APIVersion{ID: api_version[1], Status: status_deprecated, Updated: updated} + api_deprecated_v2 := apiversions.APIVersion{ID: api_version[2], Status: status_deprecated, Updated: updated} + + var testCases = []struct { + test_case []apiversions.APIVersion + wanted_result string + }{ + {[]apiversions.APIVersion{api_current_v1}, result_version[1]}, + {[]apiversions.APIVersion{api_current_v2}, result_version[2]}, + {[]apiversions.APIVersion{api_supported_v1, api_current_v2}, result_version[2]}, // current always selected + {[]apiversions.APIVersion{api_current_v1, api_supported_v2}, result_version[1]}, // current always selected + {[]apiversions.APIVersion{api_current_v3, api_supported_v2, api_deprecated_v1}, result_version[2]}, // with current v3, but should fall back to v2 + {[]apiversions.APIVersion{api_current_v3, api_deprecated_v2, api_deprecated_v1}, result_version[0]}, // v3 is not supported + } + + for _, suite := range testCases { + if autodetectedVersion := doBsApiVersionAutodetect(suite.test_case); autodetectedVersion != suite.wanted_result { + t.Fatalf("Autodetect for suite: %s, failed with result: '%s', wanted '%s'", suite.test_case, autodetectedVersion, suite.wanted_result) + } + } +} diff --git a/pkg/cloudprovider/providers/openstack/openstack_volumes.go b/pkg/cloudprovider/providers/openstack/openstack_volumes.go index 8fdcc35a4f5..659e4305ac6 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_volumes.go +++ b/pkg/cloudprovider/providers/openstack/openstack_volumes.go @@ -23,100 +23,96 @@ import ( "path" "strings" - "k8s.io/kubernetes/pkg/volume" + k8s_volume "k8s.io/kubernetes/pkg/volume" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" - "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" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/gophercloud/gophercloud/pagination" "github.com/golang/glog" ) -// Attaches given cinder volume to the compute running kubelet -func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) { - disk, err := os.getVolume(diskName) - if err != nil { - return "", err - } - cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil || cClient == nil { - glog.Errorf("Unable to initialize nova client for region: %s", os.region) - return "", err - } - - if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil { - if instanceID == disk.Attachments[0]["server_id"] { - glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID) - return disk.ID, nil - } - - glog.V(2).Infof("Disk %q is attached to a different compute (%q), detaching", diskName, disk.Attachments[0]["server_id"]) - err = os.DetachDisk(fmt.Sprintf("%s", disk.Attachments[0]["server_id"]), diskName) - if err != nil { - return "", err - } - } - - // add read only flag here if possible spothanis - _, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{ - VolumeID: disk.ID, - }).Extract() - if err != nil { - glog.Errorf("Failed to attach %s volume to %s compute: %v", diskName, instanceID, err) - return "", err - } - glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID) - return disk.ID, nil +type volumeService interface { + createVolume(opts VolumeCreateOpts) (string, error) + getVolume(diskName string) (Volume, error) + deleteVolume(volumeName string) error } -// Detaches given cinder volume from the compute running kubelet -func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error { - disk, err := os.getVolume(partialDiskId) - if err != nil { - return err - } - cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - if err != nil || cClient == nil { - glog.Errorf("Unable to initialize nova client for region: %s", os.region) - return err - } - if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] { - // This is a blocking call and effects kubelet's performance directly. - // We should consider kicking it out into a separate routine, if it is bad. - err = volumeattach.Delete(cClient, instanceID, disk.ID).ExtractErr() - if err != nil { - glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, instanceID, err) - return err - } - glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, instanceID) - } else { - errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, instanceID) - glog.Errorf(errMsg) - return errors.New(errMsg) - } - return nil +// Volumes implementation for v1 +type VolumesV1 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts } -// Takes a partial/full disk id or diskname -func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) { - sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) +// Volumes implementation for v2 +type VolumesV2 struct { + blockstorage *gophercloud.ServiceClient + opts BlockStorageOpts +} - var volume volumes.Volume - if err != nil || sClient == nil { - glog.Errorf("Unable to initialize cinder client for region: %s", os.region) - return volume, err +type Volume struct { + // ID of the instance, to which this volume is attached. "" if not attached + AttachedServerId string + // Device file path + AttachedDevice string + // Unique identifier for the volume. + ID string + // Human-readable display name for the volume. + Name string + // Current status of the volume. + Status string +} + +type VolumeCreateOpts struct { + Size int + Availability string + Name string + VolumeType string + Metadata map[string]string +} + +func (volumes *VolumesV1) createVolume(opts VolumeCreateOpts) (string, error) { + + create_opts := volumes_v1.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + Availability: opts.Availability, + Metadata: opts.Metadata, } - err = volumes.List(sClient, nil).EachPage(func(page pagination.Page) (bool, error) { - vols, err := volumes.ExtractVolumes(page) + vol, err := volumes_v1.Create(volumes.blockstorage, create_opts).Extract() + if err != nil { + return "", err + } + return vol.ID, nil +} + +func (volumes *VolumesV2) createVolume(opts VolumeCreateOpts) (string, error) { + + create_opts := volumes_v2.CreateOpts{ + Name: opts.Name, + Size: opts.Size, + VolumeType: opts.VolumeType, + AvailabilityZone: opts.Availability, + Metadata: opts.Metadata, + } + + vol, err := volumes_v2.Create(volumes.blockstorage, create_opts).Extract() + if err != nil { + return "", err + } + return vol.ID, nil +} + +func (volumes *VolumesV1) getVolume(diskName string) (Volume, error) { + var volume_v1 volumes_v1.Volume + var volume Volume + err := volumes_v1.List(volumes.blockstorage, nil).EachPage(func(page pagination.Page) (bool, error) { + vols, err := volumes_v1.ExtractVolumes(page) if err != nil { glog.Errorf("Failed to extract volumes: %v", err) return false, err @@ -124,35 +120,177 @@ func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) { for _, v := range vols { glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments) if v.Name == diskName || strings.Contains(v.ID, diskName) { - volume = v + volume_v1 = v return true, nil } } } // if it reached here then no disk with the given name was found. - errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, os.region) + errmsg := fmt.Sprintf("Unable to find disk: %s", diskName) return false, errors.New(errmsg) }) if err != nil { glog.Errorf("Error occurred getting volume: %s", diskName) return volume, err } - return volume, err + + volume.ID = volume_v1.ID + volume.Name = volume_v1.Name + volume.Status = volume_v1.Status + + if len(volume_v1.Attachments) > 0 && volume_v1.Attachments[0]["server_id"] != nil { + volume.AttachedServerId = volume_v1.Attachments[0]["server_id"].(string) + volume.AttachedDevice = volume_v1.Attachments[0]["device"].(string) + } + + return volume, nil +} + +func (volumes *VolumesV2) getVolume(diskName string) (Volume, error) { + var volume_v2 volumes_v2.Volume + var volume Volume + err := volumes_v2.List(volumes.blockstorage, nil).EachPage(func(page pagination.Page) (bool, error) { + vols, err := volumes_v2.ExtractVolumes(page) + if err != nil { + glog.Errorf("Failed to extract volumes: %v", err) + return false, err + } else { + for _, v := range vols { + glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments) + if v.Name == diskName || strings.Contains(v.ID, diskName) { + volume_v2 = v + return true, nil + } + } + } + // if it reached here then no disk with the given name was found. + errmsg := fmt.Sprintf("Unable to find disk: %s", diskName) + return false, errors.New(errmsg) + }) + if err != nil { + glog.Errorf("Error occurred getting volume: %s", diskName) + return volume, err + } + + volume.ID = volume_v2.ID + volume.Name = volume_v2.Name + volume.Status = volume_v2.Status + + if len(volume_v2.Attachments) > 0 { + volume.AttachedServerId = volume_v2.Attachments[0].ServerID + volume.AttachedDevice = volume_v2.Attachments[0].Device + } + + return volume, nil +} + +func (volumes *VolumesV1) deleteVolume(volumeName string) error { + + err := volumes_v1.Delete(volumes.blockstorage, volumeName).ExtractErr() + if err != nil { + glog.Errorf("Cannot delete volume %s: %v", volumeName, err) + } + return err +} + +func (volumes *VolumesV2) deleteVolume(volumeName string) error { + err := volumes_v2.Delete(volumes.blockstorage, volumeName).ExtractErr() + if err != nil { + glog.Errorf("Cannot delete volume %s: %v", volumeName, err) + } + return err +} + +// Attaches given cinder volume to the compute running kubelet +func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) { + volume, err := os.getVolume(diskName) + if err != nil { + return "", err + } + cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || cClient == nil { + glog.Errorf("Unable to initialize nova client for region: %s", os.region) + return "", err + } + + if volume.AttachedServerId != "" { + if instanceID == volume.AttachedServerId { + glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID) + return volume.ID, nil + } + glog.V(2).Infof("Disk %q is attached to a different compute (%q), detaching", diskName, volume.AttachedServerId) + err = os.DetachDisk(volume.AttachedServerId, diskName) + if err != nil { + return "", err + } + } + + // add read only flag here if possible spothanis + _, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{ + VolumeID: volume.ID, + }).Extract() + if err != nil { + glog.Errorf("Failed to attach %s volume to %s compute: %v", diskName, instanceID, err) + return "", err + } + glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID) + return volume.ID, nil +} + +// Detaches given cinder volume from the compute running kubelet +func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error { + volume, err := os.getVolume(partialDiskId) + if err != nil { + return err + } + cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || cClient == nil { + glog.Errorf("Unable to initialize nova client for region: %s", os.region) + return err + } + if volume.AttachedServerId != instanceID { + errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID) + glog.Errorf(errMsg) + return errors.New(errMsg) + } else { + // This is a blocking call and effects kubelet's performance directly. + // We should consider kicking it out into a separate routine, if it is bad. + err = volumeattach.Delete(cClient, instanceID, volume.ID).ExtractErr() + if err != nil { + glog.Errorf("Failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err) + return err + } + glog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID) + } + + return nil +} + +// Takes a partial/full disk id or diskname +func (os *OpenStack) getVolume(diskName string) (Volume, error) { + + volumes, err := os.volumeService("") + if err != nil || volumes == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return Volume{}, err + } + + return volumes.getVolume(diskName) } // Create a volume of given size (in GiB) func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (volumeName string, err error) { - sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - - if err != nil || sClient == nil { + volumes, err := os.volumeService("") + if err != nil || volumes == nil { glog.Errorf("Unable to initialize cinder client for region: %s", os.region) return "", err } - - opts := volumes.CreateOpts{ + opts := VolumeCreateOpts{ Name: name, Size: size, VolumeType: vtype, @@ -161,13 +299,15 @@ func (os *OpenStack) CreateVolume(name string, size int, vtype, availability str if tags != nil { opts.Metadata = *tags } - vol, err := volumes.Create(sClient, opts).Extract() + volume_id, err := volumes.createVolume(opts) + if err != nil { glog.Errorf("Failed to create a %d GB volume: %v", size, err) return "", err } - glog.Infof("Created volume %v", vol.ID) - return vol.ID, err + + glog.Infof("Created volume %v", volume_id) + return volume_id, nil } // GetDevicePath returns the path of an attached block storage volume, specified by its id. @@ -202,39 +342,38 @@ func (os *OpenStack) DeleteVolume(volumeName string) error { } if used { msg := fmt.Sprintf("Cannot delete the volume %q, it's still attached to a node", volumeName) - return volume.NewDeletedVolumeInUseError(msg) + return k8s_volume.NewDeletedVolumeInUseError(msg) } - sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ - Region: os.region, - }) - - if err != nil || sClient == nil { + volumes, err := os.volumeService("") + if err != nil || volumes == nil { glog.Errorf("Unable to initialize cinder client for region: %s", os.region) return err } - err = volumes.Delete(sClient, volumeName).ExtractErr() + + err = volumes.deleteVolume(volumeName) if err != nil { glog.Errorf("Cannot delete volume %s: %v", volumeName, err) } - return err + return nil + } // Get device path of attached volume to the compute running kubelet, as known by cinder func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) { // See issue #33128 - Cinder does not always tell you the right device path, as such // we must only use this value as a last resort. - disk, err := os.getVolume(diskName) + volume, err := os.getVolume(diskName) if err != nil { return "", err } - if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil { - if instanceID == disk.Attachments[0]["server_id"] { + if volume.AttachedServerId != "" { + if instanceID == volume.AttachedServerId { // Attachment[0]["device"] points to the device path // see http://developer.openstack.org/api-ref-blockstorage-v1.html - return disk.Attachments[0]["device"].(string), nil + return volume.AttachedDevice, nil } else { - errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"]) + errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, volume.AttachedServerId) glog.Errorf(errMsg) return "", errors.New(errMsg) } @@ -244,11 +383,12 @@ func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) ( // query if a volume is attached to a compute instance func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) { - disk, err := os.getVolume(diskName) + volume, err := os.getVolume(diskName) if err != nil { return false, err } - if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] { + + if instanceID == volume.AttachedServerId { return true, nil } return false, nil @@ -258,27 +398,19 @@ func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) { func (os *OpenStack) DisksAreAttached(diskNames []string, instanceID string) (map[string]bool, error) { attached := make(map[string]bool) for _, diskName := range diskNames { - attached[diskName] = false - } - for _, diskName := range diskNames { - disk, err := os.getVolume(diskName) - if err != nil { - continue - } - if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] { - attached[diskName] = true - } + is_attached, _ := os.DiskIsAttached(diskName, instanceID) + attached[diskName] = is_attached } return attached, nil } // diskIsUsed returns true a disk is attached to any node. func (os *OpenStack) diskIsUsed(diskName string) (bool, error) { - disk, err := os.getVolume(diskName) + volume, err := os.getVolume(diskName) if err != nil { return false, err } - if len(disk.Attachments) > 0 { + if volume.AttachedServerId != "" { return true, nil } return false, nil From 89f596f408d26b8866b7edd4ec0230895abb0b1b Mon Sep 17 00:00:00 2001 From: Maxym Kutsevol Date: Sat, 11 Mar 2017 00:01:56 +0200 Subject: [PATCH 2/2] Update deps --- Godeps/Godeps.json | 10 +- Godeps/LICENSES | 398 ++++++++++++++++++ pkg/cloudprovider/providers/openstack/BUILD | 2 + vendor/BUILD | 31 ++ .../blockstorage/v1/apiversions/doc.go | 3 + .../blockstorage/v1/apiversions/requests.go | 20 + .../blockstorage/v1/apiversions/results.go | 49 +++ .../blockstorage/v1/apiversions/urls.go | 18 + .../openstack/blockstorage/v2/volumes/doc.go | 5 + .../blockstorage/v2/volumes/requests.go | 182 ++++++++ .../blockstorage/v2/volumes/results.go | 121 ++++++ .../openstack/blockstorage/v2/volumes/urls.go | 23 + .../openstack/blockstorage/v2/volumes/util.go | 22 + 13 files changed, 883 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7472b1a894b..0f6a9af0cbf 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "k8s.io/kubernetes", "GoVersion": "go1.7", - "GodepVersion": "v79", + "GodepVersion": "v74", "Packages": [ "github.com/ugorji/go/codec/codecgen", "github.com/onsi/ginkgo/ginkgo", @@ -1416,10 +1416,18 @@ "ImportPath": "github.com/gophercloud/gophercloud/openstack", "Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791" }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions", + "Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791" + }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", "Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791" }, + { + "ImportPath": "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", + "Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791" + }, { "ImportPath": "github.com/gophercloud/gophercloud/openstack/common/extensions", "Rev": "12f19e5e04d617182cffa5c11f189ef0013b9791" diff --git a/Godeps/LICENSES b/Godeps/LICENSES index 947bd7e5f03..bc3975fc113 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -47689,6 +47689,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions licensed under: = + +Copyright 2012-2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 - +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes licensed under: = @@ -47888,6 +48087,205 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes licensed under: = + +Copyright 2012-2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + += vendor/github.com/gophercloud/gophercloud/LICENSE dd19699707373c2ca31531a659130416 - +================================================================================ + + ================================================================================ = vendor/github.com/gophercloud/gophercloud/openstack/common/extensions licensed under: = diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index 0cc537dcd00..4c31301f9b6 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -29,7 +29,9 @@ go_library( "//vendor:github.com/golang/glog", "//vendor:github.com/gophercloud/gophercloud", "//vendor:github.com/gophercloud/gophercloud/openstack", + "//vendor:github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions", "//vendor:github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", + "//vendor:github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", "//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach", "//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", "//vendor:github.com/gophercloud/gophercloud/openstack/compute/v2/servers", diff --git a/vendor/BUILD b/vendor/BUILD index 811973ee8ff..902bf67eeaf 100644 --- a/vendor/BUILD +++ b/vendor/BUILD @@ -4157,6 +4157,21 @@ go_library( ], ) +go_library( + name = "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions", + srcs = [ + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/doc.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/requests.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/results.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor:github.com/gophercloud/gophercloud", + "//vendor:github.com/gophercloud/gophercloud/pagination", + ], +) + go_library( name = "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes", srcs = [ @@ -4173,6 +4188,22 @@ go_library( ], ) +go_library( + name = "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes", + srcs = [ + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go", + "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor:github.com/gophercloud/gophercloud", + "//vendor:github.com/gophercloud/gophercloud/pagination", + ], +) + go_library( name = "github.com/gophercloud/gophercloud/openstack/common/extensions", srcs = [ diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/doc.go new file mode 100644 index 00000000000..e3af39f513a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/doc.go @@ -0,0 +1,3 @@ +// Package apiversions provides information and interaction with the different +// API versions for the OpenStack Block Storage service, code-named Cinder. +package apiversions diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/requests.go new file mode 100644 index 00000000000..725c13a7615 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/requests.go @@ -0,0 +1,20 @@ +package apiversions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// List lists all the Cinder API versions available to end-users. +func List(c *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page { + return APIVersionPage{pagination.SinglePageBase(r)} + }) +} + +// Get will retrieve the volume type with the provided ID. To extract the volume +// type from the result, call the Extract method on the GetResult. +func Get(client *gophercloud.ServiceClient, v string) (r GetResult) { + _, r.Err = client.Get(getURL(client, v), &r.Body, nil) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/results.go new file mode 100644 index 00000000000..f510c6d103a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/results.go @@ -0,0 +1,49 @@ +package apiversions + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// APIVersion represents an API version for Cinder. +type APIVersion struct { + ID string `json:"id"` // unique identifier + Status string `json:"status"` // current status + Updated string `json:"updated"` // date last updated +} + +// APIVersionPage is the page returned by a pager when traversing over a +// collection of API versions. +type APIVersionPage struct { + pagination.SinglePageBase +} + +// IsEmpty checks whether an APIVersionPage struct is empty. +func (r APIVersionPage) IsEmpty() (bool, error) { + is, err := ExtractAPIVersions(r) + return len(is) == 0, err +} + +// ExtractAPIVersions takes a collection page, extracts all of the elements, +// and returns them a slice of APIVersion structs. It is effectively a cast. +func ExtractAPIVersions(r pagination.Page) ([]APIVersion, error) { + var s struct { + Versions []APIVersion `json:"versions"` + } + err := (r.(APIVersionPage)).ExtractInto(&s) + return s.Versions, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts an API version resource. +func (r GetResult) Extract() (*APIVersion, error) { + var s struct { + Version *APIVersion `json:"version"` + } + err := r.ExtractInto(&s) + return s.Version, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go new file mode 100644 index 00000000000..c9cf895b133 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions/urls.go @@ -0,0 +1,18 @@ +package apiversions + +import ( + "strings" + "net/url" + + "github.com/gophercloud/gophercloud" +) + +func getURL(c *gophercloud.ServiceClient, version string) string { + return c.ServiceURL(strings.TrimRight(version, "/") + "/") +} + +func listURL(c *gophercloud.ServiceClient) string { + u, _ := url.Parse(c.ServiceURL("")) + u.Path = "/" + return u.String() +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go new file mode 100644 index 00000000000..307b8b12d2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/doc.go @@ -0,0 +1,5 @@ +// Package volumes 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. It can only be attached to one instance at +// a time. +package volumes diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go new file mode 100644 index 00000000000..18c9cb272ec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/requests.go @@ -0,0 +1,182 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Volume. This object is passed to +// the volumes.Create function. For more information about these parameters, +// see the Volume object. +type CreateOpts struct { + // The size of the volume, in GB + Size int `json:"size" required:"true"` + // The availability zone + AvailabilityZone string `json:"availability_zone,omitempty"` + // ConsistencyGroupID is the ID of a consistency group + ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` + // The volume description + Description string `json:"description,omitempty"` + // One or more metadata key and value pairs to associate with the volume + Metadata map[string]string `json:"metadata,omitempty"` + // The volume name + Name string `json:"name,omitempty"` + // the ID of the existing volume snapshot + SnapshotID string `json:"snapshot_id,omitempty"` + // SourceReplica is a UUID of an existing volume to replicate with + SourceReplica string `json:"source_replica,omitempty"` + // the ID of the existing volume + SourceVolID string `json:"source_volid,omitempty"` + // The ID of the image from which you want to create the volume. + // Required to create a bootable volume. + ImageID string `json:"imageRef,omitempty"` + // The associated volume type + VolumeType string `json:"volume_type,omitempty"` +} + +// ToVolumeCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Create will create a new Volume based on the values in CreateOpts. To extract +// the Volume object from the response, call the Extract method on the +// CreateResult. +func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToVolumeCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + return +} + +// Delete will delete the existing Volume with the provided ID. +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, id), nil) + return +} + +// Get retrieves the Volume with the provided ID. To extract the Volume object +// from the response, call the Extract method on the GetResult. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + return +} + +// ListOptsBuilder allows extensions to add additional parameters to the List +// request. +type ListOptsBuilder interface { + ToVolumeListQuery() (string, error) +} + +// ListOpts holds options for listing Volumes. It is passed to the volumes.List +// function. +type ListOpts struct { + // admin-only option. Set it to true to see all tenant volumes. + AllTenants bool `q:"all_tenants"` + // List only volumes that contain Metadata. + Metadata map[string]string `q:"metadata"` + // List only volumes that have Name as the display name. + Name string `q:"name"` + // List only volumes that have a status of Status. + Status string `q:"status"` +} + +// ToVolumeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToVolumeListQuery() (string, error) { + q, err := gophercloud.BuildQueryString(opts) + return q.String(), err +} + +// List returns Volumes optionally limited by the conditions provided in ListOpts. +func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToVolumeListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return VolumePage{pagination.SinglePageBase(r)} + }) +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToVolumeUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contain options for updating an existing Volume. This object is passed +// to the volumes.Update function. For more information about the parameters, see +// the Volume object. +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ToVolumeUpdateMap assembles a request body based on the contents of an +// UpdateOpts. +func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "volume") +} + +// Update will update the Volume with provided information. To extract the updated +// Volume from the response, call the Extract method on the UpdateResult. +func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToVolumeUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a server's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + count := 0 + id := "" + pages, err := List(client, nil).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractVolumes(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} + case 1: + return id, nil + default: + return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go new file mode 100644 index 00000000000..2ad94cd9a72 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/results.go @@ -0,0 +1,121 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/pagination" +) + +type Attachment struct { + AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"` + AttachmentID string `json:"attachment_id"` + Device string `json:"device"` + HostName string `json:"host_name"` + ID string `json:"id"` + ServerID string `json:"server_id"` + VolumeID string `json:"volume_id"` +} + +// Volume contains all the information associated with an OpenStack Volume. +type Volume struct { + // Unique identifier for the volume. + ID string `json:"id"` + // Current status of the volume. + Status string `json:"status"` + // Size of the volume in GB. + Size int `json:"size"` + // AvailabilityZone is which availability zone the volume is in. + AvailabilityZone string `json:"availability_zone"` + // The date when this volume was created. + CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"` + // The date when this volume was last updated + UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"` + // Instances onto which the volume is attached. + Attachments []Attachment `json:"attachments"` + // Human-readable display name for the volume. + Name string `json:"name"` + // Human-readable description for the volume. + Description string `json:"description"` + // The type of volume to create, either SATA or SSD. + VolumeType string `json:"volume_type"` + // The ID of the snapshot from which the volume was created + SnapshotID string `json:"snapshot_id"` + // The ID of another block storage volume from which the current volume was created + SourceVolID string `json:"source_volid"` + // Arbitrary key-value pairs defined by the user. + Metadata map[string]string `json:"metadata"` + // UserID is the id of the user who created the volume. + UserID string `json:"user_id"` + // Indicates whether this is a bootable volume. + Bootable string `json:"bootable"` + // Encrypted denotes if the volume is encrypted. + Encrypted bool `json:"encrypted"` + // ReplicationStatus is the status of replication. + ReplicationStatus string `json:"replication_status"` + // ConsistencyGroupID is the consistency group ID. + ConsistencyGroupID string `json:"consistencygroup_id"` + // Multiattach denotes if the volume is multi-attach capable. + Multiattach bool `json:"multiattach"` +} + +/* +THESE BELONG IN EXTENSIONS: +// ReplicationDriverData contains data about the replication driver. +ReplicationDriverData string `json:"os-volume-replication:driver_data"` +// ReplicationExtendedStatus contains extended status about replication. +ReplicationExtendedStatus string `json:"os-volume-replication:extended_status"` +// TenantID is the id of the project that owns the volume. +TenantID string `json:"os-vol-tenant-attr:tenant_id"` +*/ + +// VolumePage is a pagination.pager that is returned from a call to the List function. +type VolumePage struct { + pagination.SinglePageBase +} + +// IsEmpty returns true if a ListResult contains no Volumes. +func (r VolumePage) IsEmpty() (bool, error) { + volumes, err := ExtractVolumes(r) + return len(volumes) == 0, err +} + +// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. +func ExtractVolumes(r pagination.Page) ([]Volume, error) { + var s struct { + Volumes []Volume `json:"volumes"` + } + err := (r.(VolumePage)).ExtractInto(&s) + return s.Volumes, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract will get the Volume object out of the commonResult object. +func (r commonResult) Extract() (*Volume, error) { + var s struct { + Volume *Volume `json:"volume"` + } + err := r.ExtractInto(&s) + return s.Volume, err +} + +// CreateResult contains the response body and error from a Create request. +type CreateResult struct { + commonResult +} + +// GetResult contains the response body and error from a Get request. +type GetResult struct { + commonResult +} + +// UpdateResult contains the response body and error from an Update request. +type UpdateResult struct { + commonResult +} + +// DeleteResult contains the response body and error from a Delete request. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go new file mode 100644 index 00000000000..170724905ab --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/urls.go @@ -0,0 +1,23 @@ +package volumes + +import "github.com/gophercloud/gophercloud" + +func createURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes") +} + +func listURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("volumes", "detail") +} + +func deleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("volumes", id) +} + +func getURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} + +func updateURL(c *gophercloud.ServiceClient, id string) string { + return deleteURL(c, id) +} diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go new file mode 100644 index 00000000000..e86c1b4b4ee --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes/util.go @@ -0,0 +1,22 @@ +package volumes + +import ( + "github.com/gophercloud/gophercloud" +) + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error { + return gophercloud.WaitFor(secs, func() (bool, error) { + current, err := Get(c, id).Extract() + if err != nil { + return false, err + } + + if current.Status == status { + return true, nil + } + + return false, nil + }) +}