From cf4ff7d2f03bc285a3fae3f27577333f11dab58a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alfonso=20Mart=C3=ADnez?= Date: Fri, 22 May 2020 13:36:10 +0200 Subject: [PATCH] mgr/dashboard: grafana panels for rgw multisite sync performance MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * RGW sync perf. counters are now exposed through grafana panels. * Sync Performance tab is only shown if rgw realm is detected. * Prometheus module: added metrics suitable for prometheus consumption (from existing ones, not replacing for backward compatibility). Fixes: https://tracker.ceph.com/issues/45310 Signed-off-by: Alfonso Martínez --- .../dashboards/radosgw-sync-overview.json | 440 ++++++++++++++++++ qa/tasks/mgr/dashboard/test_rgw.py | 21 + src/pybind/mgr/dashboard/controllers/rgw.py | 8 +- .../rgw-bucket-form.component.spec.ts | 2 +- .../rgw-bucket-form.component.ts | 2 +- .../rgw-daemon-list.component.html | 9 + .../rgw-daemon-list.component.spec.ts | 40 +- .../rgw-daemon-list.component.ts | 19 +- .../app/shared/api/rgw-site.service.spec.ts | 12 +- .../src/app/shared/api/rgw-site.service.ts | 6 +- .../mgr/dashboard/services/rgw_client.py | 10 + .../mgr/dashboard/tests/test_rgw_client.py | 21 +- src/pybind/mgr/prometheus/module.py | 28 ++ 13 files changed, 599 insertions(+), 19 deletions(-) create mode 100644 monitoring/grafana/dashboards/radosgw-sync-overview.json diff --git a/monitoring/grafana/dashboards/radosgw-sync-overview.json b/monitoring/grafana/dashboards/radosgw-sync-overview.json new file mode 100644 index 0000000000000..e9136d78e3b7c --- /dev/null +++ b/monitoring/grafana/dashboards/radosgw-sync-overview.json @@ -0,0 +1,440 @@ +{ + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "5.0.0" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "5.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": false, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1534386107523, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (source_zone) (rate(ceph_data_sync_from_zone_fetch_bytes_sum[30s]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{source_zone}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Replication (throughput) from Source Zone", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "unit": "bytes", + "format": "Bps", + "decimals": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "fill": 1, + "gridPos": { + "h": 7, + "w": 7.4, + "x": 8.3, + "y": 0 + }, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (source_zone) (rate(ceph_data_sync_from_zone_fetch_bytes_count[30s]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{source_zone}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Replication (objects) from Source Zone", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "decimals": null, + "label": "Objects/s", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (source_zone) (rate(ceph_data_sync_from_zone_poll_latency_sum[30s]) * 1000)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{source_zone}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Polling Request Latency from Source Zone", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "unit": "s", + "format": "ms", + "decimals": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 7 + }, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (source_zone) (rate(ceph_data_sync_from_zone_fetch_errors[30s]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{source_zone}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Unsuccessful Object Replications from Source Zone", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "decimals": null, + "label": "Count/s", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ] + } + ], + "refresh": "15s", + "schemaVersion": 16, + "style": "dark", + "tags": [ + "overview" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "$datasource", + "hide": 2, + "includeAll": true, + "label": null, + "multi": false, + "name": "rgw_servers", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "tags": [], + "text": "default", + "value": "default" + }, + "hide": 0, + "label": "Data Source", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "15s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "RGW Sync Overview", + "uid": "rgw-sync-overview", + "version": 2 +} diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index 5c70ea66e762c..9f5f782347e05 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -112,6 +112,27 @@ class RgwApiCredentialsTest(RgwTestCase): data['message']) +class RgwSiteTest(RgwTestCase): + + AUTH_ROLES = ['rgw-manager'] + + def test_get_placement_targets(self): + data = self._get('/api/rgw/site?query=placement-targets') + self.assertStatus(200) + self.assertSchema(data, JObj({ + 'zonegroup': str, + 'placement_targets': JList(JObj({ + 'name': str, + 'data_pool': str + })) + })) + + def test_get_realms(self): + data = self._get('/api/rgw/site?query=realms') + self.assertStatus(200) + self.assertSchema(data, JList(str)) + + class RgwBucketTest(RgwTestCase): _mfa_token_serial = '1' diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index 433113763f12a..fc7d5190ed29d 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -115,11 +115,11 @@ class RgwRESTController(RESTController): class RgwSite(RgwRESTController): def list(self, query=None): if query == 'placement-targets': - instance = RgwClient.admin_instance() - result = instance.get_placement_targets() + result = RgwClient.admin_instance().get_placement_targets() + elif query == 'realms': + result = RgwClient.admin_instance().get_realms() else: - # @TODO: (it'll be required for multisite workflows): - # by default, retrieve cluster realms/zonegroups map. + # @TODO: for multisite: by default, retrieve cluster topology/map. raise DashboardException(http_status_code=501, component='rgw', msg='Not Implemented') return result diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts index 52a7f22cef376..ac69006731f6e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts @@ -45,7 +45,7 @@ describe('RgwBucketFormComponent', () => { component = fixture.componentInstance; rgwBucketService = TestBed.get(RgwBucketService); rgwBucketServiceGetSpy = spyOn(rgwBucketService, 'get'); - getPlacementTargetsSpy = spyOn(TestBed.get(RgwSiteService), 'getPlacementTargets'); + getPlacementTargetsSpy = spyOn(TestBed.get(RgwSiteService), 'get'); enumerateSpy = spyOn(TestBed.get(RgwUserService), 'enumerate'); formHelper = new FormHelper(component.bucketForm); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts index 3c9cba3d49bda..77ce8badcabd5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts @@ -95,7 +95,7 @@ export class RgwBucketFormComponent extends CdForm implements OnInit { }; if (!this.editing) { - promises['getPlacementTargets'] = this.rgwSiteService.getPlacementTargets(); + promises['getPlacementTargets'] = this.rgwSiteService.get('placement-targets'); } // Process route parameters. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html index 51c0fd95a5ac0..71d9ab67c94a4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html @@ -21,4 +21,13 @@ grafanaStyle="two"> + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts index 79239e3a24754..19aacc0acd95d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts @@ -4,8 +4,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { TabsModule } from 'ngx-bootstrap/tabs'; +import { of } from 'rxjs'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; +import { RgwSiteService } from '../../../shared/api/rgw-site.service'; +import { Permissions } from '../../../shared/models/permissions'; +import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { SharedModule } from '../../../shared/shared.module'; import { PerformanceCounterModule } from '../../performance-counter/performance-counter.module'; import { RgwDaemonDetailsComponent } from '../rgw-daemon-details/rgw-daemon-details.component'; @@ -14,6 +18,15 @@ import { RgwDaemonListComponent } from './rgw-daemon-list.component'; describe('RgwDaemonListComponent', () => { let component: RgwDaemonListComponent; let fixture: ComponentFixture; + let getPermissionsSpy: jasmine.Spy; + let getRealmsSpy: jasmine.Spy; + const permissions = new Permissions({ grafana: ['read'] }); + const expectTabsAndHeading = (length: number, heading: string) => { + const tabs = fixture.debugElement.nativeElement.querySelectorAll('tab'); + + expect(tabs.length).toEqual(length); + expect(tabs[length - 1].getAttribute('heading')).toEqual(heading); + }; configureTestBed({ declarations: [RgwDaemonListComponent, RgwDaemonDetailsComponent], @@ -29,12 +42,37 @@ describe('RgwDaemonListComponent', () => { }); beforeEach(() => { + getPermissionsSpy = spyOn(TestBed.get(AuthStorageService), 'getPermissions'); + getPermissionsSpy.and.returnValue(new Permissions({})); + getRealmsSpy = spyOn(TestBed.get(RgwSiteService), 'get'); + getRealmsSpy.and.returnValue(of([])); fixture = TestBed.createComponent(RgwDaemonListComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); + + it('should only show Daemons List tab', () => { + fixture.detectChanges(); + + expectTabsAndHeading(1, 'Daemons List'); + }); + + it('should show Overall Performance tab', () => { + getPermissionsSpy.and.returnValue(permissions); + fixture.detectChanges(); + + expectTabsAndHeading(2, 'Overall Performance'); + }); + + it('should show Sync Performance tab', () => { + getPermissionsSpy.and.returnValue(permissions); + getRealmsSpy.and.returnValue(of(['realm1'])); + fixture.detectChanges(); + + expectTabsAndHeading(3, 'Sync Performance'); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts index 9f22e860b4de5..7a43cfeeadfb9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts @@ -1,8 +1,9 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { I18n } from '@ngx-translate/i18n-polyfill'; import { RgwDaemonService } from '../../../shared/api/rgw-daemon.service'; +import { RgwSiteService } from '../../../shared/api/rgw-site.service'; import { ListWithDetails } from '../../../shared/classes/list-with-details.class'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context'; @@ -15,18 +16,23 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic templateUrl: './rgw-daemon-list.component.html', styleUrls: ['./rgw-daemon-list.component.scss'] }) -export class RgwDaemonListComponent extends ListWithDetails { +export class RgwDaemonListComponent extends ListWithDetails implements OnInit { columns: CdTableColumn[] = []; daemons: object[] = []; grafanaPermission: Permission; + isMultiSite: boolean; constructor( private rgwDaemonService: RgwDaemonService, private authStorageService: AuthStorageService, - cephShortVersionPipe: CephShortVersionPipe, - private i18n: I18n + private cephShortVersionPipe: CephShortVersionPipe, + private i18n: I18n, + private rgwSiteService: RgwSiteService ) { super(); + } + + ngOnInit(): void { this.grafanaPermission = this.authStorageService.getPermissions().grafana; this.columns = [ { @@ -43,9 +49,12 @@ export class RgwDaemonListComponent extends ListWithDetails { name: this.i18n('Version'), prop: 'version', flexGrow: 1, - pipe: cephShortVersionPipe + pipe: this.cephShortVersionPipe } ]; + this.rgwSiteService + .get('realms') + .subscribe((realms: string[]) => (this.isMultiSite = realms.length > 0)); } getDaemonList(context: CdTableFetchDataContext) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.spec.ts index 8f4df0bc00032..a059c2f918840 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.spec.ts @@ -26,9 +26,15 @@ describe('RgwSiteService', () => { expect(service).toBeTruthy(); }); - it('should call getPlacementTargets', () => { - service.getPlacementTargets().subscribe(); - const req = httpTesting.expectOne('api/rgw/site?query=placement-targets'); + it('should contain site endpoint in GET request', () => { + service.get().subscribe(); + const req = httpTesting.expectOne(service['url']); expect(req.request.method).toBe('GET'); }); + + it('should add query param in GET request', () => { + const query = 'placement-targets'; + service.get(query).subscribe(); + httpTesting.expectOne(`${service['url']}?query=placement-targets`); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.ts index 3d76b3f8d494c..a0bd328c70443 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-site.service.ts @@ -13,9 +13,11 @@ export class RgwSiteService { constructor(private http: HttpClient) {} - getPlacementTargets() { + get(query?: string) { let params = new HttpParams(); - params = params.append('query', 'placement-targets'); + if (query) { + params = params.append('query', query); + } return this.http.get(this.url, { params: params }); } diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 587ba2d4a2cc1..5507eb6800757 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -252,6 +252,9 @@ class RgwClient(RestClient): ['api_name', 'zones'] ) for zonegroup in zonegroups['zonegroups']] + def _get_realms_info(self): # type: () -> dict + return json_str_to_object(self.proxy('GET', 'realm?list', None, None)) + @staticmethod def _rgw_settings(): return (Settings.RGW_API_HOST, @@ -491,6 +494,13 @@ class RgwClient(RestClient): return {'zonegroup': zonegroup_name, 'placement_targets': placement_targets} + def get_realms(self): # type: () -> List + realms_info = self._get_realms_info() + if 'realms' in realms_info and realms_info['realms']: + return realms_info['realms'] + + return [] + @RestClient.api_get('/{bucket_name}?versioning') def get_bucket_versioning(self, bucket_name, request=None): """ diff --git a/src/pybind/mgr/dashboard/tests/test_rgw_client.py b/src/pybind/mgr/dashboard/tests/test_rgw_client.py index 7e153a2eefc66..d69ab2f48df5c 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw_client.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw_client.py @@ -3,9 +3,9 @@ import unittest try: - from mock import patch -except ImportError: from unittest.mock import patch +except ImportError: + from mock import patch # type: ignore from ..services.rgw_client import RgwClient, _parse_frontend_config from ..settings import Settings @@ -124,6 +124,23 @@ class RgwClientTest(unittest.TestCase, KVStoreMockMixin): } self.assertEqual(expected_result, instance.get_placement_targets()) + @patch.object(RgwClient, '_get_realms_info') + def test_get_realms(self, realms_info): + realms_info.side_effect = [ + { + 'default_info': '51de8373-bc24-4f74-a9b7-8e9ef4cb71f7', + 'realms': [ + 'realm1', + 'realm2' + ] + }, + {} + ] + instance = RgwClient.admin_instance() + + self.assertEqual(['realm1', 'realm2'], instance.get_realms()) + self.assertEqual([], instance.get_realms()) + class RgwClientHelperTest(unittest.TestCase): def test_parse_frontend_config_1(self): diff --git a/src/pybind/mgr/prometheus/module.py b/src/pybind/mgr/prometheus/module.py index 2cb7849577f8b..d9e58e5c5e790 100644 --- a/src/pybind/mgr/prometheus/module.py +++ b/src/pybind/mgr/prometheus/module.py @@ -900,6 +900,33 @@ class Module(MgrModule): del self.rbd_stats['query'] self.rbd_stats['pools'].clear() + def add_fixed_name_metrics(self): + """ + Add fixed name metrics from existing ones that have details in their names + that should be in labels (not in name). + For backward compatibility, a new fixed name metric is created (instead of replacing) + and details are put in new labels. + Intended for RGW sync perf. counters but extendable as required. + See: https://tracker.ceph.com/issues/45311 + """ + new_metrics = {} + for metric_path in self.metrics.keys(): + # Address RGW sync perf. counters. + match = re.search('^data-sync-from-(.*)\.', metric_path) + if match: + new_path = re.sub('from-([^.]*)', 'from-zone', metric_path) + if new_path not in new_metrics: + new_metrics[new_path] = Metric( + self.metrics[metric_path].mtype, + new_path, + self.metrics[metric_path].desc, + self.metrics[metric_path].labelnames + ('source_zone',) + ) + for label_values, value in self.metrics[metric_path].value.items(): + new_metrics[new_path].set(value, label_values + (match.group(1),)) + + self.metrics.update(new_metrics) + def collect(self): # Clear the metrics before scraping for k in self.metrics.keys(): @@ -962,6 +989,7 @@ class Module(MgrModule): ) self.metrics[path].set(value, labels) + self.add_fixed_name_metrics() self.get_rbd_stats() # Return formatted metrics and clear no longer used data -- 2.39.5