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

manage routes for instances in multiple vpcs in a single region #241

Merged
merged 6 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ When running k8s clusters within VPC, node specific podCIDRs need to be allowed
##### Example usage in values.yaml
```yaml
routeController:
vpcName: <name of VPC>
vpcNames: <comma separated names of VPCs managed by CCM>
clusterCIDR: 10.0.0.0/8
configureCloudRoutes: true
```
Expand Down
43 changes: 9 additions & 34 deletions cloud/linode/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"net"
"os"
"strconv"
"sync"
"time"

"github.com/spf13/pflag"
"golang.org/x/exp/slices"
"k8s.io/client-go/informers"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"

"github.com/linode/linode-cloud-controller-manager/cloud/linode/client"
)
Expand All @@ -35,41 +35,14 @@
KubeconfigFlag *pflag.Flag
LinodeGoDebug bool
EnableRouteController bool
// Deprecated: use VPCNames instead
VPCName string
VPCNames string
LoadBalancerType string
BGPNodeSelector string
LinodeExternalNetwork *net.IPNet
}

// vpcDetails is set when VPCName options flag is set.
// We use it to list instances running within the VPC if set
type vpcDetails struct {
mu sync.RWMutex
id int
name string
}

func (v *vpcDetails) setDetails(client client.Client, name string) error {
v.mu.Lock()
defer v.mu.Unlock()

id, err := getVPCID(client, Options.VPCName)
if err != nil {
return fmt.Errorf("failed finding VPC ID: %w", err)
}
v.id = id
v.name = name
return nil
}

func (v *vpcDetails) getID() int {
v.mu.Lock()
defer v.mu.Unlock()
return v.id
}

var vpcInfo vpcDetails = vpcDetails{id: 0, name: ""}

type linodeCloud struct {
client client.Client
instances cloudprovider.InstancesV2
Expand Down Expand Up @@ -114,11 +87,13 @@
linodeClient.SetDebug(true)
}

if Options.VPCName != "" && Options.VPCNames != "" {
return nil, fmt.Errorf("cannot have both vpc-name and vpc-names set")
}

Check warning on line 92 in cloud/linode/cloud.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/cloud.go#L91-L92

Added lines #L91 - L92 were not covered by tests

if Options.VPCName != "" {
err := vpcInfo.setDetails(linodeClient, Options.VPCName)
if err != nil {
return nil, fmt.Errorf("failed finding VPC ID: %w", err)
}
klog.Warningf("vpc-name flag is deprecated. Use vpc-names instead")
Options.VPCNames = Options.VPCName

Check warning on line 96 in cloud/linode/cloud.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/cloud.go#L95-L96

Added lines #L95 - L96 were not covered by tests
}

routes, err := newRoutes(linodeClient)
Expand Down
15 changes: 10 additions & 5 deletions cloud/linode/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,16 @@

