Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cleanup for VPC load balancers #160

Merged
merged 1 commit into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/ibmcloud-janitor/resources/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var PowervsResources = []Resource{

var VpcResources = []Resource{
VPCInstance{},
VPCLoadBalancer{},
VPCNetwork{},
VPCs{},
}
Expand Down Expand Up @@ -74,6 +75,9 @@ type VPC interface {
GetSubnetPublicGateway(options *vpcv1.GetSubnetPublicGatewayOptions) (*vpcv1.PublicGateway, *core.DetailedResponse, error)
DeletePublicGateway(options *vpcv1.DeletePublicGatewayOptions) (*core.DetailedResponse, error)
UnsetSubnetPublicGateway(options *vpcv1.UnsetSubnetPublicGatewayOptions) (*core.DetailedResponse, error)
DeleteLoadBalancer(options *vpcv1.DeleteLoadBalancerOptions) (*core.DetailedResponse, error)
ListLoadBalancers(options *vpcv1.ListLoadBalancersOptions) (*vpcv1.LoadBalancerCollection, *core.DetailedResponse, error)
GetLoadBalancer(options *vpcv1.GetLoadBalancerOptions) (result *vpcv1.LoadBalancer, response *core.DetailedResponse, err error)
}

type ServiceIDClient interface {
Expand Down
67 changes: 67 additions & 0 deletions internal/ibmcloud-janitor/resources/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2022 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 resources

import (
"net/url"

"github.com/pkg/errors"
)

// pagingHelper is used while listing resources. It parses and fetches the start token for
// getting the next set of resources if nextURL is returned by func f.
// func f has start as a parameter and returns the following:
// isDone bool - true denotes that iterating is not needed as there is no next set of resources
// nextURL string - denotes a URL for a page of resources which is parsed for fetching start token for next iteration
// e error - will break and return error if e is not nil
func pagingHelper(f func(string) (bool, string, error)) (err error) {
start := ""

getStartToken := func(nextURL string) (string, error) {
url, err := url.Parse(nextURL)
if err != nil || url == nil {
return "", errors.Wrapf(err, "failed to parse next url for getting next resources")
}

start := url.Query().Get("start")
return start, nil
}

for {
isDone, nextURL, e := f(start)

if e != nil {
err = e
break
}

if isDone {
break
}

if nextURL != "" {
start, err = getStartToken(nextURL)
if err != nil {
break
}
} else {
break
}
}

return
}
12 changes: 12 additions & 0 deletions internal/ibmcloud-janitor/resources/vpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ func (c *IBMVPCClient) UnsetSubnetPublicGateway(options *vpcv1.UnsetSubnetPublic
return c.vpcService.UnsetSubnetPublicGateway(options)
}

func (c *IBMVPCClient) DeleteLoadBalancer(options *vpcv1.DeleteLoadBalancerOptions) (*core.DetailedResponse, error) {
return c.vpcService.DeleteLoadBalancer(options)
}

func (c *IBMVPCClient) ListLoadBalancers(options *vpcv1.ListLoadBalancersOptions) (*vpcv1.LoadBalancerCollection, *core.DetailedResponse, error) {
return c.vpcService.ListLoadBalancers(options)
}

func (c *IBMVPCClient) GetLoadBalancer(options *vpcv1.GetLoadBalancerOptions) (result *vpcv1.LoadBalancer, response *core.DetailedResponse, err error) {
return c.vpcService.GetLoadBalancer(options)
}

// Creates a new VPC Client
func NewVPCClient(options *CleanupOptions) (*IBMVPCClient, error) {
client := &IBMVPCClient{}
Expand Down
119 changes: 119 additions & 0 deletions internal/ibmcloud-janitor/resources/vpc_lbs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
Copyright 2022 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 resources

import (
"strings"
"time"

"github.com/IBM/vpc-go-sdk/vpcv1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
)

type VPCLoadBalancer struct{}

var (
lbDeletionTimeout = time.Minute * 4
lbPollingInterval = time.Second * 30
resourceLogger *logrus.Entry
)

// Cleans up the load balancers in a given region
func (VPCLoadBalancer) cleanup(options *CleanupOptions) error {
resourceLogger = logrus.WithFields(logrus.Fields{"resource": options.Resource.Name})
resourceLogger.Info("Cleaning up the load balancers")
client, err := NewVPCClient(options)
if err != nil {
return errors.Wrap(err, "couldn't create VPC client")
}

var deletedLBList []string
var errs []error

f := func(start string) (bool, string, error) {
listLbOpts := &vpcv1.ListLoadBalancersOptions{}
if start != "" {
listLbOpts.Start = &start
}

loadBalancers, _, err := client.ListLoadBalancers(listLbOpts)
if err != nil {
return false, "", errors.Wrap(err, "failed to list the load balancers")
}

if loadBalancers == nil || len(loadBalancers.LoadBalancers) <= 0 {
resourceLogger.Info("there are no available load balancers to delete")
return true, "", nil
}

for _, lb := range loadBalancers.LoadBalancers {
if *lb.ResourceGroup.ID == client.ResourceGroupID {
if _, err := client.DeleteLoadBalancer(&vpcv1.DeleteLoadBalancerOptions{
ID: lb.ID,
}); err != nil {
resourceLogger.WithField("name", *lb.Name).Error("failed to delete load balancer")
errs = append(errs, err)
continue
}
deletedLBList = append(deletedLBList, *lb.ID)
resourceLogger.WithField("name", *lb.Name).Info("load balancer deletetion triggered")
}
}

if loadBalancers.Next != nil && *loadBalancers.Next.Href != "" {
return false, *loadBalancers.Next.Href, nil
}

return true, "", nil
}

if err = pagingHelper(f); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to delete the load balancers"))
}

// check if the LBs are properly deleted
checkLBs(deletedLBList, client, &errs)

if len(errs) > 0 {
return kerrors.NewAggregate(errs)
}

resourceLogger.Info("Successfully deleted the load balancers")
return nil
}

func checkLBs(list []string, client *IBMVPCClient, errs *[]error) {
check := func(id string) error {
return wait.PollImmediate(lbPollingInterval, lbDeletionTimeout, func() (bool, error) {
_, _, err := client.GetLoadBalancer(&vpcv1.GetLoadBalancerOptions{ID: &id})
if err != nil && strings.Contains(err.Error(), "cannot be found") {
return true, nil
}
return false, err
})
}

for _, lb := range list {
if err := check(lb); err != nil {
resourceLogger.WithField("ID", lb).Error("failed to check the deletion of load balancer")
*errs = append(*errs, err)
}
}
}