Skip to content

Commit

Permalink
Update status.controlPlaneInitialized from cluster controller
Browse files Browse the repository at this point in the history
  • Loading branch information
tahsinrahman committed Sep 12, 2019
1 parent fc4f55e commit 5ca8ee2
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 28 deletions.
86 changes: 86 additions & 0 deletions controllers/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"fmt"
"path"
"sync"
"time"
Expand All @@ -27,6 +28,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/tools/record"
"k8s.io/klog"
Expand All @@ -38,7 +40,9 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
Expand Down Expand Up @@ -66,6 +70,10 @@ type ClusterReconciler struct {
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
c, err := ctrl.NewControllerManagedBy(mgr).
For(&clusterv1.Cluster{}).
Watches(
&source.Kind{Type: &clusterv1.Machine{}},
&handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(r.controlPlaneMachineToCluster)},
).
WithOptions(options).
Build(r)

Expand Down Expand Up @@ -129,6 +137,7 @@ func (r *ClusterReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cl
reconciliationErrors := []error{
r.reconcileInfrastructure(ctx, cluster),
r.reconcileKubeconfig(ctx, cluster),
r.reconcileControlPlaneInitialized(ctx, cluster),
}

// Parse the errors, making sure we record if there is a RequeueAfterError.
Expand Down Expand Up @@ -287,3 +296,80 @@ func splitMachineList(list *clusterv1.MachineList) (*clusterv1.MachineList, *clu
}
return controlplanes, nodes
}

func (r *ClusterReconciler) reconcileControlPlaneInitialized(ctx context.Context, cluster *clusterv1.Cluster) error {
if cluster.Status.ControlPlaneInitialized {
return nil
}

machines, err := r.getMachinesInCluster(ctx, cluster.Namespace, cluster.Name)
if err != nil {
r.Log.Error(err, "error getting machines in cluster", "cluster", cluster.Name)
return err
}

for _, m := range machines {
if util.IsControlPlaneMachine(m) && m.Status.NodeRef != nil {
cluster.Status.ControlPlaneInitialized = true
return nil
}
}

return nil
}

// getMachinesInCluster returns all of the Machine objects that belong to the cluster
func (r *ClusterReconciler) getMachinesInCluster(ctx context.Context, namespace, name string) ([]*clusterv1.Machine, error) {
if name == "" {
return nil, nil
}

machineList := &clusterv1.MachineList{}
labels := map[string]string{clusterv1.MachineClusterLabelName: name}

if err := r.Client.List(ctx, machineList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil {
return nil, errors.Wrap(err, "failed to list machines")
}

machines := []*clusterv1.Machine{}
for i := range machineList.Items {
m := &machineList.Items[i]
if m.DeletionTimestamp.IsZero() {
machines = append(machines, m)
}
}
return machines, nil
}

// controlPlaneMachineToCluster is a handler.ToRequestsFunc to be used to enqueue requests for reconciliation
// for Cluster to update its status.controlPlaneInitialized field
func (r *ClusterReconciler) controlPlaneMachineToCluster(o handler.MapObject) []ctrl.Request {
m, ok := o.Object.(*clusterv1.Machine)
if !ok {
r.Log.Error(errors.New("did not get machine"), "got", fmt.Sprintf("%T", o.Object))
return nil
}
if !util.IsControlPlaneMachine(m) {
return nil
}
if m.Status.NodeRef == nil {
return nil
}

cluster, err := util.GetClusterFromMetadata(context.TODO(), r.Client, m.ObjectMeta)
if err != nil {
r.Log.Error(err, "failed to get cluster", "machine", m.Name, "cluster", m.ClusterName, "namespace", m.Namespace)
return nil
}

if cluster.Status.ControlPlaneInitialized {
return nil
}

return []ctrl.Request{{
NamespacedName: types.NamespacedName{
Namespace: cluster.Namespace,
Name: cluster.Name,
},
}}
}
202 changes: 202 additions & 0 deletions controllers/cluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ limitations under the License.
package controllers

import (
"reflect"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
"sigs.k8s.io/cluster-api/util/patch"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
)

var _ = Describe("Cluster Reconciler", func() {
Expand Down Expand Up @@ -211,4 +219,198 @@ var _ = Describe("Cluster Reconciler", func() {
return instance.Finalizers
}, timeout).Should(BeEmpty())
})

It("Should successfully set Status.ControlPlaneInitialized on the cluster object if controlplane is ready", func() {
cluster := &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: v1.NamespaceDefault,
},
}

Expect(k8sClient.Create(ctx, cluster)).To(BeNil())
key := client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}
defer k8sClient.Delete(ctx, cluster)

// Wait for reconciliation to happen.
Eventually(func() bool {
if err := k8sClient.Get(ctx, key, cluster); err != nil {
return false
}
return len(cluster.Finalizers) > 0
}, timeout).Should(BeTrue())

machine := &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: v1.NamespaceDefault,
Labels: map[string]string{
clusterv1.MachineControlPlaneLabelName: cluster.Name,
clusterv1.MachineClusterLabelName: cluster.Name,
},
},
Spec: clusterv1.MachineSpec{
ProviderID: pointer.StringPtr("aws:///id-node-1"),
Bootstrap: clusterv1.Bootstrap{
Data: pointer.StringPtr(""),
},
},
}

Expect(k8sClient.Create(ctx, machine)).To(BeNil())
key = client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}
defer k8sClient.Delete(ctx, machine)

// wait for machine to be ready
Eventually(func() bool {
if err := k8sClient.Get(ctx, key, machine); err != nil {
return false
}
return len(machine.Finalizers) > 0
}, timeout).Should(BeTrue())

