]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix issues with read-only user on landing page 51396/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Mon, 8 May 2023 20:51:40 +0000 (22:51 +0200)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Mon, 29 May 2023 06:58:23 +0000 (08:58 +0200)
Fixes: https://tracker.ceph.com/issues/61418
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
Signed-off-by: Nizamudeen A <nia@redhat.com>
14 files changed:
src/pybind/mgr/dashboard/controllers/cluster.py
src/pybind/mgr/dashboard/controllers/health.py
src/pybind/mgr/dashboard/controllers/orchestrator.py
src/pybind/mgr/dashboard/controllers/prometheus.py
src/pybind/mgr/dashboard/controllers/settings.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/settings.py [new file with mode: 0644]

index 5d776e063513ddea8a7c4fbbaf58829410c2e224..d8170e672e9929fc7a80b381f352cc603cf4eb56 100644 (file)
@@ -19,7 +19,3 @@ class Cluster(RESTController):
                  parameters={'status': (str, 'Cluster Status')})
     def singleton_set(self, status: str):
         ClusterModel(status).to_db()
-
-    @RESTController.Collection('GET', 'capacity')
-    def get_capacity(self):
-        return ClusterModel.get_capacity()
index 992b2bc0277b50d8db6b6a6e7aaa6598e4dfe951..633d37a327eaf8d04ccd2fe2ea9bfe52be9e5b29 100644 (file)
@@ -6,6 +6,7 @@ from .. import mgr
 from ..rest_client import RequestException
 from ..security import Permission, Scope
 from ..services.ceph_service import CephService
+from ..services.cluster import ClusterModel
 from ..services.iscsi_cli import IscsiGatewaysConfig
 from ..services.iscsi_client import IscsiClient
 from ..tools import partial_dict
@@ -291,3 +292,11 @@ class Health(BaseController):
                  responses={200: HEALTH_MINIMAL_SCHEMA})
     def minimal(self):
         return self.health_minimal.all_health()
+
+    @Endpoint()
+    def get_cluster_capacity(self):
+        return ClusterModel.get_capacity()
+
+    @Endpoint()
+    def get_cluster_fsid(self):
+        return mgr.get('config')['fsid']
index 51d0a459dab462f62bccf79cc19999c952c76781..3864820ea630260ad4a6a95be79c710b2c269a8b 100644 (file)
@@ -2,6 +2,7 @@
 
 from functools import wraps
 
+from .. import mgr
 from ..exceptions import DashboardException
 from ..services.orchestrator import OrchClient
 from . import APIDoc, Endpoint, EndpointDoc, ReadPermission, RESTController, UIRouter
@@ -45,3 +46,7 @@ class Orchestrator(RESTController):
                  responses={200: STATUS_SCHEMA})
     def status(self):
         return OrchClient.instance().status()
+
+    @Endpoint()
+    def get_name(self):
+        return mgr.get_module_option_ex('orchestrator', 'orchestrator')
index d9a360799d9eb9403d2d81da0321b63aabe79239..94860344128f7291cf299b8585a48a7aa5f10ce7 100644 (file)
@@ -1,5 +1,4 @@
 # -*- coding: utf-8 -*-
-
 import json
 import os
 import tempfile
@@ -11,8 +10,9 @@ from .. import mgr
 from ..exceptions import DashboardException
 from ..security import Scope
 from ..services import ceph_service
-from ..settings import Settings
-from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router
+from ..services.settings import SettingsService
+from ..settings import Options, Settings
+from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router, UIRouter
 
 
 @Router('/api/prometheus_receiver', secure=False)
@@ -158,3 +158,16 @@ class PrometheusNotifications(RESTController):
                 return PrometheusReceiver.notifications[-1:]
             return PrometheusReceiver.notifications[int(f) + 1:]
         return PrometheusReceiver.notifications
