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
3 changes: 3 additions & 0 deletions cloud/linode/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Client interface {

UpdateInstanceConfigInterface(context.Context, int, int, int, linodego.InstanceConfigInterfaceUpdateOptions) (*linodego.InstanceConfigInterface, error)

ListInterfaces(ctx context.Context, linodeID int, opts *linodego.ListOptions) ([]linodego.LinodeInterface, error)
UpdateInterface(ctx context.Context, linodeID int, interfaceID int, opts linodego.LinodeInterfaceUpdateOptions) (*linodego.LinodeInterface, error)

GetVPC(context.Context, int) (*linodego.VPC, error)
GetVPCSubnet(context.Context, int, int) (*linodego.VPCSubnet, error)
ListVPCs(context.Context, *linodego.ListOptions) ([]linodego.VPC, error)
Expand Down
26 changes: 26 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.

30 changes: 30 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.

57 changes: 41 additions & 16 deletions cloud/linode/route_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func (r *routes) CreateRoute(ctx context.Context, clusterName string, nameHint s

// check already configured routes
intfRoutes := []string{}
linodeInterfaceRoutes := []linodego.VPCInterfaceIPv4RangeCreateOptions{}
intfVPCIP := linodego.VPCIP{}

for _, vpcid := range services.GetAllVPCIDs() {
Expand All @@ -176,6 +177,9 @@ func (r *routes) CreateRoute(ctx context.Context, clusterName string, nameHint s
}

intfRoutes = append(intfRoutes, *ir.AddressRange)
linodeInterfaceRoutes = append(linodeInterfaceRoutes, linodego.VPCInterfaceIPv4RangeCreateOptions{
Range: *ir.AddressRange,
})
}
}

Expand All @@ -184,16 +188,11 @@ func (r *routes) CreateRoute(ctx context.Context, clusterName string, nameHint s
}

intfRoutes = append(intfRoutes, route.DestinationCIDR)
interfaceUpdateOptions := linodego.InstanceConfigInterfaceUpdateOptions{
IPRanges: &intfRoutes,
}
linodeInterfaceRoutes = append(linodeInterfaceRoutes, linodego.VPCInterfaceIPv4RangeCreateOptions{
Range: route.DestinationCIDR,
})

resp, err := r.client.UpdateInstanceConfigInterface(ctx, instance.ID, intfVPCIP.ConfigID, intfVPCIP.InterfaceID, interfaceUpdateOptions)
if err != nil {
return err
}
klog.V(4).Infof("Added routes for node %s. Current routes: %v", route.TargetNode, resp.IPRanges)
return nil
return r.handleInterfaces(ctx, intfRoutes, linodeInterfaceRoutes, instance, intfVPCIP, route)
}

// DeleteRoute removes route's subnet from ip_ranges of target node's VPC interface
Expand All @@ -210,6 +209,7 @@ func (r *routes) DeleteRoute(ctx context.Context, clusterName string, route *clo

// check already configured routes
intfRoutes := []string{}
linodeInterfaceRoutes := []linodego.VPCInterfaceIPv4RangeCreateOptions{}
intfVPCIP := linodego.VPCIP{}

for _, vpcid := range services.GetAllVPCIDs() {
Expand All @@ -228,21 +228,46 @@ func (r *routes) DeleteRoute(ctx context.Context, clusterName string, route *clo
}

intfRoutes = append(intfRoutes, *ir.AddressRange)
linodeInterfaceRoutes = append(linodeInterfaceRoutes, linodego.VPCInterfaceIPv4RangeCreateOptions{
Range: *ir.AddressRange,
})
}
}

if intfVPCIP.Address == nil {
return fmt.Errorf("unable to remove route %s for node %s. no valid interface found", route.DestinationCIDR, route.TargetNode)
}

interfaceUpdateOptions := linodego.InstanceConfigInterfaceUpdateOptions{
IPRanges: &intfRoutes,
}
resp, err := r.client.UpdateInstanceConfigInterface(ctx, instance.ID, intfVPCIP.ConfigID, intfVPCIP.InterfaceID, interfaceUpdateOptions)
if err != nil {
return err
return r.handleInterfaces(ctx, intfRoutes, linodeInterfaceRoutes, instance, intfVPCIP, route)
}

