Skip to content

Commit

Permalink
Cache node listings instead of random lookups (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
okokes-akamai authored Jun 29, 2023
1 parent 82fde84 commit 83f2d2e
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 93 deletions.
36 changes: 0 additions & 36 deletions cloud/linode/common.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package linode

import (
"context"
"fmt"
"strconv"
"strings"

"github.com/linode/linodego"
"k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider"
)

const providerIDPrefix = "linode://"
Expand All @@ -31,34 +26,3 @@ func parseProviderID(providerID string) (int, error) {
}
return id, nil
}

func linodeFilterListOptions(targetLabel string) *linodego.ListOptions {
jsonFilter := fmt.Sprintf(`{"label":%q}`, targetLabel)
return linodego.NewListOptions(0, jsonFilter)
}

func linodeByName(ctx context.Context, client Client, nodeName types.NodeName) (*linodego.Instance, error) {
linodes, err := client.ListInstances(ctx, linodeFilterListOptions(string(nodeName)))
if err != nil {
return nil, err
}

if len(linodes) == 0 {
return nil, cloudprovider.InstanceNotFound
} else if len(linodes) > 1 {
return nil, fmt.Errorf("Multiple instances found with name %v", nodeName)
}

return &linodes[0], nil
}

func linodeByID(ctx context.Context, client Client, id int) (*linodego.Instance, error) {
instance, err := client.GetInstance(ctx, id)
if err != nil {
return nil, err
}
if instance == nil {
return nil, fmt.Errorf("linode not found with id %v", id)
}
return instance, nil
}
82 changes: 77 additions & 5 deletions cloud/linode/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package linode
import (
"context"
"fmt"
"net/http"
"os"
"strconv"
"sync"
"time"

"github.com/linode/linode-cloud-controller-manager/sentry"
"github.com/linode/linodego"
Expand All @@ -13,12 +15,56 @@ import (
cloudprovider "k8s.io/cloud-provider"
)

type nodeCache struct {
sync.RWMutex
nodes map[int]*linodego.Instance
lastUpdate time.Time
ttl time.Duration
}

// refreshInstances conditionally loads all instances from the Linode API and caches them.
// It does not refresh if the last update happened less than `nodeCache.ttl` ago.
func (nc *nodeCache) refreshInstances(ctx context.Context, client Client) error {
nc.Lock()
defer nc.Unlock()

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

instances, err := client.ListInstances(ctx, nil)
if err != nil {
return err
}
nc.nodes = make(map[int]*linodego.Instance)
for _, instance := range instances {
instance := instance
nc.nodes[instance.ID] = &instance
}
nc.lastUpdate = time.Now()

return nil
}

type instances struct {
client Client

nodeCache *nodeCache
}

func newInstances(client Client) cloudprovider.InstancesV2 {
return &instances{client}
var timeout int
if raw, ok := os.LookupEnv("LINODE_INSTANCE_CACHE_TTL"); ok {
timeout, _ = strconv.Atoi(raw)
}
if timeout == 0 {
timeout = 15
}

return &instances{client, &nodeCache{
nodes: make(map[int]*linodego.Instance),
ttl: time.Duration(timeout) * time.Second,
}}
}

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

func (i *instances) linodeByName(nodeName types.NodeName) (*linodego.Instance, error) {
i.nodeCache.RLock()
defer i.nodeCache.RUnlock()
for _, node := range i.nodeCache.nodes {
if node.Label == string(nodeName) {
return node, nil
}
}

return nil, cloudprovider.InstanceNotFound
}

func (i *instances) linodeByID(id int) (*linodego.Instance, error) {
i.nodeCache.RLock()
defer i.nodeCache.RUnlock()
instance, ok := i.nodeCache.nodes[id]
if !ok {
return nil, cloudprovider.InstanceNotFound
}
return instance, nil
}

func (i *instances) lookupLinode(ctx context.Context, node *v1.Node) (*linodego.Instance, error) {
if err := i.nodeCache.refreshInstances(ctx, i.client); err != nil {
return nil, err
}

providerID := node.Spec.ProviderID
nodeName := types.NodeName(node.Name)

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

return linodeByID(ctx, i.client, id)
return i.linodeByID(id)
}

return linodeByName(ctx, i.client, nodeName)
return i.linodeByName(nodeName)
}

func (i *instances) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
ctx = sentry.SetHubOnContext(ctx)
if _, err := i.lookupLinode(ctx, node); err != nil {
if apiError, ok := err.(*linodego.Error); ok && apiError.Code == http.StatusNotFound {
if err == cloudprovider.InstanceNotFound {
return false, nil
}
sentry.CaptureError(ctx, err)
Expand Down
Loading

0 comments on commit 83f2d2e

Please sign in to comment.