+
+
+@UIRouter('/prometheus', Scope.PROMETHEUS)
+class PrometheusSettings(RESTController):
+    def get(self, name):
+        with SettingsService.attribute_handler(name) as settings_name:
+            setting = getattr(Options, settings_name)
+        return {
+            'name': settings_name,
+            'default': setting.default_value,
+            'type': setting.types_as_str(),
+            'value': getattr(Settings, settings_name)
+        }
index 7e81a0822d1d793fd5c78412d503f689f73b89f0..3876ce2e5692850b947a09734b3f0637aa3f166b 100644 (file)
@@ -1,9 +1,6 @@
 # -*- coding: utf-8 -*-
-from contextlib import contextmanager
-
-import cherrypy
-
 from ..security import Scope
+from ..services.settings import SettingsService, _to_native
 from ..settings import Options
 from ..settings import Settings as SettingsModule
 from . import APIDoc, APIRouter, EndpointDoc, RESTController, UIRouter
@@ -22,29 +19,6 @@ class Settings(RESTController):
     """
     Enables to manage the settings of the dashboard (not the Ceph cluster).
     """
-    @contextmanager
-    def _attribute_handler(self, name):
-        """
-        :type name: str|dict[str, str]
-        :rtype: str|dict[str, str]
-        """
-        if isinstance(name, dict):
-            result = {
-                self._to_native(key): value
-                for key, value in name.items()
-            }
-        else:
-            result = self._to_native(name)
-
-        try:
-            yield result
-        except AttributeError:  # pragma: no cover - handling is too obvious
-            raise cherrypy.NotFound(result)  # pragma: no cover - handling is too obvious
-
-    @staticmethod
-    def _to_native(setting):
-        return setting.upper().replace('-', '_')
-
     @EndpointDoc("Display Settings Information",
                  parameters={
                      'names': (str, 'Name of Settings'),
@@ -69,7 +43,7 @@ class Settings(RESTController):
         return [self._get(name) for name in option_names]
 
     def _get(self, name):
-        with self._attribute_handler(name) as sname:
+        with SettingsService.attribute_handler(name) as sname:
             setting = getattr(Options, sname)
         return {
             'name': sname,
@@ -89,17 +63,17 @@ class Settings(RESTController):
         return self._get(name)
 
     def set(self, name, value):
-        with self._attribute_handler(name) as sname:
-            setattr(SettingsModule, self._to_native(sname), value)
+        with SettingsService.attribute_handler(name) as sname:
+            setattr(SettingsModule, _to_native(sname), value)
 
     def delete(self, name):
-        with self._attribute_handler(name) as sname:
-            delattr(SettingsModule, self._to_native(sname))
+        with SettingsService.attribute_handler(name) as sname:
+            delattr(SettingsModule, _to_native(sname))
 
     def bulk_set(self, **kwargs):
-        with self._attribute_handler(kwargs) as data:
+        with SettingsService.attribute_handler(kwargs) as data:
             for name, value in data.items():
-                setattr(SettingsModule, self._to_native(name), value)
+                setattr(SettingsModule, _to_native(name), value)
 
 
 @UIRouter('/standard_settings')
index 677ca35905b73c703383fe1120d0f8321342ba3f..3c7b7e0c283a6a5c334071c224ccb9b1d9023e7b 100644 (file)
@@ -8,9 +8,7 @@ import _ from 'lodash';
 import { ToastrModule } from 'ngx-toastr';
 import { BehaviorSubject, of } from 'rxjs';
 
-import { ConfigurationService } from '~/app/shared/api/configuration.service';
 import { HealthService } from '~/app/shared/api/health.service';
-import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { PrometheusService } from '~/app/shared/api/prometheus.service';
 import { CssHelper } from '~/app/shared/classes/css-helper';
 import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts';
@@ -25,6 +23,7 @@ import { CardComponent } from '../card/card.component';
 import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
 import { PgSummaryPipe } from '../pg-summary.pipe';
 import { DashboardV3Component } from './dashboard-v3.component';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
 
 export class SummaryServiceMock {
   summaryDataSource = new BehaviorSubject({
@@ -42,8 +41,8 @@ export class SummaryServiceMock {
 describe('Dashbord Component', () => {
   let component: DashboardV3Component;
   let fixture: ComponentFixture<DashboardV3Component>;
-  let configurationService: ConfigurationService;
-  let orchestratorService: MgrModuleService;
+  let healthService: HealthService;
+  let orchestratorService: OrchestratorService;
   let getHealthSpy: jasmine.Spy;
   let getAlertsSpy: jasmine.Spy;
   let fakeFeatureTogglesService: jasmine.Spy;
@@ -133,22 +132,9 @@ describe('Dashbord Component', () => {
     }
   ];
 
-  const configValueData: any = {
-    value: [
-      {
-        section: 'mgr',
-        value: 'e90a0d58-658e-4148-8f61-e896c86f0696'
-      }
-    ]
-  };
+  const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696';
 
-  const orchData: any = {
-    log_level: '',
-    log_to_cluster: false,
-    log_to_cluster_level: 'info',
-    log_to_file: false,
-    orchestrator: 'cephadm'
-  };
+  const orchName: any = 'Cephadm';
 
   configureTestBed({
     imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
@@ -186,8 +172,8 @@ describe('Dashbord Component', () => {
     );
     fixture = TestBed.createComponent(DashboardV3Component);
     component = fixture.componentInstance;
-    configurationService = TestBed.inject(ConfigurationService);
-    orchestratorService = TestBed.inject(MgrModuleService);
+    healthService = TestBed.inject(HealthService);
+    orchestratorService = TestBed.inject(OrchestratorService);
     getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
     getHealthSpy.and.returnValue(of(healthPayload));
     spyOn(TestBed.inject(PrometheusService), 'ifAlertmanagerConfigured').and.callFake((fn) => fn());
@@ -206,8 +192,8 @@ describe('Dashbord Component', () => {
   });
 
   it('should get corresponding data into detailsCardData', () => {
-    spyOn(configurationService, 'get').and.returnValue(of(configValueData));
-    spyOn(orchestratorService, 'getConfig').and.returnValue(of(orchData));
+    spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData));
+    spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
     component.ngOnInit();
     expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696');
     expect(component.detailsCardData.orchestrator).toBe('Cephadm');
index 1aeb57fccc768a8765e8facac08796cd668fa81e..a3bd264c6843507d1766d802b1c1d293372ae11c 100644 (file)
@@ -5,10 +5,7 @@ import { Observable, Subscription, timer } from 'rxjs';
 import { take } from 'rxjs/operators';
 import moment from 'moment';
 
-import { ClusterService } from '~/app/shared/api/cluster.service';
-import { ConfigurationService } from '~/app/shared/api/configuration.service';
 import { HealthService } from '~/app/shared/api/health.service';
-import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { OsdService } from '~/app/shared/api/osd.service';
 import { PrometheusService } from '~/app/shared/api/prometheus.service';
 import { Promqls as queries } from '~/app/shared/enum/dashboard-promqls.enum';
@@ -25,6 +22,7 @@ import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.s
 import { SummaryService } from '~/app/shared/services/summary.service';
 import { PrometheusListHelper } from '~/app/shared/helpers/prometheus-list-helper';
 import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
 
 @Component({
   selector: 'cd-dashboard-v3',
@@ -77,9 +75,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
 
   constructor(
     private summaryService: SummaryService,
-    private configService: ConfigurationService,
-    private mgrModuleService: MgrModuleService,
-    private clusterService: ClusterService,
+    private orchestratorService: OrchestratorService,
     private osdService: OsdService,
     private authStorageService: AuthStorageService,
     private featureToggles: FeatureTogglesService,
@@ -136,12 +132,11 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
   }
 
   getDetailsCardData() {
-    this.configService.get('fsid').subscribe((data) => {
-      this.detailsCardData.fsid = data['value'][0]['value'];
+    this.healthService.getClusterFsid().subscribe((data: string) => {
+      this.detailsCardData.fsid = data;
     });
-    this.mgrModuleService.getConfig('orchestrator').subscribe((data) => {
-      const orchStr = data['orchestrator'];
-      this.detailsCardData.orchestrator = orchStr.charAt(0).toUpperCase() + orchStr.slice(1);
+    this.orchestratorService.getName().subscribe((data: string) => {
+      this.detailsCardData.orchestrator = data;
     });
     this.summaryService.subscribe((summary) => {
       const version = summary.version.replace('ceph version ', '').split(' ');
@@ -157,7 +152,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
       .subscribe((data: any) => {
         this.osdSettings = data;
       });
-    this.capacityService = this.clusterService.getCapacity().subscribe((data: any) => {
+    this.capacityService = this.healthService.getClusterCapacity().subscribe((data: any) => {
       this.capacity = data;
     });
   }
index f5b8e4d7cc11851852bc8409d78a266724494612..6b435d6ffed1dc15a7c5a4de19e9a2d650a7acfe 100644 (file)
@@ -24,8 +24,4 @@ export class ClusterService {
       { headers: { Accept: 'application/vnd.ceph.api.v0.1+json' } }
     );
   }
-
-  getCapacity() {
-    return this.http.get(`${this.baseURL}/capacity`, {});
-  }
 }
index a8f7c467a0ca742c5e11911e2fc9e479f2af7caf..42634a1481cf2fb596630e4508231df85ebe8915 100644 (file)
@@ -14,4 +14,16 @@ export class HealthService {
   getMinimalHealth() {
     return this.http.get('api/health/minimal');
   }
+
+  getClusterCapacity() {
+    return this.http.get('api/health/get_cluster_capacity');
+  }
+
+  getClusterFsid() {
+    return this.http.get('api/health/get_cluster_fsid');
+  }
+
+  getOrchestratorName() {
+    return this.http.get('api/health/get_orchestrator_name');
+  }
 }
index a6e33e8342acbec2b249d0ec518f2f6143078089..a036b3943d2a2d016be8b3af4be3a5ab0144d105 100644 (file)
@@ -43,4 +43,8 @@ export class OrchestratorService {
     }
     return false;
   }
+
+  getName() {
+    return this.http.get(`${this.url}/get_name`);
+  }
 }
index c42f6e7ac1273b13e33fbcb407f6fd27254aac0e..65fc174b92e347079c7f422f68fc73ddee4db237 100644 (file)
@@ -168,7 +168,7 @@ describe('PrometheusService', () => {
     let host: string;
 
     const receiveConfig = () => {
-      const req = httpTesting.expectOne('api/settings/alertmanager-api-host');
+      const req = httpTesting.expectOne('ui-api/prometheus/alertmanager-api-host');
       expect(req.request.method).toBe('GET');
       req.flush({ value: host });
     };
@@ -209,7 +209,7 @@ describe('PrometheusService', () => {
     let host: string;
 
     const receiveConfig = () => {
-      const req = httpTesting.expectOne('api/settings/prometheus-api-host');
+      const req = httpTesting.expectOne('ui-api/prometheus/prometheus-api-host');
       expect(req.request.method).toBe('GET');
       req.flush({ value: host });
     };
index 30d7d488649ae21dc7a3e8a5954219e4365b1012..340f89ca38f8ff9f9d7d5d83ac5de8f18ce8f4a5 100644 (file)
@@ -10,7 +10,6 @@ import {
   AlertmanagerNotification,
   PrometheusRuleGroup
 } from '../models/prometheus-alerts';
-import { SettingsService } from './settings.service';
 
 @Injectable({
   providedIn: 'root'
@@ -18,30 +17,31 @@ import { SettingsService } from './settings.service';
 export class PrometheusService {
   private baseURL = 'api/prometheus';
   private settingsKey = {
-    alertmanager: 'api/settings/alertmanager-api-host',
-    prometheus: 'api/settings/prometheus-api-host'
+    alertmanager: 'ui-api/prometheus/alertmanager-api-host',
+    prometheus: 'ui-api/prometheus/prometheus-api-host'
   };
+  private settings: { [url: string]: string } = {};
 
-  constructor(private http: HttpClient, private settingsService: SettingsService) {}
+  constructor(private http: HttpClient) {}
 
   getPrometheusData(params: any): any {
     return this.http.get<any>(`${this.baseURL}/data`, { params });
   }
 
   ifAlertmanagerConfigured(fn: (value?: string) => void, elseFn?: () => void): void {
-    this.settingsService.ifSettingConfigured(this.settingsKey.alertmanager, fn, elseFn);
+    this.ifSettingConfigured(this.settingsKey.alertmanager, fn, elseFn);
   }
 
   disableAlertmanagerConfig(): void {
-    this.settingsService.disableSetting(this.settingsKey.alertmanager);
+    this.disableSetting(this.settingsKey.alertmanager);
   }
 
   ifPrometheusConfigured(fn: (value?: string) => void, elseFn?: () => void): void {
-    this.settingsService.ifSettingConfigured(this.settingsKey.prometheus, fn, elseFn);
+    this.ifSettingConfigured(this.settingsKey.prometheus, fn, elseFn);
   }
 
   disablePrometheusConfig(): void {
-    this.settingsService.disableSetting(this.settingsKey.prometheus);
+    this.disableSetting(this.settingsKey.prometheus);
   }
 
   getAlerts(params = {}): Observable<AlertmanagerAlert[]> {
@@ -83,4 +83,36 @@ export class PrometheusService {
     }`;
     return this.http.get<AlertmanagerNotification[]>(url);
   }
+
+  ifSettingConfigured(url: string, fn: (value?: string) => void, elseFn?: () => void): void {
+    const setting = this.settings[url];
+    if (setting === undefined) {
+      this.http.get(url).subscribe(
+        (data: any) => {
+          this.settings[url] = this.getSettingsValue(data);
+          this.ifSettingConfigured(url, fn, elseFn);
+        },
+        (resp) => {
+          if (resp.status !== 401) {
+            this.settings[url] = '';
+          }
+        }
+      );
+    } else if (setting !== '') {
+      fn(setting);
+    } else {
+      if (elseFn) {
+        elseFn();
+      }
+    }
+  }
+
+  // Easiest way to stop reloading external content that can't be reached
+  disableSetting(url: string) {
+    this.settings[url] = '';
+  }
+
+  private getSettingsValue(data: any): string {
+    return data.value || data.instance || '';
+  }
 }
index 120816fdac3d317a70efeaa178e4f3bd55b4d974..a46fbe1767d6f668fe0118661d0a23da449aa5a8 100644 (file)
@@ -2151,28 +2151,6 @@ paths:
       summary: Update the cluster status
       tags:
       - Cluster
-  /api/cluster/capacity:
-    get:
-      parameters: []
-      responses:
-        '200':
-          content:
-            application/vnd.ceph.api.v1.0+json:
-              type: object
-          description: OK
-        '400':
-          description: Operation exception. Please check the response body for details.
-        '401':
-          description: Unauthenticated access. Please login first.
-        '403':
-          description: Unauthorized access. Please check your permissions.
-        '500':
-          description: Unexpected error. Please check the response body for the stack
-            trace.
-      security:
-      - jwt: []
-      tags:
-      - Cluster
   /api/cluster/user:
     get:
       description: "\n        Get list of ceph users and its respective data\n   \
@@ -3290,6 +3268,50 @@ paths:
       - jwt: []
       tags:
       - Health
+  /api/health/get_cluster_capacity:
+    get:
+      parameters: []
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      tags:
+      - Health
+  /api/health/get_cluster_fsid:
+    get:
+      parameters: []
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      tags:
+      - Health
   /api/health/minimal:
     get:
       parameters: []
diff --git a/src/pybind/mgr/dashboard/services/settings.py b/src/pybind/mgr/dashboard/services/settings.py
new file mode 100644 (file)
index 0000000..373d396
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from contextlib import contextmanager
+
+import cherrypy
+
+
+class SettingsService:
+    @contextmanager
+    # pylint: disable=no-self-argument
+    def attribute_handler(name):
+        """
+        :type name: str|dict[str, str]
+        :rtype: str|dict[str, str]
+        """
+        if isinstance(name, dict):
+            result = {
+                _to_native(key): value
+                for key, value in name.items()
+            }
+        else:
+            result = _to_native(name)
+
+        try:
+            yield result
+        except AttributeError:  # pragma: no cover - handling is too obvious
+            raise cherrypy.NotFound(result)  # pragma: no cover - handling is too obvious
+
+
+def _to_native(setting):
+    return setting.upper().replace('-', '_')