Skip to content

Commit e3c2045

Browse files
authored
manage routes for instances in multiple vpcs in a single region (#241)
* manage routes for instances in multiple vpcs in a single region * fix readme and helm chart * address review comments * add test for multiple vpcs * remove vpc from cache if it doesn't exist * address review comments
1 parent dc407dd commit e3c2045

File tree

9 files changed

+247
-138
lines changed

9 files changed

+247
-138
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ When running k8s clusters within VPC, node specific podCIDRs need to be allowed
176176
##### Example usage in values.yaml
177177
```yaml
178178
routeController:
179-
vpcName: <name of VPC>
179+
vpcNames: <comma separated names of VPCs managed by CCM>
180180
clusterCIDR: 10.0.0.0/8
181181
configureCloudRoutes: true
182182
```

cloud/linode/cloud.go

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"net"
77
"os"
88
"strconv"
9-
"sync"
109
"time"
1110

1211
"github.com/spf13/pflag"
1312
"golang.org/x/exp/slices"
1413
"k8s.io/client-go/informers"
1514
cloudprovider "k8s.io/cloud-provider"
15+
"k8s.io/klog/v2"
1616

1717
"github.com/linode/linode-cloud-controller-manager/cloud/linode/client"
1818
)
@@ -35,41 +35,14 @@ var Options struct {
3535
KubeconfigFlag *pflag.Flag
3636
LinodeGoDebug bool
3737
EnableRouteController bool
38+
// Deprecated: use VPCNames instead
3839
VPCName string
40+
VPCNames string
3941
LoadBalancerType string
4042
BGPNodeSelector string
4143
LinodeExternalNetwork *net.IPNet
4244
}
4345

