From: Kiefer Chang Date: Mon, 11 May 2020 14:04:34 +0000 (+0800) Subject: mgr/dashboard: display OSD operational status X-Git-Tag: v16.1.0~570^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=fd57b5ce0a168768efa22cc56c492e3fa68a78d2;p=ceph.git mgr/dashboard: display OSD operational status Frontend: display deleting state in the OSD ID column. Backend: add `operational_status` to OSD: - `working`: the OSD is in normal operation. - `deleting`: the OSD had been scheduled for deleting. - `unmanaged`: the OSD can't be managed by the Orchestrator. Fixes: https://tracker.ceph.com/issues/45301 Signed-off-by: Kiefer Chang --- diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index 6959b5bc5e2b..1c6b2d26aeba 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import json import logging import time -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Optional, Union from ceph.deployment.drive_group import DriveGroupSpec, DriveGroupValidationError # type: ignore from mgr_util import get_most_recent_rate @@ -74,7 +74,9 @@ class Osd(RESTController): if osd_id >= 0 and osd_id in osds: osds[osd_id]['host'] = host - # Extending by osd histogram data + removing_osd_ids = self.get_removing_osds() + + # Extending by osd histogram and orchestrator data for osd_id, osd in osds.items(): osd['stats'] = {} osd['stats_history'] = {} @@ -89,9 +91,23 @@ class Osd(RESTController): # Gauge stats for stat in ['osd.numpg', 'osd.stat_bytes', 'osd.stat_bytes_used']: osd['stats'][stat.split('.')[1]] = mgr.get_latest('osd', osd_spec, stat) - + osd['operational_status'] = self._get_operational_status(osd_id, removing_osd_ids) return list(osds.values()) + def _get_operational_status(self, osd_id: int, removing_osd_ids: Optional[List[int]]): + if removing_osd_ids is None: + return 'unmanaged' + if osd_id in removing_osd_ids: + return 'deleting' + return 'working' + + @staticmethod + def get_removing_osds() -> Optional[List[int]]: + orch = OrchClient.instance() + if orch.available(features=[OrchFeature.OSD_GET_REMOVE_STATUS]): + return [osd.osd_id for osd in orch.osds.removing_status()] + return None + @staticmethod def get_osd_map(svc_id=None): # type: (Union[int, None]) -> Dict[int, Union[dict, Any]] @@ -127,6 +143,8 @@ class Osd(RESTController): return { 'osd_map': self.get_osd_map(svc_id), 'osd_metadata': mgr.get_metadata('osd', svc_id), + 'operational_status': self._get_operational_status(int(svc_id), + self.get_removing_osds()) } @RESTController.Resource('GET') @@ -209,7 +227,7 @@ class Osd(RESTController): while True: removal_osds = orch.osds.removing_status() logger.info('Current removing OSDs %s', removal_osds) - pending = [osd for osd in removal_osds if osd.osd_id == svc_id] + pending = [osd for osd in removal_osds if osd.osd_id == int(svc_id)] if not pending: break logger.info('Wait until osd.%s is removed...', svc_id) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json index 83590a12b881..2de532703e1a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json @@ -197,7 +197,8 @@ [1594973156.8602881, 0.0], [1594973161.862781, 0.0] ] - } + }, + "operational_status": "working" }, { "osd": 1, @@ -397,7 +398,8 @@ [1594973157.288044, 0.0], [1594973162.2904015, 0.0] ] - } + }, + "operational_status": "unmanaged" }, { "osd": 2, @@ -597,6 +599,7 @@ [1594973156.837437, 0.0], [1594973161.8397536, 0.0] ] - } + }, + "operational_status": "deleting" } ] diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts index 4887847e187f..7d4c85e44f57 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts @@ -256,6 +256,15 @@ describe('OsdListComponent', () => { component.getOsdList(); expect(component.osds[0].cdClusterFlags).toStrictEqual([]); }); + + it('should have custom attribute "cdExecuting"', () => { + osds[1].operational_status = 'unmanaged'; + osds[2].operational_status = 'deleting'; + component.getOsdList(); + expect(component.osds[0].cdExecuting).toBeUndefined(); + expect(component.osds[1].cdExecuting).toBeUndefined(); + expect(component.osds[2].cdExecuting).toBe('deleting'); + }); }); describe('show osd actions as defined', () => { @@ -519,6 +528,7 @@ describe('OsdListComponent', () => { beforeEach(() => { component.permissions = fakeAuthStorageService.getPermissions(); spyOn(osdService, 'getList').and.callFake(() => of(fakeOsds)); + spyOn(osdService, 'getFlags').and.callFake(() => of([])); }); const testTableActions = async ( @@ -557,6 +567,20 @@ describe('OsdListComponent', () => { Create: { disabled: false, disableDesc: '' }, Delete: { disabled: false, disableDesc: '' } } + }, + { + selectRow: fakeOsds[1], // Select a row that is not managed. + expectResults: { + Create: { disabled: false, disableDesc: '' }, + Delete: { disabled: true, disableDesc: '' } + } + }, + { + selectRow: fakeOsds[2], // Select a row that is being deleted. + expectResults: { + Create: { disabled: false, disableDesc: '' }, + Delete: { disabled: true, disableDesc: '' } + } } ]; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts index 5257b368bad5..b03daa392df7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts @@ -267,7 +267,15 @@ export class OsdListComponent extends ListWithDetails implements OnInit { } ]; this.columns = [ - { prop: 'id', name: $localize`ID`, flexGrow: 1, cellTransformation: CellTemplate.bold }, + { + prop: 'id', + name: $localize`ID`, + flexGrow: 1, + cellTransformation: CellTemplate.executing, + customTemplateConfig: { + valueClass: 'bold' + } + }, { prop: 'host.name', name: $localize`Host` }, { prop: 'collectedStates', @@ -339,8 +347,19 @@ export class OsdListComponent extends ListWithDetails implements OnInit { } getDisable(action: 'create' | 'delete', selection: CdTableSelection): boolean | string { - if (action === 'delete' && !selection.hasSelection) { - return true; + if (action === 'delete') { + if (!selection.hasSelection) { + return true; + } else { + // Disable delete action if any selected OSDs are under deleting or unmanaged. + const deletingOSDs = _.some(this.getSelectedOsds(), (osd) => { + const status = _.get(osd, 'operational_status'); + return status === 'deleting' || status === 'unmanaged'; + }); + if (deletingOSDs) { + return true; + } + } } return this.orchService.getTableActionDisableDesc( this.orchStatus, @@ -403,6 +422,10 @@ export class OsdListComponent extends ListWithDetails implements OnInit { osd.cdIsBinary = true; osd.cdIndivFlags = osd.state.filter((f: string) => this.indivFlagNames.includes(f)); osd.cdClusterFlags = resp[1].filter((f: string) => !this.disabledFlags.includes(f)); + const deploy_state = _.get(osd, 'operational_status', 'unmanaged'); + if (deploy_state !== 'unmanaged' && deploy_state !== 'working') { + osd.cdExecuting = deploy_state; + } return osd; }); }); diff --git a/src/pybind/mgr/dashboard/tests/test_osd.py b/src/pybind/mgr/dashboard/tests/test_osd.py index 1534d8de2c82..669dd63bbf8b 100644 --- a/src/pybind/mgr/dashboard/tests/test_osd.py +++ b/src/pybind/mgr/dashboard/tests/test_osd.py @@ -219,7 +219,8 @@ class OsdTest(ControllerTestCase): with mock.patch.object(mgr, 'get', side_effect=mgr_get_replacement): with mock.patch.object(mgr, 'get_counter', side_effect=mgr_get_counter_replacement): with mock.patch.object(mgr, 'get_latest', return_value=1146609664): - yield + with mock.patch.object(Osd, 'get_removing_osds', return_value=[]): + yield def _get_drive_group_data(self, service_id='all_hdd', host_pattern_k='host_pattern', host_pattern_v='*'):