// handleInterfaces updates the VPC interface with adding or deleting routes
func (r *routes) handleInterfaces(ctx context.Context, intfRoutes []string, linodeInterfaceRoutes []linodego.VPCInterfaceIPv4RangeCreateOptions, instance *linodego.Instance, intfVPCIP linodego.VPCIP, route *cloudprovider.Route) error {
if instance.InterfaceGeneration == linodego.GenerationLinode {
interfaceUpdateOptions := linodego.LinodeInterfaceUpdateOptions{
VPC: &linodego.VPCInterfaceCreateOptions{
SubnetID: intfVPCIP.SubnetID,
IPv4: &linodego.VPCInterfaceIPv4CreateOptions{Ranges: linodeInterfaceRoutes},
},
}
resp, err := r.client.UpdateInterface(ctx, instance.ID, intfVPCIP.InterfaceID, interfaceUpdateOptions)
if err != nil {
klog.V(4).Infof("Unable to update legacy interface %d for node %s", intfVPCIP.InterfaceID, route.TargetNode)
return err
}
klog.V(4).Infof("Updated routes for node %s. Current routes: %v", route.TargetNode, resp.VPC.IPv4.Ranges)
} else {
interfaceUpdateOptions := linodego.InstanceConfigInterfaceUpdateOptions{
IPRanges: &intfRoutes,
}
resp, err := r.client.UpdateInstanceConfigInterface(ctx, instance.ID, intfVPCIP.ConfigID, intfVPCIP.InterfaceID, interfaceUpdateOptions)
if err != nil {
klog.V(4).Infof("Unable to update linode interface %d for node %s", intfVPCIP.InterfaceID, route.TargetNode)
return err
}
klog.V(4).Infof("Updated routes for node %s. Current routes: %v", route.TargetNode, resp.IPRanges)
}
klog.V(4).Infof("Deleted route for node %s. Current routes: %v", route.TargetNode, resp.IPRanges)

return nil
}

Expand Down
72 changes: 72 additions & 0 deletions cloud/linode/route_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,38 @@ func TestCreateRoute(t *testing.T) {
assert.NoError(t, err)
})

interfaceWithVPCAndRoute := linodego.LinodeInterface{
ID: services.VpcIDs["dummy"],
VPC: &linodego.VPCInterface{
IPv4: linodego.VPCInterfaceIPv4{
Ranges: []linodego.VPCInterfaceIPv4Range{{Range: "10.10.10.0/24"}},
},
},
}
validInstance.InterfaceGeneration = linodego.GenerationLinode
t.Run("should return no error if instance exists, connected to VPC we add a route with linode interfaces", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mocks.NewMockClient(ctrl)
instanceCache := services.NewInstances(client)
existingK8sCache := registeredK8sNodeCache
defer func() {
registeredK8sNodeCache = existingK8sCache
}()
registeredK8sNodeCache = newK8sNodeCache()
registeredK8sNodeCache.addNodeToCache(node)
routeController, err := newRoutes(client, instanceCache)
require.NoError(t, err)

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().UpdateInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&interfaceWithVPCAndRoute, nil)
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
assert.NoError(t, err)
})
validInstance.InterfaceGeneration = ""

v6Route := &cloudprovider.Route{
Name: "route2",
TargetNode: types.NodeName(name),
Expand Down Expand Up @@ -552,6 +584,29 @@ func TestDeleteRoute(t *testing.T) {
assert.NoError(t, err)
})

interfaceWitVPCAndNoRoute := linodego.LinodeInterface{
ID: services.VpcIDs["dummy"],
VPC: &linodego.VPCInterface{IPv4: linodego.VPCInterfaceIPv4{Ranges: nil}},
}

validInstance.InterfaceGeneration = linodego.GenerationLinode
t.Run("should return no error if instance exists, connected to VPC, route doesn't exist and we try to delete route with linode interfaces", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mocks.NewMockClient(ctrl)
instanceCache := services.NewInstances(client)
routeController, err := newRoutes(client, instanceCache)
require.NoError(t, err)

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().UpdateInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&interfaceWitVPCAndNoRoute, nil)
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.NoError(t, err)
})
validInstance.InterfaceGeneration = ""

