Skip to content

Commit 1240be1

Browse files
committed
Bug 1822396: Delete subscription metric when an operator is uninstalled
When an operator was subscribed to using a Subscription Object, the subscription_sync_total metric was emitted whenever the Subscription Object was created/updated/deleted. This PR updates that behaviour to emit the metric only when the Subscription object is created/updated, and deletes the time series for that particular subscription when the subscription object is deleted.
1 parent 8ad4341 commit 1240be1

File tree

5 files changed

+76
-21
lines changed

5 files changed

+76
-21
lines changed

pkg/controller/operators/catalog/subscription/syncer.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,37 @@ func (s *subscriptionSyncer) now() *metav1.Time {
4545
// Sync reconciles Subscription events by invoking a sequence of reconcilers, passing the result of each
4646
// successful reconciliation as an argument to its successor.
4747
func (s *subscriptionSyncer) Sync(ctx context.Context, event kubestate.ResourceEvent) error {
48-
res := &v1alpha1.Subscription{}
49-
if err := scheme.Convert(event.Resource(), res, nil); err != nil {
48+
sub := &v1alpha1.Subscription{}
49+
if err := scheme.Convert(event.Resource(), sub, nil); err != nil {
5050
return err
5151
}
5252

53-
s.recordMetrics(res)
53+
metrics.EmitSubMetric(sub)
5454

5555
logger := s.logger.WithFields(logrus.Fields{
56-
"reconciling": fmt.Sprintf("%T", res),
57-
"selflink": res.GetSelfLink(),
56+
"reconciling": fmt.Sprintf("%T", sub),
57+
"selflink": sub.GetSelfLink(),
5858
"event": event.Type(),
5959
})
6060
logger.Info("syncing")
6161

6262
// Enter initial state based on subscription and event type
6363
// TODO: Consider generalizing initial generic add, update, delete transitions in the kubestate package.
6464
// Possibly make a resource event aware bridge between Sync and reconciler.
65-
initial := NewSubscriptionState(res.DeepCopy())
65+
initialSubState := NewSubscriptionState(sub.DeepCopy())
6666
switch event.Type() {
6767
case kubestate.ResourceAdded:
68-
initial = initial.Add()
68+
initialSubState = initialSubState.Add()
6969
case kubestate.ResourceUpdated:
70-
initial = initial.Update()
70+
initialSubState = initialSubState.Update()
7171
case kubestate.ResourceDeleted:
72-
initial = initial.Delete()
72+
{
73+
initialSubState = initialSubState.Delete()
74+
metrics.DeleteSubsMetric(sub)
75+
}
7376
}
7477

75-
reconciled, err := s.reconcilers.Reconcile(ctx, initial)
78+
reconciled, err := s.reconcilers.Reconcile(ctx, initialSubState)
7679
if err != nil {
7780
logger.WithError(err).Warn("an error was encountered during reconciliation")
7881
return err
@@ -85,15 +88,6 @@ func (s *subscriptionSyncer) Sync(ctx context.Context, event kubestate.ResourceE
8588
return nil
8689
}
8790

88-
func (s *subscriptionSyncer) recordMetrics(sub *v1alpha1.Subscription) {
89-
// sub.Spec is not a required field.
90-
if sub.Spec == nil {
91-
return
92-
}
93-
94-
metrics.CounterForSubscription(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package).Inc()
95-
}
96-
9791
func (s *subscriptionSyncer) Notify(event kubestate.ResourceEvent) {
9892
s.notify(event)
9993
}

pkg/controller/operators/catalog/subscription/syncer_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ import (
66

77
"github.com/sirupsen/logrus"
88
"github.com/stretchr/testify/require"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
910

1011
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1112
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
1213
)
1314

15+
const (
16+
name = "test-subscription"
17+
packageName = "test-package"
18+
channel = "test-channel"
19+
installedCSVName = "test-csv"
20+
)
21+
1422
func TestSync(t *testing.T) {
1523
type fields struct {
1624
syncer kubestate.Syncer
@@ -39,7 +47,22 @@ func TestSync(t *testing.T) {
3947
args: args{
4048
event: kubestate.NewResourceEvent(
4149
kubestate.ResourceAdded,
42-
&v1alpha1.Subscription{},
50+
&v1alpha1.Subscription{
51+
TypeMeta: metav1.TypeMeta{
52+
Kind: v1alpha1.SubscriptionKind,
53+
APIVersion: v1alpha1.SubscriptionCRDAPIVersion,
54+
},
55+
ObjectMeta: metav1.ObjectMeta{
56+
Name: name,
57+
},
58+
Spec: &v1alpha1.SubscriptionSpec{
59+
Package: packageName,
60+
Channel: channel,
61+
},
62+
Status: v1alpha1.SubscriptionStatus{
63+
InstalledCSV: installedCSVName,
64+
},
65+
},
4366
),
4467
},
4568
want: want{

pkg/metrics/metrics.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package metrics
22

33
import (
44
"context"
5+
56
"github.com/prometheus/client_golang/prometheus"
67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
78
"k8s.io/apimachinery/pkg/labels"
@@ -217,3 +218,11 @@ func EmitCSVMetric(oldCSV *olmv1alpha1.ClusterServiceVersion, newCSV *olmv1alpha
217218
csvAbnormal.WithLabelValues(newCSV.Namespace, newCSV.Name, newCSV.Spec.Version.String(), string(newCSV.Status.Phase), string(newCSV.Status.Reason)).Set(1)
218219
}
219220
}
221+
222+
func EmitSubMetric(sub *olmv1alpha1.Subscription) {
223+
SubscriptionSyncCount.WithLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package).Inc()
224+
}
225+
226+
func DeleteSubsMetric(sub *olmv1alpha1.Subscription) {
227+
SubscriptionSyncCount.DeleteLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package)
228+
}

test/e2e/metrics_e2e_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1818
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
19+
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
1920
)
2021

2122
var _ = Describe("Metrics", func() {
@@ -65,6 +66,22 @@ var _ = Describe("Metrics", func() {
6566
ContainSubstring("csv_abnormal{name=\"%s\"", failingCSV.Name),
6667
ContainSubstring("csv_succeeded{name=\"%s\"", failingCSV.Name),
6768
))
69+
70+
subscriptionCleanup := createSubscription(GinkgoT(), crc, testNamespace, "metric-subscription", testPackageName, stableChannel, v1alpha1.ApprovalManual)
71+
72+
// Verify metrics have been emitted for subscription
73+
Expect(getMetricsFromPod(c, getCatalogPod(c), "8081")).To(And(
74+
ContainSubstring("subscription_sync_total"),
75+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.NAME_LABEL, "metric-subscription")),
76+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.CHANNEL_LABEL, stableChannel)),
77+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.PACKAGE_LABEL, testPackageName)),
78+
))
79+
80+
subscriptionCleanup()
81+
82+
Expect(getMetricsFromPod(c, getCatalogPod(c), "8081")).ToNot(And(
83+
ContainSubstring("subscription_sync_total{name=\"metric-subscription\""),
84+
))
6885
})
6986
})
7087

