Skip to content

Commit

Permalink
Add initial controllers implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Mateusz Gozdek <[email protected]>
  • Loading branch information
invidian committed Dec 22, 2020
1 parent fec0144 commit 70df4a5
Show file tree
Hide file tree
Showing 21 changed files with 1,929 additions and 151 deletions.
18 changes: 8 additions & 10 deletions api/v1alpha3/tinkerbellcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package v1alpha3

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
Expand All @@ -27,16 +28,13 @@ import (
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file.
type TinkerbellClusterSpec struct {
// HardwareDiscoveryStrategy is a switch we have to implement more advacned
// discovery strategy. The unique one we have today is the default one
// obviously and it uses the two lists of hardware IDs specified down here.
HardwareDiscoveryStrategy string `json:"hardwareDiscoveryStrategy,omitempty"`
// ControlPlaneHardwareIDs contains a list of hardware IDs used as pool for
// control plane kubernetes instances.
ControlPlaneHardwareIDs []string `json:"controlPlaneHardwareIDs,omitempty"`
// MachineHardwareIDs contains a list of hardware IDs used as pool for data
// plane kubernetes instances.
MachineHardwareIDs []string `json:"machineHardwareIDs,omitempty"`
// ControlPlaneEndpoint is a required field by ClusterAPI v1alpha3.
//
// See https://cluster-api.sigs.k8s.io/developer/architecture/controllers/cluster.html
// for more details.
//
// +optional
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitempty"`
}

// TinkerbellClusterStatus defines the observed state of TinkerbellCluster.
Expand Down
7 changes: 6 additions & 1 deletion api/v1alpha3/tinkerbellmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ const (

// TinkerbellMachineSpec defines the desired state of TinkerbellMachine.
type TinkerbellMachineSpec struct {
HardwareID string `json:"hardwareReservationID,omitempty"`
// Those fields are set programmatically, but they cannot be re-constructed from "state of the world", so
// we put them in spec instead of status.
HardwareID string `json:"hardwareID,omitempty"`
TemplateID string `json:"templateID,omitempty"`
WorkflowID string `json:"workflowID,omitempty"`
ProviderID string `json:"providerID,omitempty"`
}

// TinkerbellMachineStatus defines the observed state of TinkerbellMachine.
Expand Down
13 changes: 2 additions & 11 deletions api/v1alpha3/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,22 @@ spec:
INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important:
Run "make" to regenerate code after modifying this file.'
properties:
controlPlaneHardwareIDs:
description: ControlPlaneHardwareIDs contains a list of hardware IDs
used as pool for control plane kubernetes instances.
items:
type: string
type: array
hardwareDiscoveryStrategy:
description: HardwareDiscoveryStrategy is a switch we have to implement
more advacned discovery strategy. The unique one we have today is
the default one obviously and it uses the two lists of hardware
IDs specified down here.
type: string
machineHardwareIDs:
description: MachineHardwareIDs contains a list of hardware IDs used
as pool for data plane kubernetes instances.
items:
type: string
type: array
controlPlaneEndpoint:
description: "ControlPlaneEndpoint is a required field by ClusterAPI
v1alpha3. \n See https://cluster-api.sigs.k8s.io/developer/architecture/controllers/cluster.html
for more details."
properties:
host:
description: The hostname on which the API server is serving.
type: string
port:
description: The port on which the API server is serving.
format: int32
type: integer
required:
- host
- port
type: object
type: object
status:
description: TinkerbellClusterStatus defines the observed state of TinkerbellCluster.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,16 @@ spec:
spec:
description: TinkerbellMachineSpec defines the desired state of TinkerbellMachine.
properties:
hardwareReservationID:
hardwareID:
description: Those fields are set programmatically, but they cannot
be re-constructed from "state of the world", so we put them in spec
instead of status.
type: string
providerID:
type: string
templateID:
type: string
workflowID:
type: string
type: object
status:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@ spec:
description: Spec is the specification of the desired behavior
of the machine.
properties:
hardwareReservationID:
hardwareID:
description: Those fields are set programmatically, but they
cannot be re-constructed from "state of the world", so we
put them in spec instead of status.
type: string
providerID:
type: string
templateID:
type: string
workflowID:
type: string
type: object
required:
Expand Down
85 changes: 10 additions & 75 deletions controllers/tinkerbellcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,91 +20,44 @@ package controllers
import (
"context"
"fmt"
"time"

"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
"sigs.k8s.io/cluster-api/util"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"

infrastructurev1alpha3 "github.com/tinkerbell/cluster-api-provider-tinkerbell/api/v1alpha3"
"github.com/tinkerbell/cluster-api-provider-tinkerbell/internal/reconcilers"
)

// TinkerbellClusterReconciler implements Reconciler interface.
type TinkerbellClusterReconciler struct {
client.Client
Log logr.Logger
Recorder record.EventRecorder
Scheme *runtime.Scheme
Log logr.Logger
ClusterReconcileContextConfig *reconcilers.ClusterReconcileContextConfig
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=tinkerbellclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=tinkerbellclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch

// Reconcile ensures state of Tinkerbell clusters.
func (r *TinkerbellClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) {
func (r *TinkerbellClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
logger := r.Log.WithValues("tinkerbellcluster", req.NamespacedName)

// Your logic here.
tcluster := &infrastructurev1alpha3.TinkerbellCluster{}
if err := r.Get(ctx, req.NamespacedName, tcluster); err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}

return ctrl.Result{}, fmt.Errorf("getting cluster: %w", err)
}

logger = logger.WithName(tcluster.APIVersion)

// Fetch the Machine.
cluster, err := util.GetOwnerCluster(ctx, r.Client, tcluster.ObjectMeta)
crc, err := r.ClusterReconcileContextConfig.New(ctx, req.NamespacedName)
if err != nil {
return ctrl.Result{}, fmt.Errorf("getting owner cluster: %w", err)
return ctrl.Result{}, fmt.Errorf("creating reconciliation context: %w", err)
}

if cluster == nil {
logger.Info("OwnerCluster is not set yet. Requeuing...")
if crc == nil {
r.Log.Info("reconciliation dependencies are not ready yet")

return ctrl.Result{
Requeue: true,
RequeueAfter: 2 * time.Second, //nolint:gomnd
}, nil
return defaultRequeueResult(), nil
}

if util.IsPaused(cluster, tcluster) {
logger.Info("TinkerbellCluster or linked Cluster is marked as paused. Won't reconcile")

return ctrl.Result{}, nil
}

// Handle deleted clusters.
if !cluster.DeletionTimestamp.IsZero() {
return r.reconcileDelete()
}

return r.reconcileNormal(tcluster)
}

//nolint:lll
func (r *TinkerbellClusterReconciler) reconcileNormal(tcluster *infrastructurev1alpha3.TinkerbellCluster) (ctrl.Result, error) {
return ctrl.Result{}, nil
}

func (r *TinkerbellClusterReconciler) reconcileDelete() (ctrl.Result, error) {
// Initially I created this handler to remove an elastic IP when a cluster
// gets delete, but it does not sound like a good idea. It is better to
// leave to the users the ability to decide if they want to keep and resign
// the IP or if they do not need it anymore.
return ctrl.Result{}, nil
return ctrl.Result{}, crc.Reconcile()
}

// SetupWithManager configures reconciler with a given manager.
Expand All @@ -119,21 +72,3 @@ func (r *TinkerbellClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
).
Complete(r)
}

// MachineNotFound error representing that the requested device was not yet found.
type MachineNotFound struct {
err string
}

func (e *MachineNotFound) Error() string {
return e.err
}

// MachineNoIP error representing that the requested device does not have an IP yet assigned.
type MachineNoIP struct {
err string
}

func (e *MachineNoIP) Error() string {
return e.err
}
57 changes: 47 additions & 10 deletions controllers/tinkerbellmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,25 @@ limitations under the License.
package controllers

import (
"github.com/go-logr/logr"
"context"
"fmt"
"time"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
"github.com/go-logr/logr"
clusterv1alpha3 "sigs.k8s.io/cluster-api/api/v1alpha3"
"sigs.k8s.io/cluster-api/util"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"

infrastructurev1alpha3 "github.com/tinkerbell/cluster-api-provider-tinkerbell/api/v1alpha3"
"github.com/tinkerbell/cluster-api-provider-tinkerbell/internal/reconcilers"
)

// TinkerbellMachineReconciler implements Reconciler interface by managing Tinkerbell machines.
type TinkerbellMachineReconciler struct {
client.Client
Log logr.Logger
Recorder record.EventRecorder
Scheme *runtime.Scheme
Log logr.Logger
BaseMachineReconcileContextConfig *reconcilers.BaseMachineReconcileContextConfig
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=tinkerbellmachines,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -45,8 +44,36 @@ type TinkerbellMachineReconciler struct {
// +kubebuilder:rbac:groups="",resources=secrets;,verbs=get;list;watch

// Reconcile ensures that all Tinkerbell machines are aligned with a given spec.
func (r *TinkerbellMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) {
return ctrl.Result{}, nil
func (r *TinkerbellMachineReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()

bmrc, err := r.BaseMachineReconcileContextConfig.New(ctx, req.NamespacedName)
if err != nil {
return ctrl.Result{}, fmt.Errorf("creating reconciliation context: %w", err)
}

if bmrc == nil {
r.Log.Info("reconciliation dependencies are not ready yet")

return defaultRequeueResult(), nil
}

if bmrc.MachineScheduledForDeletion() {
return ctrl.Result{}, bmrc.DeleteMachineWithDependencies()
}

mrc, err := bmrc.IntoMachineReconcileContext(r.BaseMachineReconcileContextConfig.TinkerbellClient)
if err != nil {
return ctrl.Result{}, fmt.Errorf("building machine reconcile context: %w", err)
}

if mrc == nil {
bmrc.Log().Info("reconciliation dependencies are not ready yet")

return defaultRequeueResult(), nil
}

return ctrl.Result{}, mrc.Reconcile()
}

// SetupWithManager configures reconciler with a given manager.
Expand All @@ -61,3 +88,13 @@ func (r *TinkerbellMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
).
Complete(r)
}

// defaultRequeueResult returns requeue result with default requeue time defined, as Go does not support const structs.
func defaultRequeueResult() ctrl.Result {
return ctrl.Result{
// TODO: Why 5 seconds is a good value? Usually it takes few seconds for other controllers to converge
// and prepare dependencies for Machine and Cluster objects, so 5 seconds wait time should provide good
// balance, as sync time might be e.g. 10 minutes.
RequeueAfter: 5 * time.Second, //nolint:gomnd
}
}
18 changes: 18 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,29 @@ module github.com/tinkerbell/cluster-api-provider-tinkerbell
go 1.15

require (
cloud.google.com/go v0.46.3 // indirect
github.com/go-logr/logr v0.1.0
github.com/google/go-cmp v0.5.2 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.15.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.6.1 // indirect
github.com/tinkerbell/tink v0.0.0-20200930194128-08601fea303b
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
golang.org/x/tools v0.0.0-20200921190806-0f52b63a40e8 // indirect
google.golang.org/genproto v0.0.0-20201026171402-d4b8fe4fd877 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
k8s.io/api v0.17.16
k8s.io/apimachinery v0.17.16
k8s.io/client-go v0.17.16
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
sigs.k8s.io/cluster-api v0.3.12
sigs.k8s.io/controller-runtime v0.5.14
sigs.k8s.io/yaml v1.2.0
)
Loading

0 comments on commit 70df4a5

Please sign in to comment.