Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions cloud/linode/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Client interface {
GetVPCSubnet(context.Context, int, int) (*linodego.VPCSubnet, error)
ListVPCs(context.Context, *linodego.ListOptions) ([]linodego.VPC, error)
ListVPCIPAddresses(context.Context, int, *linodego.ListOptions) ([]linodego.VPCIP, error)
ListVPCIPv6Addresses(context.Context, int, *linodego.ListOptions) ([]linodego.VPCIP, error)
ListVPCSubnets(context.Context, int, *linodego.ListOptions) ([]linodego.VPCSubnet, error)

CreateNodeBalancer(context.Context, linodego.NodeBalancerCreateOptions) (*linodego.NodeBalancer, error)
Expand Down
13 changes: 13 additions & 0 deletions cloud/linode/client/client_with_metrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions cloud/linode/client/mocks/mock_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 38 additions & 5 deletions cloud/linode/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,22 @@ type nodeCache struct {
}

// getInstanceAddresses returns all addresses configured on a linode.
func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []string) []nodeIP {
func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []string, vpcIPv6AddrTypes map[string]v1.NodeAddressType) []nodeIP {
ips := []nodeIP{}

// We store vpc IPv6 addrs separately so that we can list them after IPv4 addresses.
// Ordering matters in k8s, first address marked as externalIP will be used as external IP for node.
// We prefer to use IPv4 address as external IP if possible, so we list them first.
vpcIPv6Addrs := []nodeIP{}

// If vpc ips are present, list them first
for _, ip := range vpcips {
ipType := v1.NodeInternalIP
if _, ok := vpcIPv6AddrTypes[ip]; ok {
ipType = vpcIPv6AddrTypes[ip]
vpcIPv6Addrs = append(vpcIPv6Addrs, nodeIP{ip: ip, ipType: ipType})
continue
}
ips = append(ips, nodeIP{ip: ip, ipType: ipType})
}

Expand All @@ -56,6 +66,9 @@ func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []s
ips = append(ips, nodeIP{ip: ip.String(), ipType: ipType})
}

// Add vpc IPv6 addresses after IPv4 addresses
ips = append(ips, vpcIPv6Addrs...)

if instance.IPv6 != "" {
ips = append(ips, nodeIP{ip: strings.TrimSuffix(instance.IPv6, "/128"), ipType: v1.NodeExternalIP})
}
Expand All @@ -80,6 +93,7 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)

// If running within VPC, find instances and store their ips
vpcNodes := map[int][]string{}
vpcIPv6AddrTypes := map[string]v1.NodeAddressType{}
for _, name := range Options.VPCNames {
vpcName := strings.TrimSpace(name)
if vpcName == "" {
Expand All @@ -90,11 +104,30 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
continue
}
for _, r := range resp {
if r.Address == nil {
for _, vpcip := range resp {
if vpcip.Address == nil {
continue
}
vpcNodes[vpcip.LinodeID] = append(vpcNodes[vpcip.LinodeID], *vpcip.Address)
}

resp, err = GetVPCIPv6Addresses(ctx, client, vpcName)
if err != nil {
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
continue
}
for _, vpcip := range resp {
if len(vpcip.IPv6Addresses) == 0 {
continue
}
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], *r.Address)
vpcIPv6AddrType := v1.NodeInternalIP
if vpcip.IPv6IsPublic != nil && *vpcip.IPv6IsPublic {
vpcIPv6AddrType = v1.NodeExternalIP
}
for _, ipv6 := range vpcip.IPv6Addresses {
vpcNodes[vpcip.LinodeID] = append(vpcNodes[vpcip.LinodeID], ipv6.SLAACAddress)
vpcIPv6AddrTypes[ipv6.SLAACAddress] = vpcIPv6AddrType
}
}
}

Expand All @@ -106,7 +139,7 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
}
node := linodeInstance{
instance: &instances[index],
ips: nc.getInstanceAddresses(instance, vpcNodes[instance.ID]),
ips: nc.getInstanceAddresses(instance, vpcNodes[instance.ID], vpcIPv6AddrTypes),
}
newNodes[instance.ID] = node
}
Expand Down
1 change: 1 addition & 0 deletions cloud/linode/instances_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func TestMetadataRetrieval(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{instance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), vpcIDs["test"], gomock.Any()).Return(routesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), vpcIDs["test"], gomock.Any()).Return([]linodego.VPCIP{}, nil)

