diff --git a/cloud/linode/client/client.go b/cloud/linode/client/client.go index 7c79e15c..70f98122 100644 --- a/cloud/linode/client/client.go +++ b/cloud/linode/client/client.go @@ -21,6 +21,7 @@ const ( // DefaultClientTimeout is the default timeout for a client Linode API call DefaultClientTimeout = 120 * time.Second DefaultLinodeAPIURL = "https://api.linode.com" + MaxPageSize = 500 ) type TokenProvider func(context.Context) (string, error) diff --git a/cloud/linode/loadbalancers_test.go b/cloud/linode/loadbalancers_test.go index 9f01ab97..ffef68f0 100644 --- a/cloud/linode/loadbalancers_test.go +++ b/cloud/linode/loadbalancers_test.go @@ -4135,7 +4135,7 @@ func Test_getNodeBackendIP(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "node-1", Annotations: map[string]string{ - annotations.AnnLinodeNodePrivateIP: "192.168.10.10", + annotations.AnnLinodeNodePrivateIP: "192.168.10.10", annotations.AnnLinodeNodePublicIPv6: "2600:3c06::1/128", }, }, diff --git a/cloud/linode/node_controller_test.go b/cloud/linode/node_controller_test.go index ffd478b6..0932d9df 100644 --- a/cloud/linode/node_controller_test.go +++ b/cloud/linode/node_controller_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/client-go/util/workqueue" "github.com/linode/linode-cloud-controller-manager/cloud/annotations" + linodeClient "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" "github.com/linode/linode-cloud-controller-manager/cloud/linode/client/mocks" "github.com/linode/linode-cloud-controller-manager/cloud/linode/services" ) @@ -51,7 +52,7 @@ func TestNodeController_Run(t *testing.T) { stopCh := make(chan struct{}) go nodeCtrl.Run(stopCh) - client.EXPECT().ListInstances(gomock.Any(), nil).AnyTimes().Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusTooManyRequests, Message: "Too many requests"}) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).AnyTimes().Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusTooManyRequests, Message: "Too many requests"}) // Add the node to the informer err = nodeCtrl.informer.Informer().GetStore().Add(node) require.NoError(t, err, "expected no error when adding node to informer") @@ -93,7 +94,7 @@ func TestNodeController_processNext(t *testing.T) { t.Run("should return no error on unknown errors", func(t *testing.T) { controller.addNodeToQueue(node) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed")) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed")) result := controller.processNext() assert.True(t, result, "processNext should return true") if queue.Len() != 0 { @@ -115,7 +116,7 @@ func TestNodeController_processNext(t *testing.T) { controller.addNodeToQueue(node) publicIP := net.ParseIP("172.234.31.123") privateIP := net.ParseIP("192.168.159.135") - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 111, Label: "test", IPv4: []*net.IP{&publicIP, &privateIP}, HostUUID: "111"}, }, nil) result := controller.processNext() @@ -143,7 +144,7 @@ func TestNodeController_processNext(t *testing.T) { controller.addNodeToQueue(node2) publicIP := net.ParseIP("172.234.31.123") privateIP := net.ParseIP("192.168.159.135") - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 112, Label: "test-node2", IPv4: []*net.IP{&publicIP, &privateIP}, HostUUID: "112"}, }, nil) result := controller.processNext() @@ -171,7 +172,7 @@ func TestNodeController_processNext(t *testing.T) { client := mocks.NewMockClient(ctrl) controller.instances = services.NewInstances(client) retryInterval = 1 * time.Nanosecond - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusTooManyRequests, Message: "Too many requests"}) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusTooManyRequests, Message: "Too many requests"}) result := controller.processNext() time.Sleep(1 * time.Second) assert.True(t, result, "processNext should return true") @@ -187,7 +188,7 @@ func TestNodeController_processNext(t *testing.T) { client := mocks.NewMockClient(ctrl) controller.instances = services.NewInstances(client) retryInterval = 1 * time.Nanosecond - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusInternalServerError, Message: "Too many requests"}) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, &linodego.Error{Code: http.StatusInternalServerError, Message: "Too many requests"}) result := controller.processNext() time.Sleep(1 * time.Second) assert.True(t, result, "processNext should return true") @@ -228,7 +229,7 @@ func TestNodeController_handleNode(t *testing.T) { publicIP := net.ParseIP("172.234.31.123") privateIP := net.ParseIP("192.168.159.135") publicIPv6SLAAC := "2001:db::f03c:91ff:fe2b:1a2b" - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-node", IPv4: []*net.IP{&publicIP, &privateIP}, IPv6: publicIPv6SLAAC, HostUUID: "123"}, }, nil) err = nodeCtrl.handleNode(t.Context(), node) @@ -251,7 +252,7 @@ func TestNodeController_handleNode(t *testing.T) { client = mocks.NewMockClient(ctrl) nodeCtrl.instances = services.NewInstances(client) nodeCtrl.metadataLastUpdate["test-node"] = time.Now().Add(-2 * nodeCtrl.ttl) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed")) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, errors.New("lookup failed")) err = nodeCtrl.handleNode(t.Context(), node) require.Error(t, err, "expected error during handleNode, got nil") @@ -259,7 +260,7 @@ func TestNodeController_handleNode(t *testing.T) { client = mocks.NewMockClient(ctrl) nodeCtrl.instances = services.NewInstances(client) nodeCtrl.metadataLastUpdate["test-node"] = time.Now().Add(-2 * nodeCtrl.ttl) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-node", IPv4: []*net.IP{&publicIP, &privateIP}, IPv6: publicIPv6SLAAC, HostUUID: "123"}, }, nil) err = nodeCtrl.handleNode(t.Context(), node) diff --git a/cloud/linode/options/options.go b/cloud/linode/options/options.go index aac562f9..08a9c1d5 100644 --- a/cloud/linode/options/options.go +++ b/cloud/linode/options/options.go @@ -37,4 +37,5 @@ var Options struct { NodeCIDRMaskSizeIPv4 int NodeCIDRMaskSizeIPv6 int NodeBalancerPrefix string + LinodeTagFilter string } diff --git a/cloud/linode/route_controller_test.go b/cloud/linode/route_controller_test.go index 4b88385b..bf2bf894 100644 --- a/cloud/linode/route_controller_test.go +++ b/cloud/linode/route_controller_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/golang/mock/gomock" + linodeClient "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" "github.com/linode/linodego" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -53,7 +54,7 @@ func TestListRoutes(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), gomock.Any()).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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") @@ -78,7 +79,7 @@ func TestListRoutes(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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") @@ -106,7 +107,7 @@ func TestListRoutes(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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") @@ -155,7 +156,7 @@ func TestListRoutes(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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") @@ -204,7 +205,7 @@ func TestListRoutes(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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") @@ -300,7 +301,7 @@ func TestListRoutes(t *testing.T) { registeredK8sNodeCache.addNodeToCache(node2) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance, validInstance2, validInstance3}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{validInstance, validInstance2, validInstance3}, nil) c1 := client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(routesInVPC, nil) 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) @@ -385,7 +386,7 @@ func TestCreateRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -416,7 +417,7 @@ func TestCreateRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -485,7 +486,7 @@ func TestCreateRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -500,7 +501,7 @@ func TestCreateRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -544,7 +545,7 @@ func TestDeleteRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -576,7 +577,7 @@ func TestDeleteRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -598,7 +599,7 @@ func TestDeleteRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -632,7 +633,7 @@ func TestDeleteRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) @@ -649,7 +650,7 @@ func TestDeleteRoute(t *testing.T) { routeController, err := newRoutes(client, instanceCache) require.NoError(t, err) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).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) diff --git a/cloud/linode/services/instances.go b/cloud/linode/services/instances.go index 9d4de001..8fe135ec 100644 --- a/cloud/linode/services/instances.go +++ b/cloud/linode/services/instances.go @@ -17,7 +17,7 @@ import ( cloudprovider "k8s.io/cloud-provider" "k8s.io/klog/v2" - "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" + linodeClient "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" "github.com/linode/linode-cloud-controller-manager/cloud/linode/options" ccmUtils "github.com/linode/linode-cloud-controller-manager/cloud/linode/utils" "github.com/linode/linode-cloud-controller-manager/sentry" @@ -80,7 +80,7 @@ func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []s // 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.Client) error { +func (nc *nodeCache) refreshInstances(ctx context.Context, client linodeClient.Client) error { nc.Lock() defer nc.Unlock() @@ -88,7 +88,16 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client) return nil } - instances, err := client.ListInstances(ctx, nil) + filter := linodego.Filter{} + if options.Options.LinodeTagFilter != "" { + filter.AddField(linodego.Contains, "tags", options.Options.LinodeTagFilter) + } + filterJSON, err := filter.MarshalJSON() + if err != nil { + return fmt.Errorf("failed to marshal filter: %w", err) + } + + instances, err := client.ListInstances(ctx, &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: string(filterJSON)}) if err != nil { return err } @@ -150,13 +159,13 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client) } type Instances struct { - client client.Client + client linodeClient.Client nodeCache *nodeCache } // NewInstances creates a new Instances cache with a specified TTL for the nodeCache. -func NewInstances(client client.Client) *Instances { +func NewInstances(client linodeClient.Client) *Instances { timeout := 15 if raw, ok := os.LookupEnv("LINODE_INSTANCE_CACHE_TTL"); ok { if t, err := strconv.Atoi(raw); t > 0 && err == nil { diff --git a/cloud/linode/services/instances_test.go b/cloud/linode/services/instances_test.go index 7c6bab58..814fa424 100644 --- a/cloud/linode/services/instances_test.go +++ b/cloud/linode/services/instances_test.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cloudprovider "k8s.io/cloud-provider" + linodeClient "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" "github.com/linode/linode-cloud-controller-manager/cloud/linode/client/mocks" "github.com/linode/linode-cloud-controller-manager/cloud/linode/options" ccmUtils "github.com/linode/linode-cloud-controller-manager/cloud/linode/utils" @@ -47,7 +48,7 @@ func TestInstanceExists(t *testing.T) { t.Run("should return false if linode does not exist (by providerID)", func(t *testing.T) { instances := NewInstances(client) node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + "123") - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, nil) exists, err := instances.InstanceExists(ctx, node) require.NoError(t, err) @@ -57,7 +58,7 @@ func TestInstanceExists(t *testing.T) { t.Run("should return true if linode exists (by provider)", func(t *testing.T) { instances := NewInstances(client) node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + "123") - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ { ID: 123, Label: "mock", @@ -76,7 +77,7 @@ func TestInstanceExists(t *testing.T) { name := "some-name" node := nodeWithName(name) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: name}, }, nil) @@ -98,7 +99,7 @@ func TestMetadataRetrieval(t *testing.T) { publicIP := net.ParseIP("172.234.31.123") privateIP := net.ParseIP("192.168.159.135") expectedInstance := linodego.Instance{Label: "expected-instance", ID: 12345, IPv4: []*net.IP{&publicIP, &privateIP}} - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{{Label: "wrong-instance", ID: 3456, IPv4: []*net.IP{&publicIP, &privateIP}}, expectedInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{{Label: "wrong-instance", ID: 3456, IPv4: []*net.IP{&publicIP, &privateIP}}, expectedInstance}, nil) name := "expected-instance" node := nodeWithName(name) @@ -112,7 +113,7 @@ func TestMetadataRetrieval(t *testing.T) { id := 456302 providerID := ccmUtils.ProviderIDPrefix + strconv.Itoa(id) node := nodeWithProviderID(providerID) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, nil) meta, err := instances.InstanceMetadata(ctx, node) require.ErrorIs(t, err, cloudprovider.InstanceNotFound) @@ -127,7 +128,7 @@ func TestMetadataRetrieval(t *testing.T) { privateIPv4 := net.ParseIP("192.168.133.65") linodeType := typeG6 region := usEast - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: id, Label: instanceName, Type: linodeType, Region: region, IPv4: []*net.IP{&publicIPv4, &privateIPv4}}, }, nil) @@ -162,6 +163,7 @@ func TestMetadataRetrieval(t *testing.T) { linodeType := typeG6 options.Options.VPCNames = []string{"test"} + options.Options.LinodeTagFilter = "test-cluster" VpcIDs["test"] = 1 options.Options.EnableRouteController = true @@ -200,7 +202,13 @@ func TestMetadataRetrieval(t *testing.T) { }, } - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{instance}, nil) + filter := linodego.Filter{} + filter.AddField(linodego.Contains, "tags", "test-cluster") + filterJSON, err := filter.MarshalJSON() + if err != nil { + t.Fatalf("failed to marshal filter: %v", err) + } + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: string(filterJSON)}).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) @@ -233,6 +241,7 @@ func TestMetadataRetrieval(t *testing.T) { }, meta.NodeAddresses) options.Options.VPCNames = []string{} + options.Options.LinodeTagFilter = "" }) ipTests := []struct { @@ -353,7 +362,7 @@ func TestMetadataRetrieval(t *testing.T) { linodeType := typeG6 region := usEast - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: id, Label: name, Type: linodeType, Region: region, IPv4: ips, IPv6: test.inputIPv6}, }, nil) @@ -422,7 +431,7 @@ func TestMetadataRetrieval(t *testing.T) { for _, test := range getByIPTests { t.Run(fmt.Sprintf("gets linode by IP - %s", test.name), func(t *testing.T) { instances := NewInstances(client) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{{ID: 3456, IPv4: []*net.IP{&wrongIP}}, expectedInstance}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{{ID: 3456, IPv4: []*net.IP{&wrongIP}}, expectedInstance}, nil) node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node-1"}, Status: v1.NodeStatus{Addresses: test.nodeAddresses}} meta, err := instances.InstanceMetadata(ctx, &node) if test.expectedErr != nil { @@ -448,7 +457,7 @@ func TestMalformedProviders(t *testing.T) { instances := NewInstances(client) providerID := ccmUtils.ProviderIDPrefix + "abc" node := nodeWithProviderID(providerID) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, nil) meta, err := instances.InstanceMetadata(ctx, node) require.ErrorIs(t, err, ccmUtils.InvalidProviderIDError{Value: providerID}) @@ -475,7 +484,7 @@ func TestRefreshInstancesVPCErrors(t *testing.T) { }() apiErr := fmt.Errorf("VPC API error") - c.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + c.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-instance"}, }, nil) c.EXPECT().ListVPCIPAddresses(gomock.Any(), 1, gomock.Any()).Return(nil, apiErr) @@ -498,7 +507,7 @@ func TestRefreshInstancesVPCErrors(t *testing.T) { vpcIP := "10.0.0.2" apiErr := fmt.Errorf("VPC IPv6 API error") - c.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + c.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-instance"}, }, nil) c.EXPECT().ListVPCIPAddresses(gomock.Any(), 1, gomock.Any()).Return([]linodego.VPCIP{ @@ -523,7 +532,7 @@ func TestRefreshInstancesVPCErrors(t *testing.T) { }() apiErr := fmt.Errorf("VPC API error") - c.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + c.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-instance"}, }, nil) c.EXPECT().ListVPCIPAddresses(gomock.Any(), 1, gomock.Any()).Return(nil, apiErr) @@ -549,7 +558,7 @@ func TestRefreshInstancesVPCErrors(t *testing.T) { vpcIP := "10.0.0.2" apiErr := fmt.Errorf("VPC IPv6 API error") - c.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + c.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: 123, Label: "test-instance"}, }, nil) c.EXPECT().ListVPCIPAddresses(gomock.Any(), 1, gomock.Any()).Return([]linodego.VPCIP{ @@ -577,7 +586,7 @@ func TestInstanceShutdown(t *testing.T) { instances := NewInstances(client) id := 12345 node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + strconv.Itoa(id)) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, nil) shutdown, err := instances.InstanceShutdown(ctx, node) require.Error(t, err) @@ -588,7 +597,7 @@ func TestInstanceShutdown(t *testing.T) { instances := NewInstances(client) name := "some-name" node := nodeWithName(name) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil) + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{}, nil) shutdown, err := instances.InstanceShutdown(ctx, node) require.Error(t, err) @@ -599,7 +608,7 @@ func TestInstanceShutdown(t *testing.T) { instances := NewInstances(client) id := 12345 node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + strconv.Itoa(id)) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: id, Label: "offline-linode", Status: linodego.InstanceOffline}, }, nil) shutdown, err := instances.InstanceShutdown(ctx, node) @@ -612,7 +621,7 @@ func TestInstanceShutdown(t *testing.T) { instances := NewInstances(client) id := 12345 node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + strconv.Itoa(id)) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: id, Label: "shutting-down-linode", Status: linodego.InstanceShuttingDown}, }, nil) shutdown, err := instances.InstanceShutdown(ctx, node) @@ -625,7 +634,7 @@ func TestInstanceShutdown(t *testing.T) { instances := NewInstances(client) id := 12345 node := nodeWithProviderID(ccmUtils.ProviderIDPrefix + strconv.Itoa(id)) - client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{ + client.EXPECT().ListInstances(gomock.Any(), &linodego.ListOptions{PageSize: linodeClient.MaxPageSize, Filter: "{}"}).Times(1).Return([]linodego.Instance{ {ID: id, Label: "running-linode", Status: linodego.InstanceRunning}, }, nil) shutdown, err := instances.InstanceShutdown(ctx, node) diff --git a/cloud/nodeipam/ipam/cloud_allocator.go b/cloud/nodeipam/ipam/cloud_allocator.go index 1b88a9a4..682d35cb 100644 --- a/cloud/nodeipam/ipam/cloud_allocator.go +++ b/cloud/nodeipam/ipam/cloud_allocator.go @@ -104,7 +104,7 @@ func NewLinodeCIDRAllocator(ctx context.Context, linodeClient linode.Client, cli } // Using Linode API, check if we need to reserve the final block in the cluster CIDR. - // Reserve when cluster CIDR last IP is the same as the VPC subnet last IP. + // Reserve when cluster CIDR last IP is the same as the VPC subnet last IP. // We cannot reserve that block since the last IP is a reserved IP for VPC functionality. reserveFinalIPv4Block, err := shouldReserveFinalIPv4Block(ctx, linodeClient, allocatorParams.ClusterCIDRs[0]) if err != nil { diff --git a/deploy/chart/templates/daemonset.yaml b/deploy/chart/templates/daemonset.yaml index e3293fb7..4fed5944 100644 --- a/deploy/chart/templates/daemonset.yaml +++ b/deploy/chart/templates/daemonset.yaml @@ -195,6 +195,9 @@ spec: {{- if .Values.nodeBalancerPrefix }} - --nodebalancer-prefix={{ .Values.nodeBalancerPrefix }} {{- end }} + {{- if .Values.linodeTagFilter }} + - --linode-tag-filter={{ .Values.linodeTagFilter }} + {{- end }} {{- if .Values.extraArgs }} {{- toYaml .Values.extraArgs | nindent 12 }} {{- end }} diff --git a/deploy/chart/values.yaml b/deploy/chart/values.yaml index c5a428cd..196fbf01 100644 --- a/deploy/chart/values.yaml +++ b/deploy/chart/values.yaml @@ -137,6 +137,9 @@ tolerations: # nodeBalancerPrefix is used to add prefix for nodeBalancer name. Default is "ccm" # nodeBalancerPrefix: "" +# linodeTagFilter is used to filter the instances returned to the CCM. Default is no filter. +# linodeTagFilter: "" + # This section adds the ability to pass environment variables to adjust CCM defaults # https://github.com/linode/linode-cloud-controller-manager/blob/master/cloud/linode/loadbalancers.go # LINODE_HOSTNAME_ONLY_INGRESS type bool is supported diff --git a/main.go b/main.go index afe5941c..bf550314 100644 --- a/main.go +++ b/main.go @@ -103,6 +103,7 @@ func main() { command.Flags().BoolVar(&ccmOptions.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") command.Flags().StringVar(&ccmOptions.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", fmt.Sprintf("Name prefix for NoadBalancers. (max. %v char.)", linode.NodeBalancerPrefixCharLimit)) command.Flags().BoolVar(&ccmOptions.Options.DisableIPv6NodeCIDRAllocation, "disable-ipv6-node-cidr-allocation", false, "disables IPv6 node cidr allocation by ipam controller (when enabled, IPv6 cidr ranges will be allocated to nodes)") + command.Flags().StringVar(&ccmOptions.Options.LinodeTagFilter, "linode-tag-filter", "", "tag filter for linodes (e.g. cluster name)") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) {