Skip to content

Commit 61f51b5

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 66c4a9d commit 61f51b5

File tree

4 files changed

+146
-22
lines changed

4 files changed

+146
-22
lines changed

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (s *subscriptionSyncer) Sync(ctx context.Context, event kubestate.ResourceE
5050
return err
5151
}
5252

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

5555
logger := s.logger.WithFields(logrus.Fields{
5656
"reconciling": fmt.Sprintf("%T", res),
@@ -68,8 +68,10 @@ func (s *subscriptionSyncer) Sync(ctx context.Context, event kubestate.ResourceE
6868
initial = initial.Add()
6969
case kubestate.ResourceUpdated:
7070
initial = initial.Update()
71+
metrics.UpdateSubsSyncCounterStorage(res)
7172
case kubestate.ResourceDeleted:
7273
initial = initial.Delete()
74+
metrics.DeleteSubsMetric(res)
7375
}
7476

7577
reconciled, err := s.reconcilers.Reconcile(ctx, initial)
@@ -85,15 +87,6 @@ func (s *subscriptionSyncer) Sync(ctx context.Context, event kubestate.ResourceE
8587
return nil
8688
}
8789

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-
9790
func (s *subscriptionSyncer) Notify(event kubestate.ResourceEvent) {
9891
s.notify(event)
9992
}

pkg/metrics/metrics.go

Lines changed: 52 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"
@@ -168,8 +169,19 @@ var (
168169
},
169170
[]string{NAMESPACE_LABEL, NAME_LABEL, VERSION_LABEL, PHASE_LABEL, REASON_LABEL},
170171
)
172+
173+
// subscriptionSyncCounters keeps a record of the promethues counters emitted by
174+
// Subscription objects. The key of a record is the Subscription name, while the value
175+
// is struct containing label values used in the counter
176+
subscriptionSyncCounters = make(map[string]subscriptionSyncLabelValues)
171177
)
172178

179+
type subscriptionSyncLabelValues struct {
180+
installedCSV string
181+
pkg string
182+
channel string
183+
}
184+
173185
func RegisterOLM() {
174186
prometheus.MustRegister(csvCount)
175187
prometheus.MustRegister(csvSucceeded)
@@ -217,3 +229,43 @@ func EmitCSVMetric(oldCSV *olmv1alpha1.ClusterServiceVersion, newCSV *olmv1alpha
217229
csvAbnormal.WithLabelValues(newCSV.Namespace, newCSV.Name, newCSV.Spec.Version.String(), string(newCSV.Status.Phase), string(newCSV.Status.Reason)).Set(1)
218230
}
219231
}
232+
233+
func EmitSubMetric(sub *olmv1alpha1.Subscription) {
234+
if sub.Spec == nil {
235+
return
236+
}
237+
SubscriptionSyncCount.WithLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package).Inc()
238+
if _, present := subscriptionSyncCounters[sub.GetName()]; !present {
239+
subscriptionSyncCounters[sub.GetName()] = subscriptionSyncLabelValues{
240+
installedCSV: sub.Status.InstalledCSV,
241+
pkg: sub.Spec.Package,
242+
channel: sub.Spec.Channel,
243+
}
244+
}
245+
}
246+
247+
func DeleteSubsMetric(sub *olmv1alpha1.Subscription) {
248+
if sub.Spec == nil {
249+
return
250+
}
251+
SubscriptionSyncCount.DeleteLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package)
252+
}
253+
254+
func UpdateSubsSyncCounterStorage(sub *olmv1alpha1.Subscription) {
255+
if sub.Spec == nil {
256+
return
257+
}
258+
counterValues := subscriptionSyncCounters[sub.GetName()]
259+
260+
if sub.Spec.Channel != counterValues.channel ||
261+
sub.Spec.Package != counterValues.pkg ||
262+
sub.Status.InstalledCSV != counterValues.installedCSV {
263+
264+
// Delete metric will label values of old Subscription first
265+
SubscriptionSyncCount.DeleteLabelValues(sub.GetName(), counterValues.installedCSV, counterValues.channel, counterValues.pkg)
266+
267+
counterValues.installedCSV = sub.Status.InstalledCSV
268+
counterValues.pkg = sub.Spec.Package
269+
counterValues.channel = sub.Spec.Channel
270+
}
271+
}

test/e2e/metrics_e2e_test.go

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@ package e2e
55
import (
66
"context"
77
"fmt"
8+
"regexp"
9+
"time"
10+
811
. "github.com/onsi/ginkgo"
912
. "github.com/onsi/gomega"
1013
corev1 "k8s.io/api/core/v1"
1114
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1215
"k8s.io/apimachinery/pkg/util/net"
13-
"regexp"
1416

1517
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1618
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
1719
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
20+
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
1821
"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
1922
)
2023

