]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: display OSD operational status 35039/head
authorKiefer Chang <kiefer.chang@suse.com>
Mon, 11 May 2020 14:04:34 +0000 (22:04 +0800)
committerKiefer Chang <kiefer.chang@suse.com>
Mon, 16 Nov 2020 08:03:25 +0000 (16:03 +0800)
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 <kiefer.chang@suse.com>
src/pybind/mgr/dashboard/controllers/osd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/fixtures/osd_list_response.json
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/tests/test_osd.py

index 6959b5bc5e2ba75f69ec0a6b6868da7dec746b60..1c6b2d26aeba11494cba796ec668492f809b94b9 100644 (file)
@@ -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)
index 83590a12b8814b1ac23fc48989cfc10caa0dc8f6..2de532703e1a36529d01342c19a4038a3c25975b 100644 (file)
         [1594973156.8602881, 0.0],
         [1594973161.862781, 0.0]
       ]
-    }
+    },
+    "operational_status": "working"
   },
   {
     "osd": 1,
         [1594973157.288044, 0.0],
         [1594973162.2904015, 0.0]
       ]
-    }
+    },
+    "operational_status": "unmanaged"
   },
   {
     "osd": 2,
         [1594973156.837437, 0.0],
         [1594973161.8397536, 0.0]
       ]
-    }
+    },
+    "operational_status": "deleting"
   }
 ]
index 4887847e187fb66cdc07c5c1eb9341cb13528c6a..7d4c85e44f57ba8a1f97ff0a7c1535b014404ab9 100644 (file)
@@ -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: '' }
+          }
         }
       ];
 
index 5257b368bad51257d724dba8e31d5bb7e80fcbdb..b03daa392df7191468f54766258eb98218118a52 100644 (file)
@@ -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;
       });
     });
index 1534d8de2c82bf606926dd00879a3bd500ba7199..669dd63bbf8b64f941e6897e530c594e7c09dd18 100644 (file)
@@ -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='*'):