]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Creating and editing Prometheus AlertManager silences is buggy 46277/head
authorVolker Theile <vtheile@suse.com>
Mon, 9 May 2022 13:31:15 +0000 (15:31 +0200)
committerVolker Theile <vtheile@suse.com>
Thu, 19 May 2022 07:31:07 +0000 (09:31 +0200)
When creating a new monitoring silence the form is pre-filled with the wrong alert data. It is always used the alert data from the very first object in the list of the API response but not the specified alert identified by the 'fingerprint' property.

The same problem applies to editing silences. The selected silence is not edited, it's always the first one in the list returned API response but not that with the specified 'id' property.

The main problem of the origin implementation is that the Prometheus Alertmanager API endpoints /api/v1/[alerts/silences] do not support querying. To fix that, filtering is done in the frontend.

Fixes: https://tracker.ceph.com/issues/55578
Signed-off-by: Volker Theile <vtheile@suse.com>
(cherry picked from commit 658486b566f0f9cac2fc0225c4cd78702f943d40)

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-form/silence-form.component.ts

index 136375cbbbe38784b793ec7c9f75a1c0bffed944..41898315019c9e8dafe960dc0e2ebb15a4f5f6b6 100644 (file)
@@ -85,7 +85,7 @@ describe('SilenceFormComponent', () => {
   const changeAction = (action: string) => {
     const modes = {
       add: '/monitoring/silences/add',
-      alertAdd: '/monitoring/silences/add/someAlert',
+      alertAdd: '/monitoring/silences/add/alert0',
       recreate: '/monitoring/silences/recreate/someExpiredId',
       edit: '/monitoring/silences/edit/someNotExpiredId'
     };
@@ -99,9 +99,10 @@ describe('SilenceFormComponent', () => {
 
     prometheus = new PrometheusHelper();
     prometheusService = TestBed.inject(PrometheusService);
-    spyOn(prometheusService, 'getAlerts').and.callFake(() =>
-      of([prometheus.createAlert('alert0')])
-    );
+    spyOn(prometheusService, 'getAlerts').and.callFake(() => {
+      const name = _.split(router.url, '/').pop();
+      return of([prometheus.createAlert(name)]);
+    });
     ifPrometheusSpy = spyOn(prometheusService, 'ifPrometheusConfigured').and.callFake((fn) => fn());
     rulesSpy = spyOn(prometheusService, 'getRules').and.callFake(() =>
       of({
@@ -231,9 +232,10 @@ describe('SilenceFormComponent', () => {
     };
 
     beforeEach(() => {
-      spyOn(prometheusService, 'getSilences').and.callFake((p) =>
-        of([prometheus.createSilence(p.id)])
-      );
+      spyOn(prometheusService, 'getSilences').and.callFake(() => {
+        const id = _.split(router.url, '/').pop();
+        return of([prometheus.createSilence(id)]);
+      });
     });
 
     it('should have no special action activate by default', () => {
@@ -251,7 +253,7 @@ describe('SilenceFormComponent', () => {
     it('should be in edit action if route includes edit', () => {
       params = { id: 'someNotExpiredId' };
       expectMode('edit', true, false, 'Edit');
-      expect(prometheusService.getSilences).toHaveBeenCalledWith(params);
+      expect(prometheusService.getSilences).toHaveBeenCalled();
       expect(component.form.value).toEqual({
         comment: `A comment for ${params.id}`,
         createdBy: `Creator of ${params.id}`,
@@ -265,7 +267,7 @@ describe('SilenceFormComponent', () => {
     it('should be in recreation action if route includes recreate', () => {
       params = { id: 'someExpiredId' };
       expectMode('recreate', false, true, 'Recreate');
-      expect(prometheusService.getSilences).toHaveBeenCalledWith(params);
+      expect(prometheusService.getSilences).toHaveBeenCalled();
       expect(component.form.value).toEqual({
         comment: `A comment for ${params.id}`,
         createdBy: `Creator of ${params.id}`,
@@ -277,7 +279,7 @@ describe('SilenceFormComponent', () => {
     });
 
     it('adds matchers based on the label object of the alert with the given id', () => {
-      params = { id: 'someAlert' };
+      params = { id: 'alert0' };
       expectMode('alertAdd', false, false, 'Create');
       expect(prometheusService.getSilences).not.toHaveBeenCalled();
       expect(prometheusService.getAlerts).toHaveBeenCalled();
index b3bc1401082a66487b29197b583f35551cb29b7c..b698e4958eddacbdfb10716da42ad8c2b2b47be3 100644 (file)
@@ -217,12 +217,18 @@ export class SilenceFormComponent {
         return;
       }
       if (this.edit || this.recreate) {
-        this.prometheusService.getSilences(params).subscribe((silences) => {
-          this.fillFormWithSilence(silences[0]);
+        this.prometheusService.getSilences().subscribe((silences) => {
+          const silence = _.find(silences, ['id', params.id]);
+          if (!_.isUndefined(silence)) {
+            this.fillFormWithSilence(silence);
+          }
         });
       } else {
-        this.prometheusService.getAlerts(params).subscribe((alerts) => {
-          this.fillFormByAlert(alerts[0]);
+        this.prometheusService.getAlerts().subscribe((alerts) => {
+          const alert = _.find(alerts, ['fingerprint', params.id]);
+          if (!_.isUndefined(alert)) {
+            this.fillFormByAlert(alert);
+          }
         });
       }
     });