Skip to content

Commit

Permalink
patch - adding reconcile tests for recordingrulegroupset and rulegroup (
Browse files Browse the repository at this point in the history
#91)

* adding reconcile tests for recordingrulegroupset, rulegroup, and prometheusrule
  • Loading branch information
OrNovo authored Oct 18, 2023
1 parent 9b2090d commit 9c36860
Show file tree
Hide file tree
Showing 11 changed files with 1,115 additions and 86 deletions.
54 changes: 14 additions & 40 deletions controllers/alphacontrollers/alert_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package alphacontrollers
import (
"context"
"encoding/json"
"fmt"
"testing"

utils "github.com/coralogix/coralogix-operator/apis"
Expand Down Expand Up @@ -116,41 +117,6 @@ func expectedAlertCRD() *coralogixv1alpha1.Alert {
}
}

var expectedAlertStatus = &coralogixv1alpha1.AlertStatus{
ID: pointer.String("id"),
Name: "name",
Description: "description",
Active: true,
Severity: "Critical",
Labels: map[string]string{"key": "value", "managed-by": "coralogix-operator"},
AlertType: coralogixv1alpha1.AlertType{
Metric: &coralogixv1alpha1.Metric{
Promql: &coralogixv1alpha1.Promql{
SearchQuery: "http_requests_total{status!~\"4..\"}",
Conditions: coralogixv1alpha1.PromqlConditions{
AlertWhen: "MoreThanUsual",
Threshold: utils.FloatToQuantity(3.0),
TimeWindow: coralogixv1alpha1.MetricTimeWindow("TwelveHours"),
MinNonNullValuesPercentage: pointer.Int(10),
ReplaceMissingValueWithZero: false,
},
},
},
},
NotificationGroups: []coralogixv1alpha1.NotificationGroup{
{
Notifications: []coralogixv1alpha1.Notification{
{
RetriggeringPeriodMinutes: 10,
NotifyOn: coralogixv1alpha1.NotifyOnTriggeredAndResolved,
EmailRecipients: []string{"example@coralogix.com"},
},
},
},
},
PayloadFilters: []string{"filter"},
}

func TestFlattenAlerts(t *testing.T) {
alert := &alerts.Alert{
UniqueIdentifier: wrapperspb.String("id1"),
Expand Down Expand Up @@ -228,9 +194,11 @@ func TestAlertReconciler_Reconcile(t *testing.T) {
scheme := runtime.NewScheme()
utilruntime.Must(coralogixv1alpha1.AddToScheme(scheme))
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Scheme: scheme,
MetricsBindAddress: "0",
})
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go mgr.GetCache().Start(ctx)
mgr.GetCache().WaitForCacheSync(ctx)
withWatch, err := client.NewWithWatch(mgr.GetConfig(), client.Options{
Expand Down Expand Up @@ -293,9 +261,11 @@ func TestAlertReconciler_Reconcile_5XX_StatusError(t *testing.T) {
scheme := runtime.NewScheme()
utilruntime.Must(coralogixv1alpha1.AddToScheme(scheme))
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Scheme: scheme,
MetricsBindAddress: "0",
})
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go mgr.GetCache().Start(ctx)
mgr.GetCache().WaitForCacheSync(ctx)
withWatch, err := client.NewWithWatch(mgr.GetConfig(), client.Options{
Expand Down Expand Up @@ -329,6 +299,10 @@ func TestAlertReconciler_Reconcile_5XX_StatusError(t *testing.T) {
actualAlertCRD := &coralogixv1alpha1.Alert{}
err = r.Client.Get(ctx, namespacedName, actualAlertCRD)
assert.NoError(t, err)

err = r.Client.Delete(ctx, actualAlertCRD)
<-watcher.ResultChan()
r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
}

// Creates a mock webhooks client that contains a single webhook with id "id1".
Expand Down Expand Up @@ -383,7 +357,7 @@ func createMockAlertsClientWith5XXStatusError(mockCtrl *gomock.Controller, alert
CreateAlert(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _ *alerts.CreateAlertRequest) (*alerts.CreateAlertResponse, error) {
if !wasCalled {
wasCalled = true
return nil, errors.NewBadRequest("bad request")
return nil, errors.NewInternalError(fmt.Errorf("internal error"))
}
alertExist = true
return &alerts.CreateAlertResponse{Alert: alert}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (r *RecordingRuleGroupSetReconciler) Reconcile(ctx context.Context, req ctr
log.V(1).Info("Creating RecordingRuleGroupSet", "RecordingRuleGroupSet", jstr)
if createRRGResp, err := rRGClient.CreateRecordingRuleGroupSet(ctx, createRuleGroupReq); err == nil {
jstr, _ := jsm.MarshalToString(createRRGResp)
log.V(1).Info("RecordingRuleGroupSet was updated", "RecordingRuleGroupSet", jstr)
log.V(1).Info("RecordingRuleGroupSet was created", "RecordingRuleGroupSet", jstr)

//To avoid a situation of the operator falling between the creation of the ruleGroup in coralogix and being saved in the cluster (something that would cause it to be created again and again), its id will be saved ASAP.
id := createRRGResp.Id
Expand Down
273 changes: 273 additions & 0 deletions controllers/alphacontrollers/recordingrulegroupset_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package alphacontrollers

import (
"context"
"fmt"
"testing"

coralogixv1alpha1 "github.com/coralogix/coralogix-operator/apis/coralogix/v1alpha1"
"github.com/coralogix/coralogix-operator/controllers/clientset"
rrg "github.com/coralogix/coralogix-operator/controllers/clientset/grpc/recording-rules-groups/v2"
"github.com/coralogix/coralogix-operator/controllers/mock_clientset"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/types/known/emptypb"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

var recordingRuleGroupSetBackendSchema = &rrg.OutRuleGroupSet{
Id: "id1",
Groups: []*rrg.OutRuleGroup{
{
Name: "name",
Interval: pointer.Uint32(60),
Limit: pointer.Uint64(100),
Rules: []*rrg.OutRule{
{
Record: "record",
Expr: "vector(1)",
Labels: map[string]string{"key": "value"},
},
},
},
},
}

func expectedRecordingRuleGroupSetCRD() *coralogixv1alpha1.RecordingRuleGroupSet {
return &coralogixv1alpha1.RecordingRuleGroupSet{
TypeMeta: metav1.TypeMeta{Kind: "RecordingRuleGroupSet", APIVersion: "coralogix.com/v1alpha1"},
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: coralogixv1alpha1.RecordingRuleGroupSetSpec{
Groups: []coralogixv1alpha1.RecordingRuleGroup{
{
Name: "name",
IntervalSeconds: 60,
Limit: 100,
Rules: []coralogixv1alpha1.RecordingRule{
{
Record: "record",
Expr: "vector(1)",
Labels: map[string]string{"key": "value"},
},
},
},
},
},
}
}

func TestFlattenRecordingRuleGroupSet(t *testing.T) {
actualStatus := flattenRecordingRuleGroupSet(recordingRuleGroupSetBackendSchema)
expectedStatus := coralogixv1alpha1.RecordingRuleGroupSetStatus{
ID: pointer.String("id1"),
Groups: []coralogixv1alpha1.RecordingRuleGroup{
{
Name: "name",
IntervalSeconds: 60,
Limit: 100,
Rules: []coralogixv1alpha1.RecordingRule{
{
Record: "record",
Expr: "vector(1)",
Labels: map[string]string{"key": "value"},
},
},
},
},
}
assert.EqualValues(t, expectedStatus, actualStatus)
}

func TestRecordingRuleGroupSetReconciler_Reconcile(t *testing.T) {
mockCtrl := gomock.NewController(t)
recordingRuleGroupClient := createRecordingRuleGroupClientSimpleMock(mockCtrl)
mockClientSet := mock_clientset.NewMockClientSetInterface(mockCtrl)
mockClientSet.EXPECT().RecordingRuleGroups().Return(recordingRuleGroupClient).AnyTimes()

scheme := runtime.NewScheme()
utilruntime.Must(coralogixv1alpha1.AddToScheme(scheme))
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: "0",
})
ctx := context.Background()
defer ctx.Done()
go mgr.GetCache().Start(ctx)
mgr.GetCache().WaitForCacheSync(ctx)
withWatch, err := client.NewWithWatch(mgr.GetConfig(), client.Options{
Scheme: mgr.GetScheme(),
})
assert.NoError(t, err)
r := RecordingRuleGroupSetReconciler{
Client: withWatch,
Scheme: mgr.GetScheme(),
CoralogixClientSet: mockClientSet,
}
r.SetupWithManager(mgr)

watcher, _ := r.Client.(client.WithWatch).Watch(ctx, &coralogixv1alpha1.RecordingRuleGroupSetList{})
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

err = r.Client.Create(ctx, expectedRecordingRuleGroupSetCRD())
assert.NoError(t, err)
<-watcher.ResultChan()

result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
assert.NoError(t, err)
assert.Equal(t, defaultRequeuePeriod, result.RequeueAfter)

namespacedName := types.NamespacedName{Namespace: "default", Name: "test"}
actualRecordingRuleGroupSetCRD := &coralogixv1alpha1.RecordingRuleGroupSet{}
err = r.Client.Get(ctx, namespacedName, actualRecordingRuleGroupSetCRD)
assert.NoError(t, err)

id := actualRecordingRuleGroupSetCRD.Status.ID
if !assert.NotNil(t, id) {
return
}
getRecordingRuleGroupSetRequest := &rrg.FetchRuleGroupSet{Id: *id}
actualRecordingRuleGroupSet, err := r.CoralogixClientSet.RecordingRuleGroups().GetRecordingRuleGroupSet(ctx, getRecordingRuleGroupSetRequest)
assert.NoError(t, err)
assert.EqualValues(t, recordingRuleGroupSetBackendSchema, actualRecordingRuleGroupSet)

err = r.Client.Delete(ctx, actualRecordingRuleGroupSetCRD)
<-watcher.ResultChan()

result, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
assert.NoError(t, err)
assert.Equal(t, false, result.Requeue)

actualRecordingRuleGroupSet, err = r.CoralogixClientSet.RecordingRuleGroups().GetRecordingRuleGroupSet(ctx, getRecordingRuleGroupSetRequest)
assert.Nil(t, actualRecordingRuleGroupSet)
assert.Error(t, err)
}

func TestRecordingRuleGroupSetReconciler_Reconcile_5XX_StatusError(t *testing.T) {
mockCtrl := gomock.NewController(t)
recordingRuleGroupClient := createRecordingRuleGroupClientMockWith5XXStatusError(mockCtrl)
mockClientSet := mock_clientset.NewMockClientSetInterface(mockCtrl)
mockClientSet.EXPECT().RecordingRuleGroups().Return(recordingRuleGroupClient).AnyTimes()

scheme := runtime.NewScheme()
utilruntime.Must(coralogixv1alpha1.AddToScheme(scheme))
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: "0",
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go mgr.GetCache().Start(ctx)
mgr.GetCache().WaitForCacheSync(ctx)
withWatch, err := client.NewWithWatch(mgr.GetConfig(), client.Options{
Scheme: mgr.GetScheme(),
})
assert.NoError(t, err)
r := RecordingRuleGroupSetReconciler{
Client: withWatch,
Scheme: mgr.GetScheme(),
CoralogixClientSet: mockClientSet,
}
r.SetupWithManager(mgr)

watcher, _ := r.Client.(client.WithWatch).Watch(ctx, &coralogixv1alpha1.RecordingRuleGroupSetList{})
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

err = r.Client.Create(ctx, expectedRecordingRuleGroupSetCRD())
assert.NoError(t, err)
<-watcher.ResultChan()

result, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
assert.Error(t, err)
assert.Equal(t, defaultErrRequeuePeriod, result.RequeueAfter)

result, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
assert.NoError(t, err)
assert.Equal(t, defaultRequeuePeriod, result.RequeueAfter)

namespacedName := types.NamespacedName{Namespace: "default", Name: "test"}
actualRecordingRuleGroupSetCRD := &coralogixv1alpha1.RecordingRuleGroupSet{}
err = r.Client.Get(ctx, namespacedName, actualRecordingRuleGroupSetCRD)
assert.NoError(t, err)

err = r.Client.Delete(ctx, actualRecordingRuleGroupSetCRD)
<-watcher.ResultChan()
r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "test"}})
}

// createRecordingRuleGroupClientSimpleMock creates a simple mock for RecordingRuleGroupsClientInterface which returns a single recording rule group.
func createRecordingRuleGroupClientSimpleMock(mockCtrl *gomock.Controller) clientset.RecordingRulesGroupsClientInterface {
mockRecordingRuleGroupsClient := mock_clientset.NewMockRecordingRulesGroupsClientInterface(mockCtrl)

var recordingRuleGroupExist bool

mockRecordingRuleGroupsClient.EXPECT().
CreateRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _ *rrg.CreateRuleGroupSet) (*rrg.CreateRuleGroupSetResult, error) {
recordingRuleGroupExist = true
return &rrg.CreateRuleGroupSetResult{Id: "id1"}, nil
}).AnyTimes()

mockRecordingRuleGroupsClient.EXPECT().
GetRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *rrg.FetchRuleGroupSet) (*rrg.OutRuleGroupSet, error) {
if recordingRuleGroupExist {
return recordingRuleGroupSetBackendSchema, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "id1")
}).AnyTimes()

mockRecordingRuleGroupsClient.EXPECT().
DeleteRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *rrg.DeleteRuleGroupSet) (*emptypb.Empty, error) {
if recordingRuleGroupExist {
recordingRuleGroupExist = false
return &emptypb.Empty{}, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "id1")
}).AnyTimes()