// If running within VPC, find instances and store their ips
vpcNodes := map[int][]string{}
vpcID := vpcInfo.getID()
if vpcID != 0 {
resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, ""))
vpcNames := strings.Split(Options.VPCNames, ",")
for _, v := range vpcNames {
vpcName := strings.TrimSpace(v)
if vpcName == "" {
continue
}
resp, err := GetVPCIPAddresses(ctx, client, vpcName)
if err != nil {
return err
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
continue

Check warning on line 91 in cloud/linode/instances.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/instances.go#L90-L91

Added lines #L90 - L91 were not covered by tests
}
for _, r := range resp {
if r.Address == nil {
Expand All @@ -97,7 +102,7 @@
for i, instance := range instances {

// if running within VPC, only store instances in cache which are part of VPC
if vpcID != 0 && len(vpcNodes[instance.ID]) == 0 {
if Options.VPCNames != "" && len(vpcNodes[instance.ID]) == 0 {
continue
}
node := linodeInstance{
Expand Down
126 changes: 68 additions & 58 deletions cloud/linode/route_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"

Expand All @@ -19,37 +20,43 @@
)

type routeCache struct {
sync.RWMutex
Mu sync.RWMutex
routes map[int][]linodego.VPCIP
lastUpdate time.Time
ttl time.Duration
}

func (rc *routeCache) refreshRoutes(ctx context.Context, client client.Client) error {
rc.Lock()
defer rc.Unlock()
// RefreshCache checks if cache has expired and updates it accordingly
func (rc *routeCache) refreshRoutes(ctx context.Context, client client.Client) {
rc.Mu.Lock()
defer rc.Mu.Unlock()

if time.Since(rc.lastUpdate) < rc.ttl {
return nil
return
}

vpcNodes := map[int][]linodego.VPCIP{}
vpcID := vpcInfo.getID()
resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, ""))
if err != nil {
return err
}
for _, r := range resp {
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], r)
vpcNames := strings.Split(Options.VPCNames, ",")
for _, v := range vpcNames {
vpcName := strings.TrimSpace(v)
if vpcName == "" {
continue

Check warning on line 43 in cloud/linode/route_controller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/route_controller.go#L43

Added line #L43 was not covered by tests
}
resp, err := GetVPCIPAddresses(ctx, client, vpcName)
if err != nil {
klog.Errorf("failed updating cache for VPC %s. Error: %s", vpcName, err.Error())
continue

Check warning on line 48 in cloud/linode/route_controller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/route_controller.go#L47-L48

Added lines #L47 - L48 were not covered by tests
}
for _, r := range resp {
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], r)
}
}

rc.routes = vpcNodes
rc.lastUpdate = time.Now()
return nil
}

type routes struct {
vpcid int
client client.Client
instances *instances
routeCache *routeCache
Expand All @@ -64,13 +71,11 @@
}
klog.V(3).Infof("TTL for routeCache set to %d seconds", timeout)

vpcid := vpcInfo.getID()
if Options.EnableRouteController && vpcid == 0 {
return nil, fmt.Errorf("cannot enable route controller as vpc [%s] not found", Options.VPCName)
if Options.EnableRouteController && Options.VPCNames == "" {
return nil, fmt.Errorf("cannot enable route controller as vpc-names is empty")
}

return &routes{
vpcid: vpcid,
client: client,
instances: newInstances(client),
routeCache: &routeCache{
Expand All @@ -82,8 +87,8 @@

// instanceRoutesByID returns routes for given instance id
func (r *routes) instanceRoutesByID(id int) ([]linodego.VPCIP, error) {
r.routeCache.RLock()
defer r.routeCache.RUnlock()
r.routeCache.Mu.RLock()
defer r.routeCache.Mu.RUnlock()
instanceRoutes, ok := r.routeCache.routes[id]
if !ok {
return nil, fmt.Errorf("no routes found for instance %d", id)
Expand All @@ -94,10 +99,7 @@
// getInstanceRoutes returns routes for given instance id
// It refreshes routeCache if it has expired
func (r *routes) getInstanceRoutes(ctx context.Context, id int) ([]linodego.VPCIP, error) {
if err := r.routeCache.refreshRoutes(ctx, r.client); err != nil {
return nil, err
}

r.routeCache.refreshRoutes(ctx, r.client)
return r.instanceRoutesByID(id)
}

Expand Down Expand Up @@ -135,22 +137,25 @@
// check already configured routes
intfRoutes := []string{}
intfVPCIP := linodego.VPCIP{}
for _, ir := range instanceRoutes {
if ir.VPCID != r.vpcid {
continue
}

if ir.Address != nil {
intfVPCIP = ir
continue
}
for _, vpcid := range GetAllVPCIDs() {
for _, ir := range instanceRoutes {
if ir.VPCID != vpcid {
continue
}

if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
klog.V(4).Infof("Route already exists for node %s", route.TargetNode)
return nil
}
if ir.Address != nil {
intfVPCIP = ir
continue
}

intfRoutes = append(intfRoutes, *ir.AddressRange)
if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
klog.V(4).Infof("Route already exists for node %s", route.TargetNode)
return nil
}

intfRoutes = append(intfRoutes, *ir.AddressRange)

Check warning on line 157 in cloud/linode/route_controller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/route_controller.go#L157

Added line #L157 was not covered by tests
}
}

if intfVPCIP.Address == nil {
Expand Down Expand Up @@ -185,21 +190,24 @@
// check already configured routes
intfRoutes := []string{}
intfVPCIP := linodego.VPCIP{}
for _, ir := range instanceRoutes {
if ir.VPCID != r.vpcid {
continue
}

if ir.Address != nil {
intfVPCIP = ir
continue
}
for _, vpcid := range GetAllVPCIDs() {
for _, ir := range instanceRoutes {
if ir.VPCID != vpcid {
continue
}

if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
continue
}
if ir.Address != nil {
intfVPCIP = ir
continue
}

if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
continue
}

intfRoutes = append(intfRoutes, *ir.AddressRange)
intfRoutes = append(intfRoutes, *ir.AddressRange)

Check warning on line 209 in cloud/linode/route_controller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/route_controller.go#L209

Added line #L209 was not covered by tests
}
}

if intfVPCIP.Address == nil {
Expand Down Expand Up @@ -234,17 +242,19 @@
}

// check for configured routes
for _, ir := range instanceRoutes {
if ir.Address != nil || ir.VPCID != r.vpcid {
continue
}
for _, vpcid := range GetAllVPCIDs() {
for _, ir := range instanceRoutes {
if ir.Address != nil || ir.VPCID != vpcid {
continue
}

if ir.AddressRange != nil {
route := &cloudprovider.Route{
TargetNode: types.NodeName(instance.Label),
DestinationCIDR: *ir.AddressRange,
if ir.AddressRange != nil {
route := &cloudprovider.Route{
TargetNode: types.NodeName(instance.Label),
DestinationCIDR: *ir.AddressRange,
}
configuredRoutes = append(configuredRoutes, route)
}
configuredRoutes = append(configuredRoutes, route)
}
}
}
Expand Down
Loading
Loading