21-
var _ = Describe("Metrics are generated for OLM pod", func() {
24+
var _ = Describe("Metrics are generated for OLM managed resources", func() {
2225

2326
var (
2427
c operatorclient.ClientInterface
@@ -69,7 +72,7 @@ var _ = Describe("Metrics are generated for OLM pod", func() {
6972
It("generates csv_abnormal metric for OLM pod", func() {
7073

7174
// Verify metrics have been emitted for packageserver csv
72-
Expect(getMetricsFromPod(c, getOLMPod(c), "8081")).To(And(
75+
Expect(getMetricsFromPod(c, getPodWithLabel(c, "app=olm-operator"), "8081")).To(And(
7376
ContainSubstring("csv_abnormal"),
7477
ContainSubstring(fmt.Sprintf("name=\"%s\"", failingCSV.Name)),
7578
ContainSubstring("phase=\"Failed\""),
@@ -91,18 +94,89 @@ var _ = Describe("Metrics are generated for OLM pod", func() {
9194

9295
It("deletes its associated CSV metrics", func() {
9396
// Verify that when the csv has been deleted, it deletes the corresponding CSV metrics
94-
Expect(getMetricsFromPod(c, getOLMPod(c), "8081")).ToNot(And(
97+
Expect(getMetricsFromPod(c, getPodWithLabel(c, "app=olm-operator"), "8081")).ToNot(And(
9598
ContainSubstring("csv_abnormal{name=\"%s\"", failingCSV.Name),
9699
ContainSubstring("csv_succeeded{name=\"%s\"", failingCSV.Name),
97100
))
98101
})
99102
})
100103
})
101104
})
105+
106+
Context("Subscription Metric", func() {
107+
var (
108+
subscriptionCleanup cleanupFunc
109+
subscription *v1alpha1.Subscription
110+
)
111+
When("A subscription object is created", func() {
112+
113+
BeforeEach(func() {
114+
subscriptionCleanup, _ = createSubscription(GinkgoT(), crc, testNamespace, "metric-subscription-for-create", testPackageName, stableChannel, v1alpha1.ApprovalManual)
115+
})
116+
117+
It("generates subscription_sync_total metric", func() {
118+
119+
// Verify metrics have been emitted for subscription
120+
Eventually(func() string {
121+
return getMetricsFromPod(c, getPodWithLabel(c, "app=catalog-operator"), "8081")
122+
}, time.Minute, 5*time.Second).Should(And(
123+
ContainSubstring("subscription_sync_total"),
124+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.NAME_LABEL, "metric-subscription-for-create")),
125+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.CHANNEL_LABEL, stableChannel)),
126+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.PACKAGE_LABEL, testPackageName))))
127+
})
128+
if subscriptionCleanup != nil {
129+
subscriptionCleanup()
130+
}
131+
})
132+
When("A subscription object is updated", func() {
133+
134+
BeforeEach(func() {
135+
subscriptionCleanup, subscription = createSubscription(GinkgoT(), crc, testNamespace, "metric-subscription-for-update", testPackageName, stableChannel, v1alpha1.ApprovalManual)
136+
subscription.Spec.Channel = "beta"
137+
updateSubscription(GinkgoT(), crc, subscription)
138+
})
139+
140+
It("deletes the old Subscription metric and emits the new metric", func() {
141+
Eventually(func() string {
142+
return getMetricsFromPod(c, getPodWithLabel(c, "app=catalog-operator"), "8081")
143+
}, time.Minute, 5*time.Second).ShouldNot(And(
144+
ContainSubstring("subscription_sync_total{name=\"metric-subscription-for-update\""),
145+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.CHANNEL_LABEL, stableChannel))))
146+
147+
Eventually(func() string {
148+
return getMetricsFromPod(c, getPodWithLabel(c, "app=catalog-operator"), "8081")
149+
}, time.Minute, 5*time.Second).Should(And(
150+
ContainSubstring("subscription_sync_total"),
151+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.NAME_LABEL, "metric-subscription-for-update")),
152+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.CHANNEL_LABEL, "beta")),
153+
ContainSubstring(fmt.Sprintf("%s=\"%s\"", metrics.PACKAGE_LABEL, testPackageName))))
154+
})
155+
if subscriptionCleanup != nil {
156+
subscriptionCleanup()
157+
}
158+
})
159+
160+
When("A subscription object is deleted", func() {
161+
162+
BeforeEach(func() {
163+
subscriptionCleanup, subscription = createSubscription(GinkgoT(), crc, testNamespace, "metric-subscription-for-delete", testPackageName, stableChannel, v1alpha1.ApprovalManual)
164+
if subscriptionCleanup != nil {
165+
subscriptionCleanup()
166+
}
167+
})
168+
169+
It("deletes the Subscription metric", func() {
170+
Eventually(func() string {
171+
return getMetricsFromPod(c, getPodWithLabel(c, "app=catalog-operator"), "8081")
172+
}, time.Minute, 5*time.Second).ShouldNot(ContainSubstring("subscription_sync_total{name=\"metric-subscription-for-update\""))
173+
})
174+
})
175+
})
102176
})
103177

