Commit 7df59f75 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #29726 from anguslees/lb-autodetect

Automatic merge from submit-queue openstack: Autodetect LBaaS v1 vs v2 ```release-note * openstack: autodetect LBaaS v1/v2 by querying for available extensions. For most installs, this effectively changes the default from v1 to v2. Existing installs can add "lb-version = v1" to the provider config file to continue to use v1. ``` <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.kubernetes.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.kubernetes.io/reviews/kubernetes/kubernetes/29726) <!-- Reviewable:end -->
parents 9fe15e73 e4c354c3
...@@ -1796,6 +1796,11 @@ ...@@ -1796,6 +1796,11 @@
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/common/extensions",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume", "ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume",
"Comment": "v1.0.0-920-g934dbf8", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
...@@ -1841,6 +1846,11 @@ ...@@ -1841,6 +1846,11 @@
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
}, },
{ {
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions",
"Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips", "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Comment": "v1.0.0-920-g934dbf8", "Comment": "v1.0.0-920-g934dbf8",
"Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04"
......
...@@ -78,7 +78,7 @@ type LoadBalancer struct { ...@@ -78,7 +78,7 @@ type LoadBalancer struct {
} }
type LoadBalancerOpts struct { type LoadBalancerOpts struct {
LBVersion string `gcfg:"lb-version"` // v1 or v2 LBVersion string `gcfg:"lb-version"` // overrides autodetection. v1 or v2
SubnetId string `gcfg:"subnet-id"` // required SubnetId string `gcfg:"subnet-id"` // required
FloatingNetworkId string `gcfg:"floating-network-id"` FloatingNetworkId string `gcfg:"floating-network-id"`
LBMethod string `gcfg:"lb-method"` LBMethod string `gcfg:"lb-method"`
...@@ -390,13 +390,35 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { ...@@ -390,13 +390,35 @@ func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
return nil, false return nil, false
} }
lbversion := os.lbOpts.LBVersion
if lbversion == "" {
// No version specified, try newest supported by server
netExts, err := networkExtensions(network)
if err != nil {
glog.Warningf("Failed to list neutron extensions: %v", err)
return nil, false
}
if netExts["lbaasv2"] {
lbversion = "v2"
} else if netExts["lbaas"] {
lbversion = "v1"
} else {
glog.Warningf("Failed to find neutron LBaaS extension (v1 or v2)")
return nil, false
}
glog.V(3).Infof("Using LBaaS extension %v", lbversion)
}
glog.V(1).Info("Claiming to support LoadBalancer") glog.V(1).Info("Claiming to support LoadBalancer")
if os.lbOpts.LBVersion == "v2" { if os.lbOpts.LBVersion == "v2" {
return &LbaasV2{LoadBalancer{network, compute, os.lbOpts}}, true return &LbaasV2{LoadBalancer{network, compute, os.lbOpts}}, true
} else { } else if lbversion == "v1" {
return &LbaasV1{LoadBalancer{network, compute, os.lbOpts}}, true return &LbaasV1{LoadBalancer{network, compute, os.lbOpts}}, true
} else {
glog.Warningf("Config error: unrecognised lb-version \"%v\"", lbversion)
return nil, false
} }
} }
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"time" "time"
"github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
...@@ -55,6 +56,24 @@ type LbaasV2 struct { ...@@ -55,6 +56,24 @@ type LbaasV2 struct {
LoadBalancer LoadBalancer
} }
func networkExtensions(client *gophercloud.ServiceClient) (map[string]bool, error) {
seen := make(map[string]bool)
pager := extensions.List(client)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
exts, err := extensions.ExtractExtensions(page)
if err != nil {
return false, err
}
for _, ext := range exts {
seen[ext.Alias] = true
}
return true, nil
})
return seen, err
}
func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, error) { func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, error) {
var portID string var portID string
......
...@@ -205,50 +205,29 @@ func TestLoadBalancer(t *testing.T) { ...@@ -205,50 +205,29 @@ func TestLoadBalancer(t *testing.T) {
t.Skipf("No config found in environment") t.Skipf("No config found in environment")
} }
cfg.LoadBalancer.LBVersion = "v1" versions := []string{"v1", "v2", ""}
os, err := newOpenStack(cfg) for _, v := range versions {
if err != nil { t.Logf("Trying LBVersion = '%s'\n", v)
t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) cfg.LoadBalancer.LBVersion = v
}
lb, ok := os.LoadBalancer()
if !ok {
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?")
}
_, exists, err := lb.GetLoadBalancer(testClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
if err != nil {
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
}
if exists {
t.Fatalf("GetLoadBalancer(\"noexist\") returned exists")
}
}
func TestLoadBalancerV2(t *testing.T) { os, err := newOpenStack(cfg)
cfg, ok := configFromEnv() if err != nil {
if !ok { t.Fatalf("Failed to construct/authenticate OpenStack: %s", err)
t.Skipf("No config found in environment") }
}
cfg.LoadBalancer.LBVersion = "v2"
os, err := newOpenStack(cfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate OpenStack: %s", err)
}
lbaas, ok := os.LoadBalancer() lb, ok := os.LoadBalancer()
if !ok { if !ok {
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?")
} }
_, exists, err := lbaas.GetLoadBalancer(testClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}}) _, exists, err := lb.GetLoadBalancer(testClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
if err != nil { if err != nil {
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
} }
if exists { if exists {
t.Fatalf("GetLoadBalancer(\"noexist\") returned exists") t.Fatalf("GetLoadBalancer(\"noexist\") returned exists")
}
} }
} }
......
// Package extensions provides information and interaction with the different extensions available
// for an OpenStack service.
//
// The purpose of OpenStack API extensions is to:
//
// - Introduce new features in the API without requiring a version change.
// - Introduce vendor-specific niche functionality.
// - Act as a proving ground for experimental functionalities that might be included in a future
// version of the API.
//
// Extensions usually have tags that prevent conflicts with other extensions that define attributes
// or resources with the same names, and with core resources and attributes.
// Because an extension might not be supported by all plug-ins, its availability varies with deployments
// and the specific plug-in.
package extensions
// +build fixtures
package extensions
import (
"fmt"
"net/http"
"testing"
th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)
// ListOutput provides a single page of Extension results.
const ListOutput = `
{
"extensions": [
{
"updated": "2013-01-20T00:00:00-00:00",
"name": "Neutron Service Type Management",
"links": [],
"namespace": "http://docs.openstack.org/ext/neutron/service-type/api/v1.0",
"alias": "service-type",
"description": "API for retrieving service providers for Neutron advanced services"
}
]
}`
// GetOutput provides a single Extension result.
const GetOutput = `
{
"extension": {
"updated": "2013-02-03T10:00:00-00:00",
"name": "agent",
"links": [],
"namespace": "http://docs.openstack.org/ext/agent/api/v2.0",
"alias": "agent",
"description": "The agent management extension."
}
}
`
// ListedExtension is the Extension that should be parsed from ListOutput.
var ListedExtension = Extension{
Updated: "2013-01-20T00:00:00-00:00",
Name: "Neutron Service Type Management",
Links: []interface{}{},
Namespace: "http://docs.openstack.org/ext/neutron/service-type/api/v1.0",
Alias: "service-type",
Description: "API for retrieving service providers for Neutron advanced services",
}
// ExpectedExtensions is a slice containing the Extension that should be parsed from ListOutput.
var ExpectedExtensions = []Extension{ListedExtension}
// SingleExtension is the Extension that should be parsed from GetOutput.
var SingleExtension = &Extension{
Updated: "2013-02-03T10:00:00-00:00",
Name: "agent",
Links: []interface{}{},
Namespace: "http://docs.openstack.org/ext/agent/api/v2.0",
Alias: "agent",
Description: "The agent management extension.",
}
// HandleListExtensionsSuccessfully creates an HTTP handler at `/extensions` on the test handler
// mux that response with a list containing a single tenant.
func HandleListExtensionsSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w, ListOutput)
})
}
// HandleGetExtensionSuccessfully creates an HTTP handler at `/extensions/agent` that responds with
// a JSON payload corresponding to SingleExtension.
func HandleGetExtensionSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, GetOutput)
})
}
package extensions
import (
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) GetResult {
var res GetResult
_, res.Err = c.Get(ExtensionURL(c, alias), &res.Body, nil)
return res
}
// List returns a Pager which allows you to iterate over the full collection of extensions.
// It does not accept query parameters.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(c, ListExtensionURL(c), func(r pagination.PageResult) pagination.Page {
return ExtensionPage{pagination.SinglePageBase(r)}
})
}
package extensions
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/pagination"
)
// GetResult temporarily stores the result of a Get call.
// Use its Extract() method to interpret it as an Extension.
type GetResult struct {
gophercloud.Result
}
// Extract interprets a GetResult as an Extension.
func (r GetResult) Extract() (*Extension, error) {
if r.Err != nil {
return nil, r.Err
}
var res struct {
Extension *Extension `json:"extension"`
}
err := mapstructure.Decode(r.Body, &res)
return res.Extension, err
}
// Extension is a struct that represents an OpenStack extension.
type Extension struct {
Updated string `json:"updated" mapstructure:"updated"`
Name string `json:"name" mapstructure:"name"`
Links []interface{} `json:"links" mapstructure:"links"`
Namespace string `json:"namespace" mapstructure:"namespace"`
Alias string `json:"alias" mapstructure:"alias"`
Description string `json:"description" mapstructure:"description"`
}
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
type ExtensionPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an ExtensionPage struct is empty.
func (r ExtensionPage) IsEmpty() (bool, error) {
is, err := ExtractExtensions(r)
if err != nil {
return true, err
}
return len(is) == 0, nil
}
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
// elements into a slice of Extension structs.
// In other words, a generic collection is mapped into a relevant slice.
func ExtractExtensions(page pagination.Page) ([]Extension, error) {
var resp struct {
Extensions []Extension `mapstructure:"extensions"`
}
err := mapstructure.Decode(page.(ExtensionPage).Body, &resp)
return resp.Extensions, err
}
package extensions
import "github.com/rackspace/gophercloud"
// ExtensionURL generates the URL for an extension resource by name.
func ExtensionURL(c *gophercloud.ServiceClient, name string) string {
return c.ServiceURL("extensions", name)
}
// ListExtensionURL generates the URL for the extensions resource collection.
func ListExtensionURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("extensions")
}
package extensions
import (
"github.com/rackspace/gophercloud"
common "github.com/rackspace/gophercloud/openstack/common/extensions"
"github.com/rackspace/gophercloud/pagination"
)
// Extension is a single OpenStack extension.
type Extension struct {
common.Extension
}
// GetResult wraps a GetResult from common.
type GetResult struct {
common.GetResult
}
// ExtractExtensions interprets a Page as a slice of Extensions.
func ExtractExtensions(page pagination.Page) ([]Extension, error) {
inner, err := common.ExtractExtensions(page)
if err != nil {
return nil, err
}
outer := make([]Extension, len(inner))
for index, ext := range inner {
outer[index] = Extension{ext}
}
return outer, nil
}
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) GetResult {
return GetResult{common.Get(c, alias)}
}
// List returns a Pager which allows you to iterate over the full collection of extensions.
// It does not accept query parameters.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return common.List(c)
}
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