mirror of https://github.com/grafana/loki
operator: Refactor status update to reduce API calls (#8578)
parent
233997ef20
commit
93c35a7a13
@ -1,316 +1,137 @@ |
||||
package status_test |
||||
package status |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"testing" |
||||
|
||||
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" |
||||
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes" |
||||
"github.com/grafana/loki/operator/internal/status" |
||||
"github.com/grafana/loki/operator/internal/manifests" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
v1 "k8s.io/api/core/v1" |
||||
apierrors "k8s.io/apimachinery/pkg/api/errors" |
||||
corev1 "k8s.io/api/core/v1" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
"k8s.io/apimachinery/pkg/runtime/schema" |
||||
"k8s.io/apimachinery/pkg/types" |
||||
ctrl "sigs.k8s.io/controller-runtime" |
||||
"sigs.k8s.io/controller-runtime/pkg/client" |
||||
) |
||||
|
||||
func TestSetComponentsStatus_WhenGetLokiStackReturnsError_ReturnError(t *testing.T) { |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
return apierrors.NewBadRequest("something wasn't found") |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.Error(t, err) |
||||
} |
||||
|
||||
func TestSetComponentsStatus_WhenGetLokiStackReturnsNotFound_DoNothing(t *testing.T) { |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
func createPodList(baseName string, phases ...corev1.PodPhase) *corev1.PodList { |
||||
items := []corev1.Pod{} |
||||
for i, p := range phases { |
||||
items = append(items, corev1.Pod{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: fmt.Sprintf("%s-pod-%d", baseName, i), |
||||
}, |
||||
Status: corev1.PodStatus{ |
||||
Phase: p, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
return &corev1.PodList{ |
||||
Items: items, |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
func TestSetComponentsStatus_WhenListReturnError_ReturnError(t *testing.T) { |
||||
sw := &k8sfakes.FakeStatusWriter{} |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
k.StatusStub = func() client.StatusWriter { return sw } |
||||
|
||||
s := lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
if r.Name == name.Name && r.Namespace == name.Namespace { |
||||
k.SetClientObject(object, &s) |
||||
return nil |
||||
func setupListClient(t *testing.T, stack *lokiv1.LokiStack, componentPods map[string]*corev1.PodList) (*k8sfakes.FakeClient, *k8sfakes.FakeStatusWriter) { |
||||
k, sw := setupFakesNoError(t, stack) |
||||
k.ListStub = func(_ context.Context, list client.ObjectList, options ...client.ListOption) error { |
||||
componentLabel := "" |
||||
for _, o := range options { |
||||
if m, ok := o.(client.MatchingLabels); ok { |
||||
componentLabel = m["app.kubernetes.io/component"] |
||||
} |
||||
} |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
} |
||||
|
||||
k.ListStub = func(_ context.Context, l client.ObjectList, opts ...client.ListOption) error { |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.Error(t, err) |
||||
} |
||||
|
||||
func TestSetComponentsStatus_WhenPodListExisting_SetPodStatusMap(t *testing.T) { |
||||
sw := &k8sfakes.FakeStatusWriter{} |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
k.StatusStub = func() client.StatusWriter { return sw } |
||||
|
||||
s := lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
if r.Name == name.Name && r.Namespace == name.Namespace { |
||||
k.SetClientObject(object, &s) |
||||
return nil |
||||
if componentLabel == "" { |
||||
t.Fatalf("no component label on list call: %s", options) |
||||
} |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
} |
||||
|
||||
k.ListStub = func(_ context.Context, l client.ObjectList, _ ...client.ListOption) error { |
||||
pods := v1.PodList{ |
||||
Items: []v1.Pod{ |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-a", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodPending, |
||||
}, |
||||
}, |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-b", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodRunning, |
||||
}, |
||||
}, |
||||
}, |
||||
podList, ok := componentPods[componentLabel] |
||||
if !ok { |
||||
t.Fatalf("no pods found for label: %s", componentLabel) |
||||
} |
||||
k.SetClientObjectList(l, &pods) |
||||
return nil |
||||
} |
||||
|
||||
expected := lokiv1.PodStatusMap{ |
||||
"Pending": []string{"pod-a"}, |
||||
"Running": []string{"pod-b"}, |
||||
} |
||||
|
||||
sw.UpdateStub = func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error { |
||||
stack := obj.(*lokiv1.LokiStack) |
||||
require.Equal(t, expected, stack.Status.Components.Compactor) |
||||
k.SetClientObjectList(list, podList) |
||||
return nil |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.NoError(t, err) |
||||
require.NotZero(t, k.ListCallCount()) |
||||
require.NotZero(t, k.StatusCallCount()) |
||||
require.NotZero(t, sw.UpdateCallCount()) |
||||
return k, sw |
||||
} |
||||
|
||||
func TestSetComponentsStatus_WhenRulerEnabled_SetPodStatusMap(t *testing.T) { |
||||
sw := &k8sfakes.FakeStatusWriter{} |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
k.StatusStub = func() client.StatusWriter { return sw } |
||||
|
||||
s := lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
Spec: lokiv1.LokiStackSpec{ |
||||
Rules: &lokiv1.RulesSpec{ |
||||
Enabled: true, |
||||
func TestGenerateComponentStatus(t *testing.T) { |
||||
tt := []struct { |
||||
desc string |
||||
componentPods map[string]*corev1.PodList |
||||
wantComponentStatus *lokiv1.LokiStackComponentStatus |
||||
}{ |
||||
{ |
||||
desc: "no pods", |
||||
componentPods: map[string]*corev1.PodList{ |
||||
manifests.LabelCompactorComponent: {}, |
||||
manifests.LabelDistributorComponent: {}, |
||||
manifests.LabelIngesterComponent: {}, |
||||
manifests.LabelQuerierComponent: {}, |
||||
manifests.LabelQueryFrontendComponent: {}, |
||||
manifests.LabelIndexGatewayComponent: {}, |
||||
manifests.LabelRulerComponent: {}, |
||||
manifests.LabelGatewayComponent: {}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
if r.Name == name.Name && r.Namespace == name.Namespace { |
||||
k.SetClientObject(object, &s) |
||||
return nil |
||||
} |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
} |
||||
|
||||
k.ListStub = func(_ context.Context, l client.ObjectList, _ ...client.ListOption) error { |
||||
pods := v1.PodList{ |
||||
Items: []v1.Pod{ |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-a", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodPending, |
||||
}, |
||||
}, |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-b", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodRunning, |
||||
}, |
||||
}, |
||||
wantComponentStatus: &lokiv1.LokiStackComponentStatus{ |
||||
Compactor: map[corev1.PodPhase][]string{}, |
||||
Distributor: map[corev1.PodPhase][]string{}, |
||||
IndexGateway: map[corev1.PodPhase][]string{}, |
||||
Ingester: map[corev1.PodPhase][]string{}, |
||||
Querier: map[corev1.PodPhase][]string{}, |
||||
QueryFrontend: map[corev1.PodPhase][]string{}, |
||||
Gateway: map[corev1.PodPhase][]string{}, |
||||
Ruler: map[corev1.PodPhase][]string{}, |
||||
}, |
||||
} |
||||
k.SetClientObjectList(l, &pods) |
||||
return nil |
||||
} |
||||
|
||||
expected := lokiv1.PodStatusMap{ |
||||
"Pending": []string{"pod-a"}, |
||||
"Running": []string{"pod-b"}, |
||||
} |
||||
|
||||
sw.UpdateStub = func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error { |
||||
stack := obj.(*lokiv1.LokiStack) |
||||
require.Equal(t, expected, stack.Status.Components.Ruler) |
||||
return nil |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.NoError(t, err) |
||||
require.NotZero(t, k.ListCallCount()) |
||||
require.NotZero(t, k.StatusCallCount()) |
||||
require.NotZero(t, sw.UpdateCallCount()) |
||||
} |
||||
|
||||
func TestSetComponentsStatus_WhenRulerNotEnabled_DoNothing(t *testing.T) { |
||||
sw := &k8sfakes.FakeStatusWriter{} |
||||
k := &k8sfakes.FakeClient{} |
||||
|
||||
k.StatusStub = func() client.StatusWriter { return sw } |
||||
|
||||
s := lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
Spec: lokiv1.LokiStackSpec{ |
||||
Rules: &lokiv1.RulesSpec{ |
||||
Enabled: false, |
||||
{ |
||||
desc: "all one pod running", |
||||
componentPods: map[string]*corev1.PodList{ |
||||
manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, corev1.PodRunning), |
||||
manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, corev1.PodRunning), |
||||
manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, corev1.PodRunning), |
||||
manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, corev1.PodRunning), |
||||
manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, corev1.PodRunning), |
||||
manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, corev1.PodRunning), |
||||
manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, corev1.PodRunning), |
||||
manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, corev1.PodRunning), |
||||
}, |
||||
wantComponentStatus: &lokiv1.LokiStackComponentStatus{ |
||||
Compactor: map[corev1.PodPhase][]string{corev1.PodRunning: {"compactor-pod-0"}}, |
||||
Distributor: map[corev1.PodPhase][]string{corev1.PodRunning: {"distributor-pod-0"}}, |
||||
IndexGateway: map[corev1.PodPhase][]string{corev1.PodRunning: {"index-gateway-pod-0"}}, |
||||
Ingester: map[corev1.PodPhase][]string{corev1.PodRunning: {"ingester-pod-0"}}, |
||||
Querier: map[corev1.PodPhase][]string{corev1.PodRunning: {"querier-pod-0"}}, |
||||
QueryFrontend: map[corev1.PodPhase][]string{corev1.PodRunning: {"query-frontend-pod-0"}}, |
||||
Gateway: map[corev1.PodPhase][]string{corev1.PodRunning: {"lokistack-gateway-pod-0"}}, |
||||
Ruler: map[corev1.PodPhase][]string{corev1.PodRunning: {"ruler-pod-0"}}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
r := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object, _ ...client.GetOption) error { |
||||
if r.Name == name.Name && r.Namespace == name.Namespace { |
||||
k.SetClientObject(object, &s) |
||||
return nil |
||||
} |
||||
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found") |
||||
} |
||||
for _, tc := range tt { |
||||
tc := tc |
||||
t.Run(tc.desc, func(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
k.ListStub = func(_ context.Context, l client.ObjectList, o ...client.ListOption) error { |
||||
s := o[0].(client.MatchingLabels) |
||||
stack := &lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
c, ok := s["app.kubernetes.io/component"] |
||||
if !ok || c == "ruler" { |
||||
return nil |
||||
} |
||||
k, _ := setupListClient(t, stack, tc.componentPods) |
||||
|
||||
pods := v1.PodList{ |
||||
Items: []v1.Pod{ |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-a", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodPending, |
||||
}, |
||||
}, |
||||
{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "pod-b", |
||||
}, |
||||
Status: v1.PodStatus{ |
||||
Phase: v1.PodRunning, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
k.SetClientObjectList(l, &pods) |
||||
return nil |
||||
} |
||||
componentStatus, err := generateComponentStatus(context.Background(), k, stack) |
||||
require.NoError(t, err) |
||||
require.Equal(t, tc.wantComponentStatus, componentStatus) |
||||
|
||||
sw.UpdateStub = func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error { |
||||
stack := obj.(*lokiv1.LokiStack) |
||||
require.Equal(t, stack.Status.Components.Ruler, lokiv1.PodStatusMap{}) |
||||
return nil |
||||
// one list call for each component
|
||||
require.Equal(t, 8, k.ListCallCount()) |
||||
}) |
||||
} |
||||
|
||||
err := status.SetComponentsStatus(context.TODO(), k, r) |
||||
require.NoError(t, err) |
||||
require.NotZero(t, k.ListCallCount()) |
||||
require.NotZero(t, k.StatusCallCount()) |
||||
require.NotZero(t, sw.UpdateCallCount()) |
||||
} |
||||
|
@ -0,0 +1,83 @@ |
||||
package status |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
"time" |
||||
|
||||
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" |
||||
"github.com/grafana/loki/operator/internal/manifests" |
||||
"github.com/stretchr/testify/require" |
||||
corev1 "k8s.io/api/core/v1" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
"k8s.io/apimachinery/pkg/types" |
||||
ctrl "sigs.k8s.io/controller-runtime" |
||||
) |
||||
|
||||
func TestRefreshSuccess(t *testing.T) { |
||||
now := time.Now() |
||||
stack := &lokiv1.LokiStack{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
req := ctrl.Request{ |
||||
NamespacedName: types.NamespacedName{ |
||||
Name: "my-stack", |
||||
Namespace: "some-ns", |
||||
}, |
||||
} |
||||
|
||||
componentPods := map[string]*corev1.PodList{ |
||||
manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, corev1.PodRunning), |
||||
manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, corev1.PodRunning), |
||||
manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, corev1.PodRunning), |
||||
manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, corev1.PodRunning), |
||||
manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, corev1.PodRunning), |
||||
manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, corev1.PodRunning), |
||||
manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, corev1.PodRunning), |
||||
manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, corev1.PodRunning), |
||||
} |
||||
|
||||
wantStatus := lokiv1.LokiStackStatus{ |
||||
Components: lokiv1.LokiStackComponentStatus{ |
||||
Compactor: map[corev1.PodPhase][]string{corev1.PodRunning: {"compactor-pod-0"}}, |
||||
Distributor: map[corev1.PodPhase][]string{corev1.PodRunning: {"distributor-pod-0"}}, |
||||
IndexGateway: map[corev1.PodPhase][]string{corev1.PodRunning: {"index-gateway-pod-0"}}, |
||||
Ingester: map[corev1.PodPhase][]string{corev1.PodRunning: {"ingester-pod-0"}}, |
||||
Querier: map[corev1.PodPhase][]string{corev1.PodRunning: {"querier-pod-0"}}, |
||||
QueryFrontend: map[corev1.PodPhase][]string{corev1.PodRunning: {"query-frontend-pod-0"}}, |
||||
Gateway: map[corev1.PodPhase][]string{corev1.PodRunning: {"lokistack-gateway-pod-0"}}, |
||||
Ruler: map[corev1.PodPhase][]string{corev1.PodRunning: {"ruler-pod-0"}}, |
||||
}, |
||||
Storage: lokiv1.LokiStackStorageStatus{}, |
||||
Conditions: []metav1.Condition{ |
||||
{ |
||||
Type: string(lokiv1.ConditionReady), |
||||
Reason: string(lokiv1.ReasonReadyComponents), |
||||
Message: messageReady, |
||||
Status: metav1.ConditionTrue, |
||||
LastTransitionTime: metav1.NewTime(now), |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
k, sw := setupListClient(t, stack, componentPods) |
||||
|
||||
err := Refresh(context.Background(), k, req, now) |
||||
|
||||
require.NoError(t, err) |
||||
require.Equal(t, 1, k.GetCallCount()) |
||||
require.Equal(t, 8, k.ListCallCount()) |
||||
|
||||
require.Equal(t, 1, sw.UpdateCallCount()) |
||||
_, updated, _ := sw.UpdateArgsForCall(0) |
||||
updatedStack, ok := updated.(*lokiv1.LokiStack) |
||||
if !ok { |
||||
t.Fatalf("not a LokiStack: %T", updatedStack) |
||||
} |
||||
|
||||
require.Equal(t, wantStatus, updatedStack.Status) |
||||
} |
Loading…
Reference in new issue