@@ -80,6 +97,18 @@ func getOLMPod(client operatorclient.ClientInterface) *corev1.Pod {
8097
return &podList.Items[0]
8198
}
8299

100+
func getCatalogPod(client operatorclient.ClientInterface) *corev1.Pod {
101+
listOptions := metav1.ListOptions{LabelSelector: "app=catalog-operator"}
102+
var podList *corev1.PodList
103+
Eventually(func() (err error) {
104+
podList, err = client.KubernetesInterface().CoreV1().Pods(operatorNamespace).List(context.TODO(), listOptions)
105+
return
106+
}).Should(Succeed(), "Failed to list OLM pods")
107+
Expect(len(podList.Items)).To(Equal(1))
108+
109+
return &podList.Items[0]
110+
}
111+
83112
func getMetricsFromPod(client operatorclient.ClientInterface, pod *corev1.Pod, port string) string {
84113
By(fmt.Sprintf("querying pod %s/%s", pod.GetNamespace(), pod.GetName()))
85114

test/e2e/subscription_e2e_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,7 @@ func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace,
18021802
}
18031803

18041804
subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.TODO(), subscription, metav1.CreateOptions{})
1805-
require.NoError(t, err)
1805+
Expect(err).ToNot(HaveOccurred())
18061806
return buildSubscriptionCleanupFunc(crc, subscription)
18071807
}
18081808

0 commit comments

Comments
 (0)