mirror of https://github.com/kubernetes/kops.git
262 lines
7.5 KiB
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
|
|
}
|