Skip to content

Commit

Permalink
chore(operator): use List() when fetching KeptnWorkloadInstances for …
Browse files Browse the repository at this point in the history
…KeptnAppVersion (#1456)

Signed-off-by: odubajDT <[email protected]>
  • Loading branch information
odubajDT authored May 25, 2023
1 parent 30e6647 commit ecd8c48
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 23 deletions.
6 changes: 2 additions & 4 deletions operator/controllers/common/fake/fakeclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@ import (

// NewClient returns a new controller-runtime fake Client configured with the Operator's scheme, and initialized with objs.
func NewClient(objs ...client.Object) client.Client {
setupSchemes()
SetupSchemes()
return fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(objs...).Build()
}

func setupSchemes() {
func SetupSchemes() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme.Scheme))
utilruntime.Must(corev1.AddToScheme(scheme.Scheme))
utilruntime.Must(apiv1.AddToScheme(scheme.Scheme))
// utilruntime.Must(lfcv1alpha1.AddToScheme(scheme.Scheme))
// utilruntime.Must(lfcv1alpha2.AddToScheme(scheme.Scheme))
utilruntime.Must(lfcv1alpha3.AddToScheme(scheme.Scheme))
utilruntime.Must(optionsv1alpha1.AddToScheme(scheme.Scheme))
utilruntime.Must(metricsapi.AddToScheme(scheme.Scheme))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

Expand Down Expand Up @@ -160,7 +161,7 @@ func setupReconcilerWithMeters() *KeptnAppVersionReconciler {
return r
}

func setupReconciler() (*KeptnAppVersionReconciler, chan string, *fake.ITracerMock, *fake.ISpanHandlerMock) {
func setupReconciler(objs ...client.Object) (*KeptnAppVersionReconciler, chan string, *fake.ITracerMock, *fake.ISpanHandlerMock) {
//setup logger
opts := zap.Options{
Development: true,
Expand All @@ -185,7 +186,13 @@ func setupReconciler() (*KeptnAppVersionReconciler, chan string, *fake.ITracerMo
UnbindSpanFunc: func(reconcileObject client.Object, phase string) error { return nil },
}

fakeClient := fake.NewClient()
workloadInstanceIndexer := func(obj client.Object) []string {
workloadInstance, _ := obj.(*lfcv1alpha3.KeptnWorkloadInstance)
return []string{workloadInstance.Spec.AppName}
}

fake.SetupSchemes()
fakeClient := k8sfake.NewClientBuilder().WithObjects(objs...).WithScheme(scheme.Scheme).WithObjects().WithIndex(&lfcv1alpha3.KeptnWorkloadInstance{}, "spec.app", workloadInstanceIndexer).Build()

recorder := record.NewFakeRecorder(100)
r := &KeptnAppVersionReconciler{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3"
apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common"
controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appVersion *klcv1alpha3.KeptnAppVersion) (apicommon.KeptnState, error) {
Expand All @@ -20,18 +19,33 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV
LongName: "Reconcile Workloads",
}

var newStatus []klcv1alpha3.WorkloadStatus
workloadInstanceList, err := r.getWorkloadInstanceList(ctx, appVersion.Namespace, appVersion.Spec.AppName)
if err != nil {
r.Log.Error(err, "Could not get workloads")
return apicommon.StateUnknown, r.handleUnaccessibleWorkloadInstanceList(ctx, appVersion)
}

newStatus := make([]klcv1alpha3.WorkloadStatus, 0, len(appVersion.Spec.Workloads))
for _, w := range appVersion.Spec.Workloads {
r.Log.Info("Reconciling workload " + w.Name)
workload, err := r.getWorkloadInstance(ctx, getWorkloadInstanceName(appVersion.Namespace, appVersion.Spec.AppName, w.Name, w.Version))
if err != nil && errors.IsNotFound(err) {
workloadStatus := apicommon.StatePending
found := false
instanceName := getWorkloadInstanceName(appVersion.Spec.AppName, w.Name, w.Version)
for _, i := range workloadInstanceList.Items {
r.Log.Info("No WorkloadInstance found for KeptnApp " + appVersion.Spec.AppName)
// additional filtering of the retrieved WIs is needed, as the List() method retrieves all
// WIs for a specific KeptnApp. The result can contain also WIs, that are not part of the
// latest KeptnAppVersion, so it's needed to double check them
// no need to compare version, as it is part of WI name
if instanceName == i.Name {
found = true
workloadStatus = i.Status.Status
}
}

if !found {
controllercommon.RecordEvent(r.Recorder, phase, "Warning", appVersion, "NotFound", "workloadInstance not found", appVersion.GetVersion())
workload.Status.Status = apicommon.StatePending
} else if err != nil {
r.Log.Error(err, "Could not get workload")
workload.Status.Status = apicommon.StateUnknown
}
workloadStatus := workload.Status.Status

newStatus = append(newStatus, klcv1alpha3.WorkloadStatus{
Workload: w,
Expand All @@ -48,16 +62,31 @@ func (r *KeptnAppVersionReconciler) reconcileWorkloads(ctx context.Context, appV
r.Log.Info("Workload status", "status", appVersion.Status.WorkloadStatus)

// Write Status Field
err := r.Client.Status().Update(ctx, appVersion)
err = r.Client.Status().Update(ctx, appVersion)
return overallState, err
}

func (r *KeptnAppVersionReconciler) getWorkloadInstance(ctx context.Context, workload types.NamespacedName) (klcv1alpha3.KeptnWorkloadInstance, error) {
workloadInstance := &klcv1alpha3.KeptnWorkloadInstance{}
err := r.Get(ctx, workload, workloadInstance)
return *workloadInstance, err
func (r *KeptnAppVersionReconciler) getWorkloadInstanceList(ctx context.Context, namespace string, appName string) (*klcv1alpha3.KeptnWorkloadInstanceList, error) {
workloadInstanceList := &klcv1alpha3.KeptnWorkloadInstanceList{}
err := r.Client.List(ctx, workloadInstanceList, client.InNamespace(namespace), client.MatchingFields{
"spec.app": appName,
})
return workloadInstanceList, err
}

func (r *KeptnAppVersionReconciler) handleUnaccessibleWorkloadInstanceList(ctx context.Context, appVersion *klcv1alpha3.KeptnAppVersion) error {
newStatus := make([]klcv1alpha3.WorkloadStatus, 0, len(appVersion.Spec.Workloads))
for _, w := range appVersion.Spec.Workloads {
newStatus = append(newStatus, klcv1alpha3.WorkloadStatus{
Workload: w,
Status: apicommon.StateUnknown,
})
}
appVersion.Status.WorkloadOverallStatus = apicommon.StateUnknown
appVersion.Status.WorkloadStatus = newStatus
return r.Client.Status().Update(ctx, appVersion)
}

func getWorkloadInstanceName(namespace string, appName string, workloadName string, version string) types.NamespacedName {
return types.NamespacedName{Namespace: namespace, Name: appName + "-" + workloadName + "-" + version}
func getWorkloadInstanceName(appName string, workloadName string, version string) string {
return appName + "-" + workloadName + "-" + version
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package keptnappversion

import (
"context"
"testing"

lfcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3"
apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

//nolint:dogsled
func TestKeptnAppVersionReconciler_reconcileWorkloads_noWorkloads(t *testing.T) {
appVersion := &lfcv1alpha3.KeptnAppVersion{
ObjectMeta: v1.ObjectMeta{
Name: "appversion",
Namespace: "default",
},
Spec: lfcv1alpha3.KeptnAppVersionSpec{
AppName: "app",
},
}
r, _, _, _ := setupReconciler(appVersion)

state, err := r.reconcileWorkloads(context.TODO(), appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateSucceeded, state)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateSucceeded, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 0)
}

//nolint:dogsled
func TestKeptnAppVersionReconciler_reconcileWorkloads(t *testing.T) {
appVersion := &lfcv1alpha3.KeptnAppVersion{
ObjectMeta: v1.ObjectMeta{
Name: "appversion",
Namespace: "default",
},
Spec: lfcv1alpha3.KeptnAppVersionSpec{
KeptnAppSpec: lfcv1alpha3.KeptnAppSpec{
Workloads: []lfcv1alpha3.KeptnWorkloadRef{
{
Name: "workload",
Version: "ver1",
},
},
},
AppName: "app",
},
}
r, _, _, _ := setupReconciler(appVersion)

// No workloadInstances are created yet, should stay in Pending state

state, err := r.reconcileWorkloads(context.TODO(), appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StatePending, state)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StatePending, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 1)
require.Equal(t, []lfcv1alpha3.WorkloadStatus{
{
Workload: lfcv1alpha3.KeptnWorkloadRef{
Name: "workload",
Version: "ver1",
},
Status: apicommon.StatePending,
},
}, appVersion.Status.WorkloadStatus)

// Creating WorkloadInstace that is not part of the App -> should stay Pending

wi1 := &lfcv1alpha3.KeptnWorkloadInstance{
ObjectMeta: v1.ObjectMeta{
Name: "workload",
Namespace: "default",
},
Spec: lfcv1alpha3.KeptnWorkloadInstanceSpec{
KeptnWorkloadSpec: lfcv1alpha3.KeptnWorkloadSpec{
AppName: "app2",
},
},
}

err = r.Client.Create(context.TODO(), wi1)
require.Nil(t, err)

state, err = r.reconcileWorkloads(context.TODO(), appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StatePending, state)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StatePending, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 1)
require.Equal(t, []lfcv1alpha3.WorkloadStatus{
{
Workload: lfcv1alpha3.KeptnWorkloadRef{
Name: "workload",
Version: "ver1",
},
Status: apicommon.StatePending,
},
}, appVersion.Status.WorkloadStatus)

// Creating WorkloadInstance of App with progressing state -> appVersion should be Progressing

wi2 := &lfcv1alpha3.KeptnWorkloadInstance{
ObjectMeta: v1.ObjectMeta{
Name: "app-workload-ver1",
Namespace: "default",
},
Spec: lfcv1alpha3.KeptnWorkloadInstanceSpec{
KeptnWorkloadSpec: lfcv1alpha3.KeptnWorkloadSpec{
AppName: "app",
},
},
}

err = r.Client.Create(context.TODO(), wi2)
require.Nil(t, err)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: wi2.Namespace, Name: wi2.Name}, wi2)
require.Nil(t, err)

wi2.Status.Status = apicommon.StateProgressing
err = r.Client.Update(context.TODO(), wi2)
require.Nil(t, err)

state, err = r.reconcileWorkloads(context.TODO(), appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateProgressing, state)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateProgressing, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 1)
require.Equal(t, []lfcv1alpha3.WorkloadStatus{
{
Workload: lfcv1alpha3.KeptnWorkloadRef{
Name: "workload",
Version: "ver1",
},
Status: apicommon.StateProgressing,
},
}, appVersion.Status.WorkloadStatus)

// Updating WorkloadInstance of App with succeeded state -> appVersion should be Succeeded

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: wi2.Namespace, Name: wi2.Name}, wi2)
require.Nil(t, err)

wi2.Status.Status = apicommon.StateSucceeded
err = r.Client.Update(context.TODO(), wi2)
require.Nil(t, err)

state, err = r.reconcileWorkloads(context.TODO(), appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateSucceeded, state)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateSucceeded, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 1)
require.Equal(t, []lfcv1alpha3.WorkloadStatus{
{
Workload: lfcv1alpha3.KeptnWorkloadRef{
Name: "workload",
Version: "ver1",
},
Status: apicommon.StateSucceeded,
},
}, appVersion.Status.WorkloadStatus)
}

//nolint:dogsled
func TestKeptnAppVersionReconciler_handleUnaccessibleWorkloadInstanceList(t *testing.T) {
appVersion := &lfcv1alpha3.KeptnAppVersion{
ObjectMeta: v1.ObjectMeta{
Name: "appversion",
Namespace: "default",
},
Spec: lfcv1alpha3.KeptnAppVersionSpec{
KeptnAppSpec: lfcv1alpha3.KeptnAppSpec{
Workloads: []lfcv1alpha3.KeptnWorkloadRef{
{
Name: "workload",
Version: "ver1",
},
},
},
AppName: "app",
},
}
r, _, _, _ := setupReconciler(appVersion)

err := r.handleUnaccessibleWorkloadInstanceList(context.TODO(), appVersion)
require.Nil(t, err)

err = r.Client.Get(context.TODO(), types.NamespacedName{Namespace: appVersion.Namespace, Name: appVersion.Name}, appVersion)
require.Nil(t, err)
require.Equal(t, apicommon.StateUnknown, appVersion.Status.WorkloadOverallStatus)
require.Len(t, appVersion.Status.WorkloadStatus, 1)
require.Equal(t, []lfcv1alpha3.WorkloadStatus{
{
Workload: lfcv1alpha3.KeptnWorkloadRef{
Name: "workload",
Version: "ver1",
},
Status: apicommon.StateUnknown,
},
}, appVersion.Status.WorkloadStatus)
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ func (r *KeptnWorkloadInstanceReconciler) finishKeptnWorkloadInstanceReconcile(c

// SetupWithManager sets up the controller with the Manager.
func (r *KeptnWorkloadInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &klcv1alpha3.KeptnWorkloadInstance{}, "spec.app", func(rawObj client.Object) []string {
workloadInstance := rawObj.(*klcv1alpha3.KeptnWorkloadInstance)
return []string{workloadInstance.Spec.AppName}
}); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
// predicate disabling the auto reconciliation after updating the object status
For(&klcv1alpha3.KeptnWorkloadInstance{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Expand Down

0 comments on commit ecd8c48

Please sign in to comment.