Skip to content

Commit 83f2d2e

Browse files
Cache node listings instead of random lookups (#131)
1 parent 82fde84 commit 83f2d2e

File tree

3 files changed

+117
-93
lines changed

3 files changed

+117
-93
lines changed

cloud/linode/common.go

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package linode
22

33
import (
4-
"context"
54
"fmt"
65
"strconv"
76
"strings"
8-
9-
"github.com/linode/linodego"
10-
"k8s.io/apimachinery/pkg/types"
11-
cloudprovider "k8s.io/cloud-provider"
127
)
138

149
const providerIDPrefix = "linode://"
@@ -31,34 +26,3 @@ func parseProviderID(providerID string) (int, error) {
3126
}
3227
return id, nil
3328
}
34-
35-
func linodeFilterListOptions(targetLabel string) *linodego.ListOptions {
36-
jsonFilter := fmt.Sprintf(`{"label":%q}`, targetLabel)
37-
return linodego.NewListOptions(0, jsonFilter)
38-
}
39-
40-
func linodeByName(ctx context.Context, client Client, nodeName types.NodeName) (*linodego.Instance, error) {
41-
linodes, err := client.ListInstances(ctx, linodeFilterListOptions(string(nodeName)))
42-
if err != nil {
43-
return nil, err
44-
}
45-
46-
if len(linodes) == 0 {
47-
return nil, cloudprovider.InstanceNotFound
48-
} else if len(linodes) > 1 {
49-
return nil, fmt.Errorf("Multiple instances found with name %v", nodeName)
50-
}
51-
52-
return &linodes[0], nil
53-
}
54-
55-
func linodeByID(ctx context.Context, client Client, id int) (*linodego.Instance, error) {
56-
instance, err := client.GetInstance(ctx, id)
57-
if err != nil {
58-
return nil, err
59-
}
60-
if instance == nil {
61-
return nil, fmt.Errorf("linode not found with id %v", id)
62-
}
63-
return instance, nil
64-
}

cloud/linode/instances.go

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package linode
33
import (
44
"context"
55
"fmt"
6-
"net/http"
6+
"os"
77
"strconv"
8+
"sync"
9+
"time"
810

911
"github.com/linode/linode-cloud-controller-manager/sentry"
1012
"github.com/linode/linodego"
@@ -13,12 +15,56 @@ import (
1315
cloudprovider "k8s.io/cloud-provider"
1416
)
1517

18+
type nodeCache struct {
19+
sync.RWMutex
20+
nodes map[int]*linodego.Instance
21+
lastUpdate time.Time
22+
ttl time.Duration
23+
}
24+
25+
// refreshInstances conditionally loads all instances from the Linode API and caches them.
26+
// It does not refresh if the last update happened less than `nodeCache.ttl` ago.
27+
func (nc *nodeCache) refreshInstances(ctx context.Context, client Client) error {
28+
nc.Lock()
29+
defer nc.Unlock()
30+
31+
if time.Since(nc.lastUpdate) < nc.ttl {
32+
return nil
33+
}
34+
35+
instances, err := client.ListInstances(ctx, nil)
36+
if err != nil {
37+
return err
38+
}
39+
nc.nodes = make(map[int]*linodego.Instance)
40+
for _, instance := range instances {
41+
instance := instance
42+
nc.nodes[instance.ID] = &instance
43+
}
44+
nc.lastUpdate = time.Now()
45+
46+
return nil
47+
}
48+
1649
type instances struct {
1750
client Client
51+
52+
nodeCache *nodeCache
1853
}
1954

2055
func newInstances(client Client) cloudprovider.InstancesV2 {
21-
return &instances{client}
56+
var timeout int
57+
if raw, ok := os.LookupEnv("LINODE_INSTANCE_CACHE_TTL"); ok {
58+
timeout, _ = strconv.Atoi(raw)
59+
}
60+
if timeout == 0 {
61+
timeout = 15
62+
}
63+
64+
return &instances{client, &nodeCache{
65+
nodes: make(map[int]*linodego.Instance),
66+
ttl: time.Duration(timeout) * time.Second,
67+
}}
2268
}
2369

2470
type instanceNoIPAddressesError struct {
@@ -29,7 +75,33 @@ func (e instanceNoIPAddressesError) Error() string {
2975
return fmt.Sprintf("instance %d has no IP addresses", e.id)
3076
}
3177

78+
func (i *instances) linodeByName(nodeName types.NodeName) (*linodego.Instance, error) {
79+
i.nodeCache.RLock()
80+
defer i.nodeCache.RUnlock()
81+
for _, node := range i.nodeCache.nodes {
82+
if node.Label == string(nodeName) {
83+
return node, nil
84+
}
85+
}
86+
87+
return nil, cloudprovider.InstanceNotFound
88+
}
89+
90+
func (i *instances) linodeByID(id int) (*linodego.Instance, error) {
91+
i.nodeCache.RLock()
92+
defer i.nodeCache.RUnlock()
93+
instance, ok := i.nodeCache.nodes[id]
94+
if !ok {
95+
return nil, cloudprovider.InstanceNotFound
96+
}
97+
return instance, nil
98+
}
99+
32100
func (i *instances) lookupLinode(ctx context.Context, node *v1.Node) (*linodego.Instance, error) {
101+
if err := i.nodeCache.refreshInstances(ctx, i.client); err != nil {
102+
return nil, err
103+
}
104+
33105
providerID := node.Spec.ProviderID
34106
nodeName := types.NodeName(node.Name)
35107

@@ -44,16 +116,16 @@ func (i *instances) lookupLinode(ctx context.Context, node *v1.Node) (*linodego.
44116
}
45117
sentry.SetTag(ctx, "linode_id", strconv.Itoa(id))
46118

47-
return linodeByID(ctx, i.client, id)
119+
return i.linodeByID(id)
48120
}
49121

50-
return linodeByName(ctx, i.client, nodeName)
122+
return i.linodeByName(nodeName)
51123
}
52124

53125
func (i *instances) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
54126
ctx = sentry.SetHubOnContext(ctx)
55127
if _, err := i.lookupLinode(ctx, node); err != nil {
56-
if apiError, ok := err.(*linodego.Error); ok && apiError.Code == http.StatusNotFound {
128+
if err == cloudprovider.InstanceNotFound {
57129
return false, nil
58130
}
59131
sentry.CaptureError(ctx, err)

0 commit comments

Comments
 (0)