Skip to content

Commit

Permalink
Continue building out Tink types and controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
detiber committed Dec 11, 2020
1 parent 008849e commit 74f5b3d
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 63 deletions.
9 changes: 6 additions & 3 deletions config/crd/bases/tinkerbell.org_workflows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ spec:
spec:
description: WorkflowSpec defines the desired state of Workflow.
properties:
hardware:
hardwareRef:
description: Name of the Hardware associated with this workflow.
type: string
template:
description: Name of the Template associated with this Workflow.
hardwareTemplate:
description: HardwareTemplate is the template used for creating the workflow.
type: string
templateRef:
description: Name of the Template associated with this workflow.
type: string
type: object
status:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.15

require (
github.com/go-logr/logr v0.1.0
github.com/onsi/gomega v1.10.1
github.com/tinkerbell/tink v0.0.0-20201210163923-6d9159b63857
google.golang.org/grpc v1.32.0
k8s.io/api v0.17.14
Expand Down
4 changes: 2 additions & 2 deletions tink/api/v1alpha1/hardware_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

const (
// HardwareIDAnnotation is used by the controller to store the
// ID assigned to the workflow by Tinkerbell.
// ID assigned to the hardware by Tinkerbell.
HardwareIDAnnotation = "hardware.tinkerbell.org/id"

HardwareFinalizer = "hardware.tinkerbell.org"
Expand All @@ -38,7 +38,7 @@ type HardwareStatus struct {

// +kubebuilder:subresource:status
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=hardware,scope=Cluster,categories=tinkerbell
// +kubebuilder:resource:path=hardware,scope=Cluster,categories=tinkerbell,singular=hardware
// +kubebuilder:storageversion

// Hardware is the Schema for the Hardware API.
Expand Down
2 changes: 1 addition & 1 deletion tink/api/v1alpha1/template_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

const (
// TemplateIDAnnotation is used by the controller to store the
// ID assigned to the workflow by Tinkerbell.
// ID assigned to the template by Tinkerbell.
TemplateIDAnnotation = "template.tinkerbell.org/id"

TemplateFinalizer = "template.tinkerbell.org"
Expand Down
9 changes: 6 additions & 3 deletions tink/api/v1alpha1/workflow_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ const (

// WorkflowSpec defines the desired state of Workflow.
type WorkflowSpec struct {
// Name of the Template associated with this Workflow.
Template string `json:"template,omitempty"`
// Name of the Template associated with this workflow.
TemplateRef string `json:"templateRef,omitempty"`

// Name of the Hardware associated with this workflow.
Hardware string `json:"hardware,omitempty"`
HardwareRef string `json:"hardwareRef,omitempty"`

// HardwareTemplate is the template used for creating the workflow.
HardwareTemplate string `json:"hardwareTemplate,omitempty"`
}

// WorkflowStatus defines the observed state of Workflow.
Expand Down
46 changes: 46 additions & 0 deletions tink/controllers/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package controllers contains controllers for Tinkerbell.
package controllers

import (
"context"

"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type Object interface {
metav1.Object
runtime.Object
}

func ensureFinalizer(ctx context.Context, c client.Client, logger logr.Logger, obj Object, finalizer string) error {
patch := client.MergeFrom(obj.DeepCopyObject())

controllerutil.AddFinalizer(obj, finalizer)

if err := c.Patch(ctx, obj, patch); err != nil {
logger.Error(err, "Failed to add finalizer to template")
return err
}

return nil
}
87 changes: 87 additions & 0 deletions tink/controllers/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package controllers contains controllers for Tinkerbell.
package controllers

import (
"context"
"testing"

. "github.com/onsi/gomega"
tinkv1alpha1 "github.com/tinkerbell/cluster-api-provider-tinkerbell/tink/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log"
)

func Test_ensureFinalizer(t *testing.T) {
g := NewWithT(t)
scheme := runtime.NewScheme()

g.Expect(tinkv1alpha1.AddToScheme(scheme)).To(Succeed())

tests := []struct {
name string
in Object
finalizer string
wantErr bool
}{
{
name: "Adds finalizer when not present",
in: &tinkv1alpha1.Template{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: tinkv1alpha1.TemplateSpec{},
},
finalizer: "my-test-finalizer",
wantErr: false,
},
{
name: "Finalizer already exists",
in: &tinkv1alpha1.Template{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Finalizers: []string{"my-test-finalizer"},
},
Spec: tinkv1alpha1.TemplateSpec{},
},
finalizer: "my-test-finalizer",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
fakeClient := fake.NewFakeClientWithScheme(scheme, tt.in.DeepCopyObject())

err := ensureFinalizer(context.Background(), fakeClient, log.Log, tt.in, tt.finalizer)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
g.Expect(err).NotTo(HaveOccurred())

actual := &tinkv1alpha1.Template{}
key := client.ObjectKey{Name: tt.in.GetName()}
g.Expect(fakeClient.Get(context.Background(), key, actual)).To(Succeed())
g.Expect(actual.Finalizers).To(ContainElement(tt.finalizer))
})
}
}
58 changes: 39 additions & 19 deletions tink/controllers/hardware_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,19 @@ func (r *HardwareReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, nil
}

logger.Error(err, "Failed to get hardware")
return ctrl.Result{}, fmt.Errorf("getting hardware: %w", err)
}

// Add finalizer first if not exist to avoid the race condition between init and delete
if !controllerutil.ContainsFinalizer(hardware, tinkv1alpha1.HardwareFinalizer) {
before := hardware.DeepCopy()
controllerutil.AddFinalizer(hardware, tinkv1alpha1.HardwareFinalizer)

if err := r.Client.Patch(ctx, hardware, client.MergeFrom(before)); err != nil {
logger.Error(err, "Failed to add finalizer to hardware")

return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err)
}

return ctrl.Result{}, nil
// Ensure that we add the finalizer to the resource
err := ensureFinalizer(ctx, r.Client, logger, hardware, tinkv1alpha1.HardwareFinalizer)
if err != nil {
return ctrl.Result{}, err
}

// Handle deleted hardware.
if !hardware.DeletionTimestamp.IsZero() {
return r.reconcileDelete(hardware)
return r.reconcileDelete(ctx, hardware)
}

return r.reconcileNormal(hardware)
Expand All @@ -96,13 +89,40 @@ func (r *HardwareReconciler) reconcileNormal(hardware *tinkv1alpha1.Hardware) (c
return ctrl.Result{}, err
}

func (r *HardwareReconciler) reconcileDelete(hardware *tinkv1alpha1.Hardware) (ctrl.Result, error) {
logger := r.Log.WithValues("hardware", hardware.Name)
err := ErrNotImplemented
func (r *HardwareReconciler) reconcileDelete(ctx context.Context, h *tinkv1alpha1.Hardware) (ctrl.Result, error) {
// Create a patch for use later
patch := client.MergeFrom(h.DeepCopy())

logger.Error(err, "Not yet implemented")
logger := r.Log.WithValues("hardware", h.Name)

// controllerutil.RemoveFinalizer(hardware, tinkv1alpha1.HardwareFinalizer)
if h.Annotations == nil {
h.Annotations = map[string]string{}
}

return ctrl.Result{}, err
id, ok := h.Annotations[tinkv1alpha1.HardwareIDAnnotation]
if !ok {
// TODO: figure out how to lookup a hardware without an ID
logger.Error(ErrNotImplemented, "Unable to delete a hardware without having recorded the ID")
return ctrl.Result{}, ErrNotImplemented
}

hardwareRequest := &hardware.DeleteRequest{
Id: id,
}

_, err := r.HardwareClient.Delete(ctx, hardwareRequest)
// TODO: Tinkerbell should return some type of status that is easier to handle
// than parsing for this specific error message.
if err != nil && err.Error() != "rpc error: code = Unknown desc = sql: no rows in result set" {
logger.Error(err, "Failed to delete hardware from Tinkerbell")
return ctrl.Result{}, err
}

controllerutil.RemoveFinalizer(h, tinkv1alpha1.HardwareFinalizer)
if err := r.Client.Patch(ctx, h, patch); err != nil {
logger.Error(err, "Failed to patch hardware")
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}
20 changes: 6 additions & 14 deletions tink/controllers/template_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,14 @@ func (r *TemplateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, nil
}

return ctrl.Result{}, fmt.Errorf("getting template: %w", err)
logger.Error(err, "Failed to get template")
return ctrl.Result{}, err
}

// Add finalizer first if not exist to avoid the race condition between init and delete
if !controllerutil.ContainsFinalizer(template, tinkv1alpha1.TemplateFinalizer) {
before := template.DeepCopy()
controllerutil.AddFinalizer(template, tinkv1alpha1.TemplateFinalizer)

if err := r.Client.Patch(ctx, template, client.MergeFrom(before)); err != nil {
logger.Error(err, "Failed to add finalizer to template")

return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err)
}

return ctrl.Result{}, nil
// Ensure that we add the finalizer to the resource
err := ensureFinalizer(ctx, r.Client, logger, template, tinkv1alpha1.TemplateFinalizer)
if err != nil {
return ctrl.Result{}, err
}

// Handle deleted templates.
Expand Down Expand Up @@ -183,7 +176,6 @@ func (r *TemplateReconciler) reconcileDelete(ctx context.Context, t *tinkv1alpha
// TODO: Tinkerbell should return some type of status that is easier to handle
// than parsing for this specific error message.
if err != nil && err.Error() != "rpc error: code = Unknown desc = sql: no rows in result set" {
// If it doesn't exist, then return without an error
logger.Error(err, "Failed to delete template from Tinkerbell")
return ctrl.Result{}, err
}
Expand Down
Loading

0 comments on commit 74f5b3d

Please sign in to comment.