routesInVPC := []linodego.VPCIP{
{
Address: &vpcIP,
Expand Down Expand Up @@ -584,4 +639,21 @@ func TestDeleteRoute(t *testing.T) {
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.NoError(t, err)
})

validInstance.InterfaceGeneration = linodego.GenerationLinode
t.Run("should return no error if instance exists, connected to VPC and route is deleted with linode interfaces", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mocks.NewMockClient(ctrl)
instanceCache := services.NewInstances(client)
routeController, err := newRoutes(client, instanceCache)
require.NoError(t, err)

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().UpdateInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&interfaceWitVPCAndNoRoute, nil)
err = routeController.DeleteRoute(ctx, "dummy", route)
assert.NoError(t, err)
})
}
61 changes: 48 additions & 13 deletions cloud/nodeipam/ipam/cloud_allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,16 @@ func getIPv6RangeFromInterface(iface linodego.InstanceConfigInterface) string {
return ""
}

func getIPv6RangeFromLinodeInterface(iface linodego.LinodeInterface) string {
if len(iface.VPC.IPv6.SLAAC) > 0 {
return iface.VPC.IPv6.SLAAC[0].Range
}
if len(iface.VPC.IPv6.Ranges) > 0 {
return iface.VPC.IPv6.Ranges[0].Range
}
return ""
}

// allocateIPv6CIDR allocates an IPv6 CIDR for the given node.
// It retrieves the instance configuration for the node and extracts the IPv6 range.
// It then creates a new net.IPNet with the IPv6 address and mask size defined
Expand All @@ -355,24 +365,49 @@ func (c *cloudAllocator) allocateIPv6CIDR(ctx context.Context, node *v1.Node) (*
if err != nil {
return nil, fmt.Errorf("failed to parse Linode ID from ProviderID %s: %w", node.Spec.ProviderID, err)
}
// Retrieve the instance configuration for the Linode ID
configs, err := c.linodeClient.ListInstanceConfigs(ctx, id, &linodego.ListOptions{})
if err != nil || len(configs) == 0 {
return nil, fmt.Errorf("failed to list instance configs: %w", err)
}

// fetch the instance so we can determine which interface generation to use
instance, err := c.linodeClient.GetInstance(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed get linode with id %d: %w", id, err)
}
ipv6Range := ""
for _, iface := range configs[0].Interfaces {
if iface.Purpose == linodego.InterfacePurposeVPC {
ipv6Range = getIPv6RangeFromInterface(iface)
if ipv6Range != "" {
break
if instance.InterfaceGeneration == linodego.GenerationLinode {
ifaces, listErr := c.linodeClient.ListInterfaces(ctx, id, &linodego.ListOptions{})
if listErr != nil || len(ifaces) == 0 {
return nil, fmt.Errorf("failed to list interfaces: %w", listErr)
}
for _, iface := range ifaces {
if iface.VPC != nil {
ipv6Range = getIPv6RangeFromLinodeInterface(iface)
if ipv6Range != "" {
break
}
}
}
}

if ipv6Range == "" {
return nil, fmt.Errorf("failed to find ipv6 range in instance config: %v", configs[0])
if ipv6Range == "" {
return nil, fmt.Errorf("failed to find ipv6 range in Linode interfaces: %v", ifaces)
}
} else {
// Retrieve the instance configuration for the Linode ID
configs, listErr := c.linodeClient.ListInstanceConfigs(ctx, id, &linodego.ListOptions{})
if listErr != nil || len(configs) == 0 {
return nil, fmt.Errorf("failed to list instance configs: %w", listErr)
}

for _, iface := range configs[0].Interfaces {
if iface.Purpose == linodego.InterfacePurposeVPC {
ipv6Range = getIPv6RangeFromInterface(iface)
if ipv6Range != "" {
break
}
}
}

if ipv6Range == "" {
return nil, fmt.Errorf("failed to find ipv6 range in instance config: %v", configs[0])
}
}

ip, _, err := net.ParseCIDR(ipv6Range)
Expand Down
Loading
Loading