Skip to content

Commit

Permalink
Basic E2E test for machine controller
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Kovacs committed Feb 6, 2024
1 parent e1fccfc commit 63fa807
Show file tree
Hide file tree
Showing 29 changed files with 400 additions and 65 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/build_test_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
- name: Docker cache
uses: ScribeMD/[email protected]
with:
key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }}}
key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }}}

- name: Lint
run: make lint
Expand All @@ -100,6 +100,7 @@ jobs:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.linode.com:443
api.github.com:443
github.com:443
gcr.io:443
Expand Down Expand Up @@ -130,7 +131,7 @@ jobs:
- name: Docker cache
uses: ScribeMD/[email protected]
with:
key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }}}
key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }}}

- name: E2E test
run: make e2etest
Expand Down Expand Up @@ -173,7 +174,7 @@ jobs:
- name: Docker cache
uses: ScribeMD/[email protected]
with:
key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }}
key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }}

- name: Build the Docker image
run: make docker-build
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -race -timeout 60s ./... -coverprofile cover.out

.PHONY: e2etest
e2etest: kind ctlptl tilt kuttl kustomize clusterctl manifests generate
e2etest: kind ctlptl tilt kuttl kustomize clusterctl envsubst manifests generate
@echo -n "LINODE_TOKEN=$(LINODE_TOKEN)" > config/default/.env.linode
$(CTLPTL) apply -f .tilt/ctlptl-config.yaml
$(TILT) ci --timeout 180s -f Tiltfile
$(KUTTL) test --config e2e/kuttl-config.yaml
ROOT_DIR="$(PWD)" $(KUTTL) test --config e2e/kuttl-config.yaml

##@ Build

Expand Down Expand Up @@ -180,6 +180,7 @@ TILT ?= $(LOCALBIN)/tilt
KIND ?= $(LOCALBIN)/kind
KUTTL ?= $(LOCALBIN)/kuttl
ENVTEST ?= $(LOCALBIN)/setup-envtest
ENVSUBST ?= $(LOCALBIN)/envsubst
HUSKY ?= $(LOCALBIN)/husky
NILAWAY ?= $(LOCALBIN)/nilaway
GOVULNC ?= $(LOCALBIN)/govulncheck
Expand All @@ -192,6 +193,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.13.0
TILT_VERSION ?= 0.33.6
KIND_VERSION ?= 0.20.0
KUTTL_VERSION ?= 0.15.0
ENVSUBST_VERSION ?= v1.4.2
HUSKY_VERSION ?= v0.2.16
NILAWAY_VERSION ?= latest
GOVULNC_VERSION ?= v1.0.1
Expand Down Expand Up @@ -251,6 +253,12 @@ envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest

