Skip to content

Commit

Permalink
feat: Add route_controller to linode CCM (#199)
Browse files Browse the repository at this point in the history
* add back changes reverted in PR #195

* get instanceConfig only when running within VPC

* add and fix unittests

* use lock when reading/writing vpc id

* updated route-controller using /v4/vpcs/ips api

* fix tests

* switch to new api returning ips for specific vpc

* when running with vpc set, only cache instances which are part of VPC

* address review comments

* update linodego to v1.33.0

* address review comment, make variable required if routecontroller is enabled

---------

Co-authored-by: Rahul Sharma <[email protected]>
  • Loading branch information
rahulait and rahulait authored Apr 24, 2024
1 parent 1e9005a commit e4a2ad5
Show file tree
Hide file tree
Showing 18 changed files with 1,054 additions and 106 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ Users can create CloudFirewall instances, supply their own rules and attach them
**Note**<br/>
If the user supplies a firewall-id, and later switches to using an ACL, the CCM will take over the CloudFirewall Instance. To avoid this, delete the service, and re-create it so the original CloudFirewall is left undisturbed.

#### Routes
When running k8s clusters within VPC, node specific podCIDRs need to be allowed on the VPC interface. Linode CCM comes with route-controller functionality which can be enabled for automatically adding/deleting routes on VPC interfaces. When installing CCM with helm, make sure to specify routeController settings.

##### Example usage in values.yaml
```yaml
routeController:
vpcName: <name of VPC>
clusterCIDR: 10.0.0.0/8
configureCloudRoutes: true
```

### Nodes
Kubernetes Nodes can be configured with the following annotations.

Expand Down
5 changes: 5 additions & 0 deletions cloud/linode/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ type Client interface {
ListInstances(context.Context, *linodego.ListOptions) ([]linodego.Instance, error)
GetInstanceIPAddresses(context.Context, int) (*linodego.InstanceIPAddressResponse, error)

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

ListVPCs(context.Context, *linodego.ListOptions) ([]linodego.VPC, error)
ListVPCIPAddresses(context.Context, int, *linodego.ListOptions) ([]linodego.VPCIP, error)

CreateNodeBalancer(context.Context, linodego.NodeBalancerCreateOptions) (*linodego.NodeBalancer, error)
GetNodeBalancer(context.Context, int) (*linodego.NodeBalancer, error)
UpdateNodeBalancer(context.Context, int, linodego.NodeBalancerUpdateOptions) (*linodego.NodeBalancer, error)
Expand Down
45 changes: 45 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.

60 changes: 55 additions & 5 deletions cloud/linode/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"sync"

"github.com/linode/linodego"
"github.com/spf13/pflag"
Expand All @@ -25,14 +26,46 @@ const (
// We expect it to be initialized with flags external to this package, likely in
// main.go
var Options struct {
KubeconfigFlag *pflag.Flag
LinodeGoDebug bool
KubeconfigFlag *pflag.Flag
LinodeGoDebug bool
EnableRouteController bool
VPCName string
}

// vpcDetails is set when VPCName options flag is set.
// We use it to list instances running within the VPC if set
type vpcDetails struct {
mu sync.RWMutex
id int
name string
}

func (v *vpcDetails) setDetails(client client.Client, name string) error {
v.mu.Lock()
defer v.mu.Unlock()

id, err := getVPCID(client, Options.VPCName)
if err != nil {
return fmt.Errorf("failed finding VPC ID: %w", err)
}
v.id = id
v.name = name
return nil
}

func (v *vpcDetails) getID() int {
v.mu.Lock()
defer v.mu.Unlock()
return v.id
}

var vpcInfo vpcDetails = vpcDetails{id: 0, name: ""}

type linodeCloud struct {
client client.Client
instances cloudprovider.InstancesV2
loadbalancers cloudprovider.LoadBalancer
routes cloudprovider.Routes
}

func init() {
Expand Down Expand Up @@ -67,12 +100,26 @@ func newCloud() (cloudprovider.Interface, error) {
linodeClient.SetDebug(true)
}

// Return struct that satisfies cloudprovider.Interface
return &linodeCloud{
if Options.VPCName != "" {
err := vpcInfo.setDetails(linodeClient, Options.VPCName)
if err != nil {
return nil, fmt.Errorf("failed finding VPC ID: %w", err)
}
}

routes, err := newRoutes(linodeClient)
if err != nil {
return nil, fmt.Errorf("routes client was not created successfully: %w", err)
}

// create struct that satisfies cloudprovider.Interface
lcloud := &linodeCloud{
client: linodeClient,
instances: newInstances(linodeClient),
loadbalancers: newLoadbalancers(linodeClient, region),
}, nil
routes: routes,
}
return lcloud, nil
}

func (c *linodeCloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stopCh <-chan struct{}) {
Expand Down Expand Up @@ -109,6 +156,9 @@ func (c *linodeCloud) Clusters() (cloudprovider.Clusters, bool) {
}

func (c *linodeCloud) Routes() (cloudprovider.Routes, bool) {
if Options.EnableRouteController {
return c.routes, true
}
return nil, false
}

Expand Down
30 changes: 30 additions & 0 deletions cloud/linode/cloud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package linode

import (
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)

func TestNewCloudRouteControllerDisabled(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

t.Setenv("LINODE_API_TOKEN", "dummyapitoken")
t.Setenv("LINODE_REGION", "us-east")

t.Run("should not fail if vpc is empty and routecontroller is disabled", func(t *testing.T) {
Options.VPCName = ""
Options.EnableRouteController = false
_, err := newCloud()
assert.NoError(t, err)
})

t.Run("fail if vpcname is empty and routecontroller is enabled", func(t *testing.T) {
Options.VPCName = ""
Options.EnableRouteController = true
_, err := newCloud()
assert.Error(t, err)
})
}
Loading

0 comments on commit e4a2ad5

Please sign in to comment.