Fix the version detection of OpenStack Cinder

When running Kubernetes against an installation of DevStack which
deploys the Cinder service at a path rather than a port (ex:
http://foo.bar/volume rather than http://foo.bar:xxx), the version
detection fails. It is better to use the OpenStack service catalog.
OTOH, when initialize cinder client, kubernetes will check the
endpoint from the OpenStack service catalog, so we can do this
version detection by it.
This commit is contained in:
FengyunPan 2017-09-26 21:56:17 +08:00
parent b188868fd9
commit b35aa85560
3 changed files with 17 additions and 114 deletions

View File

@ -28,7 +28,6 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces:go_default_library",
@ -74,7 +73,6 @@ go_test(
deps = [
"//pkg/cloudprovider:go_default_library",
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -24,13 +24,11 @@ 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/extensions/attachinterfaces"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts"
@ -644,49 +642,6 @@ 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 == "" {
@ -701,43 +656,36 @@ func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) {
if err != nil {
return nil, err
}
glog.V(3).Infof("Using Blockstorage API V1")
return &VolumesV1{sClient, os.bsOpts}, nil
case "v2":
sClient, err := os.NewBlockStorageV2()
if err != nil {
return nil, err
}
glog.V(3).Infof("Using Blockstorage API V2")
return &VolumesV2{sClient, os.bsOpts}, nil
case "auto":
sClient, err := os.NewBlockStorageV1()
// Currently kubernetes just support Cinder v1 and Cinder v2.
// Choose Cinder v2 firstly, if kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client.
// Return appropriate message when kubernetes can't initialize them.
// TODO(FengyunPan): revisit 'auto' after supporting Cinder v3.
sClient, err := os.NewBlockStorageV2()
if err != nil {
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)
sClient, err = os.NewBlockStorageV1()
if err != nil {
glog.Errorf("Unable to extract api versions from page: %v", err)
return false, err
// Nothing suitable found, failed autodetection, just exit with appropriate message
err_txt := "BlockStorage API version autodetection failed. " +
"Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`"
return nil, errors.New(err_txt)
} else {
glog.V(3).Infof("Using Blockstorage API V1")
return &VolumesV1{sClient, os.bsOpts}, nil
}
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, just exit with appropriate message
err_txt := "BlockStorage API version autodetection failed. " +
"Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`"
return nil, errors.New(err_txt)
glog.V(3).Infof("Using Blockstorage API V2")
return &VolumesV2{sClient, os.bsOpts}, nil
}
default:
err_txt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion)
glog.Warningf(err_txt)

View File

@ -26,7 +26,6 @@ import (
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/apiversions"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"k8s.io/api/core/v1"
@ -508,48 +507,6 @@ func TestVolumes(t *testing.T) {
}
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)
}
}
}
func TestInstanceIDFromProviderID(t *testing.T) {
testCases := []struct {
providerID string