// patch machine noderef
patchHelper, err := patch.NewHelper(machine, k8sClient)
Expect(err).NotTo(HaveOccurred())

machine.Status.NodeRef = &v1.ObjectReference{Kind: "Node", Name: "test-node"}
Expect(patchHelper.Patch(ctx, machine)).Should(BeNil())

// Assertion
key = client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}
Eventually(func() bool {
if err := k8sClient.Get(ctx, key, cluster); err != nil {
return false
}
return cluster.Status.ControlPlaneInitialized
}, timeout).Should(BeTrue())
})
})

func TestClusterReconciler_machineToCluster(t *testing.T) {
cluster := &clusterv1.Cluster{
TypeMeta: metav1.TypeMeta{
Kind: "Cluster",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test",
},
Spec: clusterv1.ClusterSpec{},
Status: clusterv1.ClusterStatus{},
}

controlPlaneWithNoderef := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "Machine",
},
ObjectMeta: metav1.ObjectMeta{
Name: "controlPlaneWithNoderef",
Labels: map[string]string{
clusterv1.MachineClusterLabelName: cluster.Name,
clusterv1.MachineControlPlaneLabelName: cluster.Name,
},
},
Status: clusterv1.MachineStatus{
NodeRef: &v1.ObjectReference{
Kind: "Node",
Namespace: "test-node",
},
},
}
controlPlaneWithoutNoderef := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "Machine",
},
ObjectMeta: metav1.ObjectMeta{
Name: "controlPlaneWithoutNoderef",
Labels: map[string]string{
clusterv1.MachineClusterLabelName: cluster.Name,
clusterv1.MachineControlPlaneLabelName: cluster.Name,
},
},
}
nonControlPlaneWithNoderef := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "Machine",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nonControlPlaneWitNoderef",
Labels: map[string]string{
clusterv1.MachineClusterLabelName: cluster.Name,
},
},
Status: clusterv1.MachineStatus{
NodeRef: &v1.ObjectReference{
Kind: "Node",
Namespace: "test-node",
},
},
}
nonControlPlaneWithoutNoderef := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "Machine",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nonControlPlaneWithoutNoderef",
Labels: map[string]string{
clusterv1.MachineClusterLabelName: cluster.Name,
},
},
}

tests := []struct {
name string
o handler.MapObject
want []ctrl.Request
}{
{
name: "controlplane machine, noderef is set, should return cluster",
o: handler.MapObject{
Meta: controlPlaneWithNoderef.GetObjectMeta(),
Object: controlPlaneWithNoderef,
},
want: []ctrl.Request{
{NamespacedName: client.ObjectKey{
Name: cluster.Name,
Namespace: cluster.Namespace,
}},
},
},
{
name: "controlplane machine, noderef is not set",
o: handler.MapObject{
Meta: controlPlaneWithoutNoderef.GetObjectMeta(),
Object: controlPlaneWithoutNoderef,
},
want: nil,
},
{
name: "not controlplane machine, noderef is set",
o: handler.MapObject{
Meta: nonControlPlaneWithNoderef.GetObjectMeta(),
Object: nonControlPlaneWithNoderef,
},
want: nil,
},
{
name: "not controlplane machine, noderef is not set",
o: handler.MapObject{
Meta: nonControlPlaneWithoutNoderef.GetObjectMeta(),
Object: nonControlPlaneWithoutNoderef,
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &ClusterReconciler{
Client: fake.NewFakeClient(cluster, controlPlaneWithNoderef, controlPlaneWithoutNoderef, nonControlPlaneWithNoderef, nonControlPlaneWithoutNoderef),
Log: log.Log,
}
if got := r.controlPlaneMachineToCluster(tt.o); !reflect.DeepEqual(got, tt.want) {
t.Errorf("controlPlaneMachineToCluster() = %v, want %v", got, tt.want)
}
})
}
}
1 change: 0 additions & 1 deletion controllers/machine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ func (r *MachineReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cl
r.reconcileBootstrap(ctx, m),
r.reconcileInfrastructure(ctx, m),
r.reconcileNodeRef(ctx, cluster, m),
r.reconcileClusterStatus(ctx, cluster, m),
}

// Parse the errors, making sure we record if there is a RequeueAfterError.
Expand Down
27 changes: 0 additions & 27 deletions controllers/machine_controller_phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"sigs.k8s.io/cluster-api/controllers/external"
capierrors "sigs.k8s.io/cluster-api/errors"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
Expand Down Expand Up @@ -236,29 +235,3 @@ func (r *MachineReconciler) reconcileInfrastructure(ctx context.Context, m *clus
m.Status.InfrastructureReady = true
return nil
}

// reconcileClusterStatus reconciles the status on the Cluster associated with Machines.
func (r *MachineReconciler) reconcileClusterStatus(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine) error {
if cluster == nil {
return nil
}

// If the Machine is a control plane, it has a NodeRef and it's ready,
// set the Status.ControlPlaneInitialized on the Cluster.
if util.IsControlPlaneMachine(m) && m.Status.NodeRef != nil {
if !cluster.Status.ControlPlaneInitialized {
patchHelper, err := patch.NewHelper(cluster, r.Client)
if err != nil {
return errors.Wrapf(err, "failed to create patch helper for Cluster %q in namespace %q",
cluster.Name, cluster.Namespace)
}
cluster.Status.ControlPlaneInitialized = true
if err := patchHelper.Patch(ctx, cluster); err != nil {
return errors.Wrapf(err, "failed to set Status.ControlPlaneInitialized on Cluster %q in namespace %q",
cluster.Name, cluster.Namespace)
}
}
}

return nil
}

0 comments on commit 5ca8ee2

Please sign in to comment.