.PHONY: envsubst
envsubst: $(ENVSUBST) ## Download envsubst locally if necessary. If wrong version is installed, it will be overwritten.
$(ENVSUBST): $(LOCALBIN)
test -s $(ENVSUBST) || \
(cd $(LOCALBIN); curl -Lso ./envsubst https://github.com/a8m/envsubst/releases/download/$(ENVSUBST_VERSION)/envsubst-$(shell uname -s)-$(ARCH) && chmod +x envsubst)

.PHONY: husky
husky: $(HUSKY) ## Download husky locally if necessary.
@echo Execute install command to enable git hooks: ./bin/husky install
Expand Down
6 changes: 3 additions & 3 deletions cloud/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (
"errors"
"fmt"

"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
"github.com/linode/linodego"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
)

// ClusterScopeParams defines the input parameters used to create a new Scope.
Expand Down
2 changes: 2 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ spec:
secretKeyRef:
name: token
key: LINODE_TOKEN
- name: LINODE_API_VERSION
value: v4beta
name: manager
securityContext:
allowPrivilegeEscalation: false
Expand Down
82 changes: 41 additions & 41 deletions controller/linodemachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package controller

import (
"context"
b64 "encoding/base64"
"errors"
"fmt"
"net/http"
Expand All @@ -44,7 +43,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
)

var skippedMachinePhases = map[string]bool{
Expand Down Expand Up @@ -94,25 +93,31 @@ type LinodeMachineReconciler struct {
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
//
//nolint:gocyclo,cyclop // As simple as possible.
func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultedLoopTimeout(r.ReconcileTimeout))
defer cancel()

log := ctrl.LoggerFrom(ctx).WithName("LinodeMachineReconciler").WithValues("name", req.NamespacedName.String())

linodeMachine := &infrav1.LinodeMachine{}
linodeMachine := &infrav1alpha1.LinodeMachine{}
if err := r.Client.Get(ctx, req.NamespacedName, linodeMachine); err != nil {
log.Error(err, "Failed to fetch Linode machine")
if err = client.IgnoreNotFound(err); err != nil {
log.Error(err, "Failed to fetch Linode machine")
}

return ctrl.Result{}, client.IgnoreNotFound(err)
return ctrl.Result{}, err
}

machine, err := kutil.GetOwnerMachine(ctx, r.Client, linodeMachine.ObjectMeta)
switch {
case err != nil:
log.Error(err, "Failed to fetch owner machine")
if err = client.IgnoreNotFound(err); err != nil {
log.Error(err, "Failed to fetch owner machine")
}

return ctrl.Result{}, client.IgnoreNotFound(err)
return ctrl.Result{}, err
case machine == nil:
log.Info("Machine Controller has not yet set OwnerRef, skipping reconciliation")

Expand All @@ -138,26 +143,39 @@ func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques
log = log.WithValues("Linode machine: ", machine.Name)

cluster, err := kutil.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
if err != nil {
log.Info("Failed to fetch cluster by label")
switch {
case err != nil:
if err = client.IgnoreNotFound(err); err != nil {
log.Error(err, "Failed to fetch cluster by label")
}

return ctrl.Result{}, err
case cluster == nil:
err = errors.New("missing cluster")

log.Error(err, "Missing cluster")

return ctrl.Result{}, err
case cluster.Spec.InfrastructureRef == nil:
err = errors.New("missing infrastructure reference")

return ctrl.Result{}, client.IgnoreNotFound(err)
} else if cluster == nil {
log.Info("Failed to find cluster by label")
log.Error(err, "Missing infrastructure reference")

return ctrl.Result{}, client.IgnoreNotFound(err)
return ctrl.Result{}, err
}

linodeCluster := &infrav1.LinodeCluster{}
linodeCluster := &infrav1alpha1.LinodeCluster{}
linodeClusterKey := client.ObjectKey{
Namespace: linodeMachine.Namespace,
Name: cluster.Spec.InfrastructureRef.Name,
}

if err = r.Client.Get(ctx, linodeClusterKey, linodeCluster); err != nil {
log.Error(err, "Failed to fetch Linode cluster")
if err = client.IgnoreNotFound(err); err != nil {
log.Error(err, "Failed to fetch Linode cluster")
}

return ctrl.Result{}, client.IgnoreNotFound(err)
return ctrl.Result{}, err
}

machineScope, err := scope.NewMachineScope(
Expand Down Expand Up @@ -203,7 +221,7 @@ func (r *LinodeMachineReconciler) reconcile(
}

// Always close the scope when exiting this function so we can persist any LinodeMachine changes.
if patchErr := machineScope.Close(ctx); patchErr != nil && client.IgnoreNotFound(patchErr) != nil {
if patchErr := machineScope.Close(ctx); patchErr != nil && util.IgnoreKubeNotFound(patchErr) != nil {
logger.Error(patchErr, "failed to patch LinodeMachine")

err = errors.Join(err, patchErr)
Expand Down Expand Up @@ -278,31 +296,13 @@ func (r *LinodeMachineReconciler) reconcileCreate(ctx context.Context, machineSc

linodeInstance = &linodeInstances[0]
case 0:
createConfig := linodeMachineSpecToInstanceCreateConfig(machineScope.LinodeMachine.Spec)
if createConfig == nil {
err = errors.New("failed to convert machine spec to create instance config")

logger.Error(err, "Panic! Struct of LinodeMachineSpec is different than InstanceCreateOptions")

return nil, err
}
createConfig.Tags = tags
createConfig.SwapSize = util.Pointer(0)

// get the bootstrap data for the Linode instance and set it for create config
bootstrapData, err := machineScope.GetBootstrapData(ctx)
createConfig, err := r.newCreateConfig(ctx, machineScope, tags, logger)
if err != nil {
logger.Info("Failed to get bootstrap data", "error", err.Error())
logger.Error(err, "Failed to create Linode machine create config")

return nil, err
}
createConfig.Metadata = &linodego.InstanceMetadataOptions{
UserData: b64.StdEncoding.EncodeToString(bootstrapData),
}

if createConfig.Label == "" {
createConfig.Label = util.RenderObjectLabel(machineScope.LinodeMachine.UID)
}

if machineScope.LinodeCluster.Spec.VPCRef != nil {
iface, err := r.getVPCInterfaceConfig(ctx, machineScope, createConfig.Interfaces, logger)
Expand Down Expand Up @@ -428,21 +428,21 @@ func (r *LinodeMachineReconciler) reconcileDelete(ctx context.Context, logger lo

machineScope.LinodeMachine.Spec.ProviderID = nil
machineScope.LinodeMachine.Spec.InstanceID = nil
controllerutil.RemoveFinalizer(machineScope.LinodeMachine, infrav1.GroupVersion.String())
controllerutil.RemoveFinalizer(machineScope.LinodeMachine, infrav1alpha1.GroupVersion.String())

return nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *LinodeMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
controller, err := ctrl.NewControllerManagedBy(mgr).
For(&infrav1.LinodeMachine{}).
For(&infrav1alpha1.LinodeMachine{}).
Watches(
&clusterv1.Machine{},
handler.EnqueueRequestsFromMapFunc(kutil.MachineToInfrastructureMapFunc(infrav1.GroupVersion.WithKind("LinodeMachine"))),
handler.EnqueueRequestsFromMapFunc(kutil.MachineToInfrastructureMapFunc(infrav1alpha1.GroupVersion.WithKind("LinodeMachine"))),
).
Watches(
&infrav1.LinodeCluster{},
&infrav1alpha1.LinodeCluster{},
handler.EnqueueRequestsFromMapFunc(r.linodeClusterToLinodeMachines(mgr.GetLogger())),
).
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(mgr.GetLogger(), r.WatchFilterValue)).
Expand Down
55 changes: 51 additions & 4 deletions controller/linodemachine_controller_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package controller
import (
"bytes"
"context"
b64 "encoding/base64"
"encoding/gob"
"errors"
"sort"

"github.com/go-logr/logr"
infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
"github.com/google/uuid"
"github.com/linode/cluster-api-provider-linode/cloud/scope"
"github.com/linode/cluster-api-provider-linode/util"
"github.com/linode/cluster-api-provider-linode/util/reconciler"
"github.com/linode/linodego"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -35,16 +37,61 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"

infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1"
)

func (*LinodeMachineReconciler) newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, tags []string, logger logr.Logger) (*linodego.InstanceCreateOptions, error) {
var err error

createConfig := linodeMachineSpecToInstanceCreateConfig(machineScope.LinodeMachine.Spec)
if createConfig == nil {
err = errors.New("failed to convert machine spec to create instance config")

logger.Error(err, "Panic! Struct of LinodeMachineSpec is different than InstanceCreateOptions")

return nil, err
}
createConfig.SwapSize = util.Pointer(0)

bootstrapData, err := machineScope.GetBootstrapData(ctx)
if err != nil {
logger.Info("Failed to get bootstrap data", "error", err.Error())

return nil, err
}
createConfig.Metadata = &linodego.InstanceMetadataOptions{
UserData: b64.StdEncoding.EncodeToString(bootstrapData),
}

if createConfig.Tags == nil {
createConfig.Tags = []string{}
}
createConfig.Tags = append(createConfig.Tags, tags...)

if createConfig.Label == "" {
createConfig.Label = util.RenderObjectLabel(machineScope.LinodeMachine.UID)
}

if createConfig.Image == "" {
createConfig.Image = reconciler.DefaultMachineControllerLinodeImage
}

if createConfig.RootPass == "" {
createConfig.RootPass = uuid.NewString()
}

return createConfig, nil
}

func (r *LinodeMachineReconciler) linodeClusterToLinodeMachines(logger logr.Logger) handler.MapFunc {
logger = logger.WithName("LinodeMachineReconciler").WithName("linodeClusterToLinodeMachines")

return func(ctx context.Context, o client.Object) []ctrl.Request {
ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
defer cancel()

linodeCluster, ok := o.(*infrav1.LinodeCluster)
linodeCluster, ok := o.(*infrav1alpha1.LinodeCluster)
if !ok {
logger.Info("Failed to cast object to Cluster")

Expand Down Expand Up @@ -142,7 +189,7 @@ func (r *LinodeMachineReconciler) getVPCInterfaceConfig(ctx context.Context, mac

logger = logger.WithValues("vpcName", name, "vpcNamespace", namespace)

linodeVPC := infrav1.LinodeVPC{
linodeVPC := infrav1alpha1.LinodeVPC{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
Expand Down Expand Up @@ -202,7 +249,7 @@ func (r *LinodeMachineReconciler) getVPCInterfaceConfig(ctx context.Context, mac
}, nil
}

func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1.LinodeMachineSpec) *linodego.InstanceCreateOptions {
func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1alpha1.LinodeMachineSpec) *linodego.InstanceCreateOptions {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(machineSpec)
Expand Down
Loading

0 comments on commit 63fa807

Please sign in to comment.