meta, err := instances.InstanceMetadata(ctx, node)
require.NoError(t, err)
Expand Down
12 changes: 12 additions & 0 deletions cloud/linode/route_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestListRoutes(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), gomock.Any()).Times(1).Return([]linodego.Instance{}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return([]linodego.VPCIP{}, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.Empty(t, routes)
Expand All @@ -76,6 +77,7 @@ func TestListRoutes(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return([]linodego.VPCIP{}, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.Empty(t, routes)
Expand Down Expand Up @@ -103,6 +105,7 @@ func TestListRoutes(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(noRoutesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.Empty(t, routes)
Expand Down Expand Up @@ -151,6 +154,7 @@ func TestListRoutes(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(routesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.NotEmpty(t, routes)
Expand Down Expand Up @@ -199,6 +203,7 @@ func TestListRoutes(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(routesInDifferentVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.Empty(t, routes)
Expand Down Expand Up @@ -297,6 +302,7 @@ func TestListRoutes(t *testing.T) {
c2 := client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c1).Times(1).Return(routesInVPC2, nil)
c3 := client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c2).Times(1).Return(routesInVPC, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c3).Times(1).Return(routesInVPC2, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
routes, err := routeController.ListRoutes(ctx, "test")
require.NoError(t, err)
assert.NotEmpty(t, routes)
Expand Down Expand Up @@ -378,6 +384,7 @@ func TestCreateRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(noRoutesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndRoute, nil)
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
assert.NoError(t, err)
Expand Down Expand Up @@ -445,6 +452,7 @@ func TestCreateRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(routesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
assert.NoError(t, err)
})
Expand All @@ -459,6 +467,7 @@ func TestCreateRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return([]linodego.VPCIP{}, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
assert.Error(t, err)
})
Expand Down Expand Up @@ -502,6 +511,7 @@ func TestDeleteRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return([]linodego.VPCIP{}, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.Error(t, err)
})
Expand Down Expand Up @@ -533,6 +543,7 @@ func TestDeleteRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(noRoutesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndNoRoute, nil)
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.NoError(t, err)
Expand Down Expand Up @@ -565,6 +576,7 @@ func TestDeleteRoute(t *testing.T) {

client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(routesInVPC, nil)
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndNoRoute, nil)
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.NoError(t, err)
Expand Down
49 changes: 39 additions & 10 deletions cloud/linode/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ func GetSubnetID(ctx context.Context, client client.Client, vpcID int, subnetNam
return 0, subnetLookupError{subnetName}
}

// GetVPCIPAddresses returns vpc ip's for given VPC label
func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
// getVPCIDAndFilter returns the VPC ID and a resultFilter for subnet names (if any)
func getVPCIDAndFilter(ctx context.Context, client client.Client, vpcName string) (int, string, error) {
vpcID, err := GetVPCID(ctx, client, strings.TrimSpace(vpcName))
if err != nil {
return nil, err
return 0, "", err
}

resultFilter := ""
Expand Down Expand Up @@ -133,20 +133,49 @@ func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string
filter, err = json.Marshal(subnetFilter{SubnetID: strings.Join(subnetIDList, ",")})
if err != nil {
klog.Error("could not create JSON filter for subnet_id")
} else {
resultFilter = string(filter)
}
resultFilter = string(filter)
}

return vpcID, resultFilter, nil
}

// handleNotFoundError checks if the error is a '404 Not Found error' and deletes the entry from the cache.
func handleNotFoundError(err error, vpcName string) error {
if linodego.ErrHasStatus(err, http.StatusNotFound) {
Mu.Lock()
defer Mu.Unlock()
klog.Errorf("vpc %s not found. Deleting entry from cache", vpcName)
delete(vpcIDs, vpcName)
}
return err
}

// GetVPCIPAddresses returns vpc ip's for given VPC label
func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
vpcID, resultFilter, err := getVPCIDAndFilter(ctx, client, vpcName)
if err != nil {
return nil, err
}

resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, resultFilter))
if err != nil {
if linodego.ErrHasStatus(err, http.StatusNotFound) {
Mu.Lock()
defer Mu.Unlock()
klog.Errorf("vpc %s not found. Deleting entry from cache", vpcName)
delete(vpcIDs, vpcName)
}
return nil, handleNotFoundError(err, vpcName)
}
return resp, nil
}

func GetVPCIPv6Addresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
vpcID, resultFilter, err := getVPCIDAndFilter(ctx, client, vpcName)
if err != nil {
return nil, err
}

resp, err := client.ListVPCIPv6Addresses(ctx, vpcID, linodego.NewListOptions(0, resultFilter))
if err != nil {
return nil, handleNotFoundError(err, vpcName)
}
return resp, nil
}

Expand Down
16 changes: 9 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,16 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.72.1 // indirect
Expand Down Expand Up @@ -193,3 +193,5 @@ replace (
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.33.0
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.33.0
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to upstream linodego when this PR is merged: linode/linodego#791

replace github.com/linode/linodego => github.com/rahulait/linodego v1.50.1-0.20250805021045-93a6855d693c
Loading
Loading