return mockRecordingRuleGroupsClient
}

// createRecordingRuleGroupClientMockWith5XXStatusError creates a simple mock for RecordingRuleGroupsClientInterface which first returns 5xx status error, then returns a single recording rule group.
func createRecordingRuleGroupClientMockWith5XXStatusError(mockCtrl *gomock.Controller) clientset.RecordingRulesGroupsClientInterface {
mockRecordingRuleGroupsClient := mock_clientset.NewMockRecordingRulesGroupsClientInterface(mockCtrl)

var recordingRuleGroupExist bool
var wasCalled bool

mockRecordingRuleGroupsClient.EXPECT().
CreateRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _ *rrg.CreateRuleGroupSet) (*rrg.CreateRuleGroupSetResult, error) {
if !wasCalled {
wasCalled = true
return nil, errors.NewInternalError(fmt.Errorf("internal error"))
}
recordingRuleGroupExist = true
return &rrg.CreateRuleGroupSetResult{Id: "id1"}, nil
}).AnyTimes()

mockRecordingRuleGroupsClient.EXPECT().
GetRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *rrg.FetchRuleGroupSet) (*rrg.OutRuleGroupSet, error) {
if recordingRuleGroupExist {
return recordingRuleGroupSetBackendSchema, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "id1")
}).AnyTimes()

mockRecordingRuleGroupsClient.EXPECT().
DeleteRecordingRuleGroupSet(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *rrg.DeleteRuleGroupSet) (*emptypb.Empty, error) {
if recordingRuleGroupExist {
recordingRuleGroupExist = false
return &emptypb.Empty{}, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "id1")
}).AnyTimes()

return mockRecordingRuleGroupsClient
}
Loading

0 comments on commit 9c36860

Please sign in to comment.