104-
func getOLMPod(client operatorclient.ClientInterface) *corev1.Pod {
105-
listOptions := metav1.ListOptions{LabelSelector: "app=olm-operator"}
178+
func getPodWithLabel(client operatorclient.ClientInterface, label string) *corev1.Pod {
179+
listOptions := metav1.ListOptions{LabelSelector: label}
106180
var podList *corev1.PodList
107181
Eventually(func() (err error) {
108182
podList, err = client.KubernetesInterface().CoreV1().Pods(operatorNamespace).List(context.TODO(), listOptions)

test/e2e/subscription_e2e_test.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ var _ = Describe("Subscription", func() {
5151
}()
5252
require.NoError(GinkgoT(), initCatalog(GinkgoT(), c, crc))
5353

54-
cleanup := createSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, testPackageName, betaChannel, v1alpha1.ApprovalAutomatic)
54+
cleanup, _ := createSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, testPackageName, betaChannel, v1alpha1.ApprovalAutomatic)
5555
defer cleanup()
5656

5757
subscription, err := fetchSubscription(crc, testNamespace, testSubscriptionName, subscriptionStateAtLatestChecker)
@@ -80,7 +80,7 @@ var _ = Describe("Subscription", func() {
8080
_, err := createCSV(c, crc, stableCSV, testNamespace, false, false)
8181
require.NoError(GinkgoT(), err)
8282

83-
subscriptionCleanup := createSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, testPackageName, alphaChannel, v1alpha1.ApprovalAutomatic)
83+
subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, testPackageName, alphaChannel, v1alpha1.ApprovalAutomatic)
8484
defer subscriptionCleanup()
8585

8686
subscription, err := fetchSubscription(crc, testNamespace, testSubscriptionName, subscriptionStateAtLatestChecker)
@@ -188,7 +188,7 @@ var _ = Describe("Subscription", func() {
188188
}()
189189
require.NoError(GinkgoT(), initCatalog(GinkgoT(), c, crc))
190190

191-
subscriptionCleanup := createSubscription(GinkgoT(), crc, testNamespace, "manual-subscription", testPackageName, stableChannel, v1alpha1.ApprovalManual)
191+
subscriptionCleanup, _ := createSubscription(GinkgoT(), crc, testNamespace, "manual-subscription", testPackageName, stableChannel, v1alpha1.ApprovalManual)
192192
defer subscriptionCleanup()
193193

194194
subscription, err := fetchSubscription(crc, testNamespace, "manual-subscription", subscriptionStateUpgradePendingChecker)
@@ -1783,7 +1783,7 @@ func buildSubscriptionCleanupFunc(crc versioned.Interface, subscription *v1alpha
17831783
}
17841784
}
17851785

1786-
func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace, name, packageName, channel string, approval v1alpha1.Approval) cleanupFunc {
1786+
func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace, name, packageName, channel string, approval v1alpha1.Approval) (cleanupFunc, *v1alpha1.Subscription) {
17871787
subscription := &v1alpha1.Subscription{
17881788
TypeMeta: metav1.TypeMeta{
17891789
Kind: v1alpha1.SubscriptionKind,
@@ -1803,8 +1803,13 @@ func createSubscription(t GinkgoTInterface, crc versioned.Interface, namespace,
18031803
}
18041804

18051805
subscription, err := crc.OperatorsV1alpha1().Subscriptions(namespace).Create(context.TODO(), subscription, metav1.CreateOptions{})
1806-
require.NoError(t, err)
1807-
return buildSubscriptionCleanupFunc(crc, subscription)
1806+
Expect(err).ToNot(HaveOccurred())
1807+
return buildSubscriptionCleanupFunc(crc, subscription), subscription
1808+
}
1809+
1810+
func updateSubscription(t GinkgoTInterface, crc versioned.Interface, subscription *v1alpha1.Subscription) {
1811+
_, err := crc.OperatorsV1alpha1().Subscriptions(subscription.GetNamespace()).Update(context.TODO(), subscription, metav1.UpdateOptions{})
1812+
Expect(err).ToNot(HaveOccurred())
18081813
}
18091814

18101815
func createSubscriptionForCatalog(crc versioned.Interface, namespace, name, catalog, packageName, channel, startingCSV string, approval v1alpha1.Approval) cleanupFunc {

0 commit comments

Comments
 (0)