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>
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
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'] = {}
# 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]]
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')
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)
[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"
}
]
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', () => {
beforeEach(() => {
component.permissions = fakeAuthStorageService.getPermissions();
spyOn(osdService, 'getList').and.callFake(() => of(fakeOsds));
+ spyOn(osdService, 'getFlags').and.callFake(() => of([]));
});
const testTableActions = async (
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: '' }
+ }
}
];
}
];
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',
}
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,
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;
});
});
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='*'):