kops/vendor/k8s.io/cloud-provider-openstack/pkg/util/openstack/loadbalancer.go

367 lines
11 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package openstack
import (
"errors"
"fmt"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
"github.com/gophercloud/gophercloud/pagination"
version "github.com/hashicorp/go-version"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cloud-provider-openstack/pkg/cloudprovider/providers/openstack/metrics"
klog "k8s.io/klog/v2"
cpoerrors "k8s.io/cloud-provider-openstack/pkg/util/errors"
)
const (
OctaviaFeatureTags = 0
OctaviaFeatureVIPACL = 1
OctaviaFeatureFlavors = 2
OctaviaFeatureTimeout = 3
OctaviaFeatureAvailabilityZones = 4
loadbalancerActiveInitDelay = 1 * time.Second
loadbalancerActiveFactor = 1.2
loadbalancerActiveSteps = 19
activeStatus = "ACTIVE"
errorStatus = "ERROR"
)
var (
octaviaVersion string
// ErrNotFound is used to inform that the object is missing
ErrNotFound = errors.New("failed to find object")
// ErrMultipleResults is used when we unexpectedly get back multiple results
ErrMultipleResults = errors.New("multiple results where only one expected")
)
// getOctaviaVersion returns the current Octavia API version.
func getOctaviaVersion(client *gophercloud.ServiceClient) (string, error) {
if octaviaVersion != "" {
return octaviaVersion, nil
}
var defaultVer = "0.0"
mc := metrics.NewMetricContext("version", "list")
allPages, err := apiversions.List(client).AllPages()
if mc.ObserveRequest(err) != nil {
return defaultVer, err
}
versions, err := apiversions.ExtractAPIVersions(allPages)
if err != nil {
return defaultVer, err
}
if len(versions) == 0 {
return defaultVer, fmt.Errorf("API versions for Octavia not found")
}
klog.V(4).Infof("Found Octavia API versions: %v", versions)
// The current version is always the last one in the list
octaviaVersion = versions[len(versions)-1].ID
klog.V(4).Infof("The current Octavia API version: %v", octaviaVersion)
return octaviaVersion, nil
}
// IsOctaviaFeatureSupported returns true if the given feature is supported in the deployed Octavia version.
func IsOctaviaFeatureSupported(client *gophercloud.ServiceClient, feature int) bool {
octaviaVer, err := getOctaviaVersion(client)
if err != nil {
klog.Warningf("Failed to get current Octavia API version: %v", err)
return false
}
currentVer, _ := version.NewVersion(octaviaVer)
switch feature {
case OctaviaFeatureVIPACL:
verACL, _ := version.NewVersion("v2.12")
if currentVer.GreaterThanOrEqual(verACL) {
return true
}
case OctaviaFeatureTags:
verTags, _ := version.NewVersion("v2.5")
if currentVer.GreaterThanOrEqual(verTags) {
return true
}
case OctaviaFeatureFlavors:
verFlavors, _ := version.NewVersion("v2.6")
if currentVer.GreaterThanOrEqual(verFlavors) {
return true
}
case OctaviaFeatureTimeout:
verFlavors, _ := version.NewVersion("v2.1")
if currentVer.GreaterThanOrEqual(verFlavors) {
return true
}
case OctaviaFeatureAvailabilityZones:
verAvailabilityZones, _ := version.NewVersion("v2.14")
if currentVer.GreaterThanOrEqual(verAvailabilityZones) {
return true
}
default:
klog.Warningf("Feature %d not recognized", feature)
}
return false
}
func waitLoadbalancerActive(client *gophercloud.ServiceClient, loadbalancerID string) error {
backoff := wait.Backoff{
Duration: loadbalancerActiveInitDelay,
Factor: loadbalancerActiveFactor,
Steps: loadbalancerActiveSteps,
}
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
mc := metrics.NewMetricContext("loadbalancer", "get")
loadbalancer, err := loadbalancers.Get(client, loadbalancerID).Extract()
if mc.ObserveRequest(err) != nil {
return false, err
}
if loadbalancer.ProvisioningStatus == activeStatus {
return true, nil
} else if loadbalancer.ProvisioningStatus == errorStatus {
return true, fmt.Errorf("loadbalancer has gone into ERROR state")
} else {
return false, nil
}
})
return err
}
// UpdateListener updates a listener and wait for the lb active
func UpdateListener(client *gophercloud.ServiceClient, lbID string, listenerID string, opts listeners.UpdateOpts) error {
mc := metrics.NewMetricContext("loadbalancer_listener", "update")
_, err := listeners.Update(client, listenerID, opts).Extract()
if mc.ObserveRequest(err) != nil {
return err
}
if err := waitLoadbalancerActive(client, lbID); err != nil {
return fmt.Errorf("failed to wait for load balancer ACTIVE after updating listener: %v", err)
}
return nil
}
// CreateListener creates a new listener
func CreateListener(client *gophercloud.ServiceClient, lbID string, opts listeners.CreateOpts) (*listeners.Listener, error) {
mc := metrics.NewMetricContext("loadbalancer_listener", "create")
listener, err := listeners.Create(client, opts).Extract()
if mc.ObserveRequest(err) != nil {
return nil, err
}
if err := waitLoadbalancerActive(client, lbID); err != nil {
return nil, fmt.Errorf("failed to wait for load balancer ACTIVE after creating listener: %v", err)
}
return listener, nil
}
// GetLoadbalancerByName retrieves loadbalancer object
func GetLoadbalancerByName(client *gophercloud.ServiceClient, name string) (*loadbalancers.LoadBalancer, error) {
opts := loadbalancers.ListOpts{
Name: name,
}
mc := metrics.NewMetricContext("loadbalancer", "list")
allPages, err := loadbalancers.List(client, opts).AllPages()
if mc.ObserveRequest(err) != nil {
return nil, err
}
loadbalancerList, err := loadbalancers.ExtractLoadBalancers(allPages)
if err != nil {
return nil, err
}
if len(loadbalancerList) > 1 {
return nil, ErrMultipleResults
}
if len(loadbalancerList) == 0 {
return nil, ErrNotFound
}
return &loadbalancerList[0], nil
}
// GetListenerByName gets a listener by its name, raise error if not found or get multiple ones.
func GetListenerByName(client *gophercloud.ServiceClient, name string, lbID string) (*listeners.Listener, error) {
opts := listeners.ListOpts{
Name: name,
LoadbalancerID: lbID,
}
mc := metrics.NewMetricContext("loadbalancer_listener", "list")
pager := listeners.List(client, opts)
var listenerList []listeners.Listener
err := pager.EachPage(func(page pagination.Page) (bool, error) {
v, err := listeners.ExtractListeners(page)
if err != nil {
return false, err
}
listenerList = append(listenerList, v...)
if len(listenerList) > 1 {
return false, ErrMultipleResults
}
return true, nil
})
if mc.ObserveRequest(err) != nil {
if cpoerrors.IsNotFound(err) {
return nil, ErrNotFound
}
return nil, err
}
if len(listenerList) == 0 {
return nil, ErrNotFound
}
return &listenerList[0], nil
}
// GetPoolByName gets a pool by its name, raise error if not found or get multiple ones.
func GetPoolByName(client *gophercloud.ServiceClient, name string, lbID string) (*pools.Pool, error) {
var listenerPools []pools.Pool
opts := pools.ListOpts{
Name: name,
LoadbalancerID: lbID,
}
mc := metrics.NewMetricContext("loadbalancer_pool", "list")
err := pools.List(client, opts).EachPage(func(page pagination.Page) (bool, error) {
v, err := pools.ExtractPools(page)
if err != nil {
return false, err
}
listenerPools = append(listenerPools, v...)
if len(listenerPools) > 1 {
return false, ErrMultipleResults
}
return true, nil
})
if mc.ObserveRequest(err) != nil {
if cpoerrors.IsNotFound(err) {
return nil, ErrNotFound
}
return nil, err
}
if len(listenerPools) == 0 {
return nil, ErrNotFound
} else if len(listenerPools) > 1 {
return nil, ErrMultipleResults
}
return &listenerPools[0], nil
}
// GetPoolsByListener finds pool for a listener. A listener always has exactly one pool.
func GetPoolByListener(client *gophercloud.ServiceClient, lbID, listenerID string) (*pools.Pool, error) {
listenerPools := make([]pools.Pool, 0, 1)
mc := metrics.NewMetricContext("loadbalancer_pool", "list")
err := pools.List(client, pools.ListOpts{LoadbalancerID: lbID}).EachPage(func(page pagination.Page) (bool, error) {
poolsList, err := pools.ExtractPools(page)
if err != nil {
return false, err
}
for _, p := range poolsList {
for _, l := range p.Listeners {
if l.ID == listenerID {
listenerPools = append(listenerPools, p)
}
}
}
if len(listenerPools) > 1 {
return false, ErrMultipleResults
}
return true, nil
})
if mc.ObserveRequest(err) != nil {
if cpoerrors.IsNotFound(err) {
return nil, ErrNotFound
}
return nil, err
}
if len(listenerPools) == 0 {
return nil, ErrNotFound
}
return &listenerPools[0], nil
}
// DeleteLoadbalancer deletes a loadbalancer with all its child objects.
func DeleteLoadbalancer(client *gophercloud.ServiceClient, lbID string) error {
mc := metrics.NewMetricContext("loadbalancer", "delete")
err := loadbalancers.Delete(client, lbID, loadbalancers.DeleteOpts{Cascade: true}).ExtractErr()
if err != nil && !cpoerrors.IsNotFound(err) {
mc.ObserveRequest(err)
return fmt.Errorf("error deleting loadbalancer %s: %v", lbID, err)
}
return mc.ObserveRequest(nil)
}
// GetMembersbyPool get all the members in the pool.
func GetMembersbyPool(client *gophercloud.ServiceClient, poolID string) ([]pools.Member, error) {
var members []pools.Member
mc := metrics.NewMetricContext("loadbalancer_member", "list")
err := pools.ListMembers(client, poolID, pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) {
membersList, err := pools.ExtractMembers(page)
if err != nil {
return false, err
}
members = append(members, membersList...)
return true, nil
})
if mc.ObserveRequest(err) != nil {
return nil, err
}
return members, nil
}
func BatchUpdatePoolMembers(client *gophercloud.ServiceClient, lbID, poolID string, opts []pools.BatchUpdateMemberOpts) error {
err := pools.BatchUpdateMembers(client, poolID, opts).ExtractErr()
if err != nil {
return err
}
if err := waitLoadbalancerActive(client, lbID); err != nil {
return fmt.Errorf("failed to wait for load balancer ACTIVE after updating pool members for %s: %v", poolID, err)
}
return nil
}