@@ -3,8 +3,10 @@ package linode
33import (
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+
1649type instances struct {
1750 client Client
51+
52+ nodeCache * nodeCache
1853}
1954
2055func 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
2470type 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+
32100func (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
53125func (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