44-
// vpcDetails is set when VPCName options flag is set.
45-
// We use it to list instances running within the VPC if set
46-
type vpcDetails struct {
47-
mu sync.RWMutex
48-
id int
49-
name string
50-
}
51-
52-
func (v *vpcDetails) setDetails(client client.Client, name string) error {
53-
v.mu.Lock()
54-
defer v.mu.Unlock()
55-
56-
id, err := getVPCID(client, Options.VPCName)
57-
if err != nil {
58-
return fmt.Errorf("failed finding VPC ID: %w", err)
59-
}
60-
v.id = id
61-
v.name = name
62-
return nil
63-
}
64-
65-
func (v *vpcDetails) getID() int {
66-
v.mu.Lock()
67-
defer v.mu.Unlock()
68-
return v.id
69-
}
70-
71-
var vpcInfo vpcDetails = vpcDetails{id: 0, name: ""}
72-
7346
type linodeCloud struct {
7447
client client.Client
7548
instances cloudprovider.InstancesV2
@@ -114,11 +87,13 @@ func newCloud() (cloudprovider.Interface, error) {
11487
linodeClient.SetDebug(true)
11588
}
11689

90+
if Options.VPCName != "" && Options.VPCNames != "" {
91+
return nil, fmt.Errorf("cannot have both vpc-name and vpc-names set")
92+
}
93+
11794
if Options.VPCName != "" {
118-
err := vpcInfo.setDetails(linodeClient, Options.VPCName)
119-
if err != nil {
120-
return nil, fmt.Errorf("failed finding VPC ID: %w", err)
121-
}
95+
klog.Warningf("vpc-name flag is deprecated. Use vpc-names instead")
96+
Options.VPCNames = Options.VPCName
12297
}
12398

12499
routes, err := newRoutes(linodeClient)

cloud/linode/instances.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
7979

8080
// If running within VPC, find instances and store their ips
8181
vpcNodes := map[int][]string{}
82-
vpcID := vpcInfo.getID()
83-
if vpcID != 0 {
84-
resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, ""))
82+
vpcNames := strings.Split(Options.VPCNames, ",")
83+
for _, v := range vpcNames {
84+
vpcName := strings.TrimSpace(v)
85+
if vpcName == "" {
86+
continue
87+
}
88+
resp, err := GetVPCIPAddresses(ctx, client, vpcName)
8589
if err != nil {
86-
return err
90+
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
91+
continue
8792
}
8893
for _, r := range resp {
8994
if r.Address == nil {
@@ -97,7 +102,7 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
97102
for i, instance := range instances {
98103

99104
// if running within VPC, only store instances in cache which are part of VPC
100-
if vpcID != 0 && len(vpcNodes[instance.ID]) == 0 {
105+
if Options.VPCNames != "" && len(vpcNodes[instance.ID]) == 0 {
101106
continue
102107
}
103108
node := linodeInstance{

cloud/linode/route_controller.go

Lines changed: 68 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"strconv"
8+
"strings"
89
"sync"
910
"time"
1011

@@ -19,37 +20,43 @@ import (
1920
)
2021

2122
type routeCache struct {
22-
sync.RWMutex
23+
Mu sync.RWMutex
2324
routes map[int][]linodego.VPCIP
2425
lastUpdate time.Time
2526
ttl time.Duration
2627
}
2728

28-
func (rc *routeCache) refreshRoutes(ctx context.Context, client client.Client) error {
29-
rc.Lock()
30-
defer rc.Unlock()
29+
// RefreshCache checks if cache has expired and updates it accordingly
30+
func (rc *routeCache) refreshRoutes(ctx context.Context, client client.Client) {
31+
rc.Mu.Lock()
32+
defer rc.Mu.Unlock()
3133

3234
if time.Since(rc.lastUpdate) < rc.ttl {
33-
return nil
35+
return
3436
}
3537

3638
vpcNodes := map[int][]linodego.VPCIP{}
37-
vpcID := vpcInfo.getID()
38-
resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, ""))
39-
if err != nil {
40-
return err
41-
}
42-
for _, r := range resp {
43-
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], r)
39+
vpcNames := strings.Split(Options.VPCNames, ",")
40+
for _, v := range vpcNames {
41+
vpcName := strings.TrimSpace(v)
42+
if vpcName == "" {
43+
continue
44+
}
45+
resp, err := GetVPCIPAddresses(ctx, client, vpcName)
46+
if err != nil {
47+
klog.Errorf("failed updating cache for VPC %s. Error: %s", vpcName, err.Error())
48+
continue
49+
}
50+
for _, r := range resp {
51+
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], r)
52+
}
4453
}
4554

4655
rc.routes = vpcNodes
4756
rc.lastUpdate = time.Now()
48-
return nil
4957
}
5058

5159
type routes struct {
52-
vpcid int
5360
client client.Client
5461
instances *instances
5562
routeCache *routeCache
@@ -64,13 +71,11 @@ func newRoutes(client client.Client) (cloudprovider.Routes, error) {
6471
}
6572
klog.V(3).Infof("TTL for routeCache set to %d seconds", timeout)
6673

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

7278
return &routes{
73-
vpcid: vpcid,
7479
client: client,
7580
instances: newInstances(client),
7681
routeCache: &routeCache{
@@ -82,8 +87,8 @@ func newRoutes(client client.Client) (cloudprovider.Routes, error) {
8287

8388
// instanceRoutesByID returns routes for given instance id
8489
func (r *routes) instanceRoutesByID(id int) ([]linodego.VPCIP, error) {
85-
r.routeCache.RLock()
86-
defer r.routeCache.RUnlock()
90+
r.routeCache.Mu.RLock()
91+
defer r.routeCache.Mu.RUnlock()
8792
instanceRoutes, ok := r.routeCache.routes[id]
8893
if !ok {
8994
return nil, fmt.Errorf("no routes found for instance %d", id)
@@ -94,10 +99,7 @@ func (r *routes) instanceRoutesByID(id int) ([]linodego.VPCIP, error) {
9499
// getInstanceRoutes returns routes for given instance id
95100
// It refreshes routeCache if it has expired
96101
func (r *routes) getInstanceRoutes(ctx context.Context, id int) ([]linodego.VPCIP, error) {
97-
if err := r.routeCache.refreshRoutes(ctx, r.client); err != nil {
98-
return nil, err
99-
}
100-
102+
r.routeCache.refreshRoutes(ctx, r.client)
101103
return r.instanceRoutesByID(id)
102104
}
103105

@@ -135,22 +137,25 @@ func (r *routes) CreateRoute(ctx context.Context, clusterName string, nameHint s
135137
// check already configured routes
136138
intfRoutes := []string{}
137139
intfVPCIP := linodego.VPCIP{}
138-
for _, ir := range instanceRoutes {
139-
if ir.VPCID != r.vpcid {
140-
continue
141-
}
142140

143-
if ir.Address != nil {
144-
intfVPCIP = ir
145-
continue
146-
}
141+
for _, vpcid := range GetAllVPCIDs() {
142+
for _, ir := range instanceRoutes {
143+
if ir.VPCID != vpcid {
144+
continue
145+
}
147146

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

153-
intfRoutes = append(intfRoutes, *ir.AddressRange)
152+
if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
153+
klog.V(4).Infof("Route already exists for node %s", route.TargetNode)
154+
return nil
155+
}
156+
157+
intfRoutes = append(intfRoutes, *ir.AddressRange)
158+
}
154159
}
155160

156161
if intfVPCIP.Address == nil {
@@ -185,21 +190,24 @@ func (r *routes) DeleteRoute(ctx context.Context, clusterName string, route *clo
185190
// check already configured routes
186191
intfRoutes := []string{}
187192
intfVPCIP := linodego.VPCIP{}
188-
for _, ir := range instanceRoutes {
189-
if ir.VPCID != r.vpcid {
190-
continue
191-
}
192193

193-
if ir.Address != nil {
194-
intfVPCIP = ir
195-
continue
196-
}
194+
for _, vpcid := range GetAllVPCIDs() {
195+
for _, ir := range instanceRoutes {
196+
if ir.VPCID != vpcid {
197+
continue
198+
}
197199

198-
if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
199-
continue
200-
}
200+
if ir.Address != nil {
201+
intfVPCIP = ir
202+
continue
203+
}
204+
205+
if ir.AddressRange != nil && *ir.AddressRange == route.DestinationCIDR {
206+
continue
207+
}
201208

202-
intfRoutes = append(intfRoutes, *ir.AddressRange)
209+
intfRoutes = append(intfRoutes, *ir.AddressRange)
210+
}
203211
}
204212

205213
if intfVPCIP.Address == nil {
@@ -234,17 +242,19 @@ func (r *routes) ListRoutes(ctx context.Context, clusterName string) ([]*cloudpr
234242
}
235243

236244
// check for configured routes
237-
for _, ir := range instanceRoutes {
238-
if ir.Address != nil || ir.VPCID != r.vpcid {
239-
continue
240-
}
245+
for _, vpcid := range GetAllVPCIDs() {
246+
for _, ir := range instanceRoutes {
247+
if ir.Address != nil || ir.VPCID != vpcid {
248+
continue
249+
}
241250

242-
if ir.AddressRange != nil {
243-
route := &cloudprovider.Route{
244-
TargetNode: types.NodeName(instance.Label),
245-
DestinationCIDR: *ir.AddressRange,
251+
if ir.AddressRange != nil {
252+
route := &cloudprovider.Route{
253+
TargetNode: types.NodeName(instance.Label),
254+
DestinationCIDR: *ir.AddressRange,
255+
}
256+
configuredRoutes = append(configuredRoutes, route)
246257
}
247-
configuredRoutes = append(configuredRoutes, route)
248258
}
249259
}
250260
}

0 commit comments

Comments
 (0)