kops/upup/pkg/fi/cloudup/openstacktasks/lb.go

262 lines
7.5 KiB
Go

/*
Copyright 2017 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 openstacktasks
import (
"context"
"fmt"
"time"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers"
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
)
// +kops:fitask
type LB struct {
ID *string
Name *string
Subnet *string
VipSubnet *string
Lifecycle fi.Lifecycle
PortID *string
SecurityGroup *SecurityGroup
Provider *string
FlavorID *string
}
const (
// loadbalancerActive* is configuration of exponential backoff for
// going into ACTIVE loadbalancer provisioning status. Starting with 1
// seconds, multiplying by 1.2 with each step and taking 22 steps at maximum
// it will time out after 326s, which roughly corresponds to about 5 minutes
loadbalancerActiveInitDelay = 1 * time.Second
loadbalancerActiveFactor = 1.2
loadbalancerActiveSteps = 22
activeStatus = "ACTIVE"
errorStatus = "ERROR"
)
func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) {
backoff := wait.Backoff{
Duration: loadbalancerActiveInitDelay,
Factor: loadbalancerActiveFactor,
Steps: loadbalancerActiveSteps,
}
var provisioningStatus string
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
loadbalancer, err := loadbalancers.Get(context.TODO(), client, loadbalancerID).Extract()
if err != nil {
return false, err
}
provisioningStatus = loadbalancer.ProvisioningStatus
switch loadbalancer.ProvisioningStatus {
case activeStatus:
return true, nil
case errorStatus:
return true, fmt.Errorf("loadbalancer has gone into ERROR state")
default:
klog.Infof("Waiting for Loadbalancer to be ACTIVE...")
return false, nil
}
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("loadbalancer failed to go into ACTIVE provisioning status within allotted time")
}
return provisioningStatus, err
}
// GetDependencies returns the dependencies of the Instance task
func (e *LB) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask {
var deps []fi.CloudupTask
for _, task := range tasks {
if _, ok := task.(*Subnet); ok {
deps = append(deps, task)
}
if _, ok := task.(*SecurityGroup); ok {
deps = append(deps, task)
}
}
return deps
}
var _ fi.CompareWithID = &LB{}
func (s *LB) CompareWithID() *string {
return s.ID
}
func NewLBTaskFromCloud(cloud openstack.OpenstackCloud, lifecycle fi.Lifecycle, lb *loadbalancers.LoadBalancer, find *LB) (*LB, error) {
osCloud := cloud
sub, err := subnets.Get(context.TODO(), osCloud.NetworkingClient(), lb.VipSubnetID).Extract()
if err != nil {
return nil, err
}
secGroup := true
if find != nil && find.SecurityGroup == nil {
secGroup = false
}
actual := &LB{
ID: fi.PtrTo(lb.ID),
Name: fi.PtrTo(lb.Name),
Lifecycle: lifecycle,
PortID: fi.PtrTo(lb.VipPortID),
Subnet: fi.PtrTo(sub.Name),
VipSubnet: fi.PtrTo(lb.VipSubnetID),
Provider: fi.PtrTo(lb.Provider),
FlavorID: fi.PtrTo(lb.FlavorID),
}
if secGroup {
sg, err := getSecurityGroupByName(&SecurityGroup{Name: fi.PtrTo(lb.Name)}, osCloud)
if err != nil {
return nil, err
}
actual.SecurityGroup = sg
}
if find != nil {
find.ID = actual.ID
find.PortID = actual.PortID
find.VipSubnet = actual.VipSubnet
find.Provider = actual.Provider
find.FlavorID = actual.FlavorID
}
return actual, nil
}
func (s *LB) Find(context *fi.CloudupContext) (*LB, error) {
if s.Name == nil {
return nil, nil
}
cloud := context.T.Cloud.(openstack.OpenstackCloud)
lbPage, err := loadbalancers.List(cloud.LoadBalancerClient(), loadbalancers.ListOpts{
Name: fi.ValueOf(s.Name),
}).AllPages(context.Context())
if err != nil {
return nil, fmt.Errorf("Failed to retrieve loadbalancers for name %s: %v", fi.ValueOf(s.Name), err)
}
lbs, err := loadbalancers.ExtractLoadBalancers(lbPage)
if err != nil {
return nil, fmt.Errorf("Failed to extract loadbalancers : %v", err)
}
if len(lbs) == 0 {
return nil, nil
}
if len(lbs) > 1 {
return nil, fmt.Errorf("Multiple load balancers for name %s", fi.ValueOf(s.Name))
}
return NewLBTaskFromCloud(cloud, s.Lifecycle, &lbs[0], s)
}
func (s *LB) Run(context *fi.CloudupContext) error {
return fi.CloudupDefaultDeltaRunMethod(s, context)
}
func (_ *LB) CheckChanges(a, e, changes *LB) error {
if a == nil {
if e.Name == nil {
return fi.RequiredField("Name")
}
} else {
if changes.ID != nil {
return fi.CannotChangeField("ID")
}
if changes.Name != nil {
return fi.CannotChangeField("Name")
}
}
return nil
}
func (_ *LB) RenderOpenstack(t *openstack.OpenstackAPITarget, a, e, changes *LB) error {
if a == nil {
klog.V(2).Infof("Creating LB with Name: %q", fi.ValueOf(e.Name))
subnets, err := t.Cloud.ListSubnets(subnets.ListOpts{
Name: fi.ValueOf(e.Subnet),
})
if err != nil {
return fmt.Errorf("Failed to retrieve subnet `%s` in loadbalancer creation: %v", fi.ValueOf(e.Subnet), err)
}
if len(subnets) != 1 {
return fmt.Errorf("Unexpected desired subnets for `%s`. Expected 1, got %d", fi.ValueOf(e.Subnet), len(subnets))
}
lbopts := loadbalancers.CreateOpts{
Name: fi.ValueOf(e.Name),
VipSubnetID: subnets[0].ID,
}
if e.FlavorID != nil {
lbopts.FlavorID = fi.ValueOf(e.FlavorID)
}
lb, err := t.Cloud.CreateLB(lbopts)
if err != nil {
return fmt.Errorf("error creating LB: %v", err)
}
e.ID = fi.PtrTo(lb.ID)
e.PortID = fi.PtrTo(lb.VipPortID)
e.VipSubnet = fi.PtrTo(lb.VipSubnetID)
e.Provider = fi.PtrTo(lb.Provider)
e.FlavorID = fi.PtrTo(lb.FlavorID)
if e.SecurityGroup != nil {
opts := ports.UpdateOpts{
SecurityGroups: &[]string{fi.ValueOf(e.SecurityGroup.ID)},
}
_, err = ports.Update(context.TODO(), t.Cloud.NetworkingClient(), lb.VipPortID, opts).Extract()
if err != nil {
return fmt.Errorf("Failed to update security group for port %s: %v", lb.VipPortID, err)
}
}
return nil
}
// We may have failed to update the security groups on the load balancer
port, err := t.Cloud.GetPort(fi.ValueOf(a.PortID))
if err != nil {
return fmt.Errorf("Failed to get port with id %s: %v", fi.ValueOf(a.PortID), err)
}
// Ensure the loadbalancer port has one security group and it is the one specified,
if e.SecurityGroup != nil &&
(len(port.SecurityGroups) < 1 || port.SecurityGroups[0] != fi.ValueOf(e.SecurityGroup.ID)) {
opts := ports.UpdateOpts{
SecurityGroups: &[]string{fi.ValueOf(e.SecurityGroup.ID)},
}
_, err = ports.Update(context.TODO(), t.Cloud.NetworkingClient(), fi.ValueOf(a.PortID), opts).Extract()
if err != nil {
return fmt.Errorf("Failed to update security group for port %s: %v", fi.ValueOf(a.PortID), err)
}
return nil
}
klog.V(2).Infof("Openstack task LB::RenderOpenstack did nothing")
return nil
}