From 8f2cf7318da1bd3e2db0eae575b73774f31b16fe Mon Sep 17 00:00:00 2001 From: Avan Thakkar Date: Wed, 20 Oct 2021 19:37:26 +0530 Subject: [PATCH] mgr/dashboard: gather facts should only be fetched when orch backend is cephadm Fixes: https://tracker.ceph.com/issues/52981 Signed-off-by: Avan Thakkar Gather facts in UI should only be fetched if there is orch available and get_facts feature is available for that orch backend. (cherry picked from commit 78b8af2bda39dea8aa46a42e0cea4c5fd53c1d42) --- src/pybind/mgr/dashboard/controllers/host.py | 15 ++++++++-- .../cluster/hosts/hosts.component.spec.ts | 30 +++++++++++++++++-- .../app/ceph/cluster/hosts/hosts.component.ts | 17 +++++++++-- .../app/shared/models/orchestrator.enum.ts | 1 + src/pybind/mgr/dashboard/tests/test_host.py | 12 ++++++-- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/host.py b/src/pybind/mgr/dashboard/controllers/host.py index e3fb2cb290ece..04ed00242cf9a 100644 --- a/src/pybind/mgr/dashboard/controllers/host.py +++ b/src/pybind/mgr/dashboard/controllers/host.py @@ -288,9 +288,18 @@ class Host(RESTController): hosts = get_hosts(sources) orch = OrchClient.instance() if str_to_bool(facts): - if orch.available(): - hosts_facts = orch.hosts.get_facts() - return merge_list_of_dicts_by_key(hosts, hosts_facts, 'hostname') + if orch.available(['get_facts']): + try: + hosts_facts = orch.hosts.get_facts() + return merge_list_of_dicts_by_key(hosts, hosts_facts, 'hostname') + + except Exception: + raise DashboardException( + code='invalid_orchestrator_backend', # pragma: no cover + msg="Please enable the cephadm orchestrator backend " + "(try `ceph orch set backend cephadm`)", + component='orchestrator', + http_status_code=400) raise DashboardException(code='orchestrator_status_unavailable', # pragma: no cover msg="Please configure and enable the orchestrator if you " diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts index 2c6a4db6983a1..4535a55911b87 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts @@ -104,7 +104,7 @@ describe('HostsComponent', () => { } ]; - OrchestratorHelper.mockStatus(true); + OrchestratorHelper.mockStatus(false); hostListSpy.and.callFake(() => of(payload)); fixture.detectChanges(); @@ -118,6 +118,7 @@ describe('HostsComponent', () => { }); it('should test if host facts are tranformed correctly if orch available', () => { + const features = [OrchestratorFeature.HOST_FACTS]; const payload = [ { hostname: 'host_test', @@ -137,7 +138,7 @@ describe('HostsComponent', () => { nic_count: 1 } ]; - OrchestratorHelper.mockStatus(true); + OrchestratorHelper.mockStatus(true, features); hostListSpy.and.callFake(() => of(payload)); fixture.detectChanges(); @@ -177,6 +178,31 @@ describe('HostsComponent', () => { expect(spans[7].textContent).toBe('Unavailable'); }); + it('should test if host facts are unavailable if get_fatcs orch feature is not available', () => { + const payload = [ + { + hostname: 'host_test', + services: [ + { + type: 'osd', + id: '0' + } + ] + } + ]; + OrchestratorHelper.mockStatus(true); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + fixture.detectChanges(); + + const spans = fixture.debugElement.nativeElement.querySelectorAll( + '.datatable-body-cell-label span' + ); + expect(spans[7].textContent).toBe('Unavailable'); + }); + it('should show force maintenance modal when it is safe to stop host', () => { const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service. Service will not be operational with no daemons left. At diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts index 6026c180242bc..920e5f35c7bdb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts @@ -392,8 +392,19 @@ export class HostsComponent extends ListWithDetails implements OnDestroy, OnInit }); } + checkHostsFactsAvailable() { + const orchFeatures = this.orchStatus.features; + if (!_.isEmpty(orchFeatures)) { + if (orchFeatures.get_facts.available) { + return true; + } + return false; + } + return false; + } + transformHostsData() { - if (this.orchStatus?.available) { + if (this.checkHostsFactsAvailable()) { _.forEach(this.hosts, (hostKey) => { hostKey['memory_total_bytes'] = hostKey['memory_total_kb'] * 1024; hostKey['raw_capacity'] = hostKey['hdd_capacity_bytes'] + hostKey['flash_capacity_bytes']; @@ -426,8 +437,8 @@ export class HostsComponent extends ListWithDetails implements OnDestroy, OnInit .pipe( mergeMap((orchStatus) => { this.orchStatus = orchStatus; - - return this.hostService.list(`${this.orchStatus?.available}`); + const factsAvailable = this.checkHostsFactsAvailable(); + return this.hostService.list(`${factsAvailable}`); }), map((hostList: object[]) => hostList.map((host) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts index cf4ba76a9cf09..545d072fc45f4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts @@ -6,6 +6,7 @@ export enum OrchestratorFeature { HOST_LABEL_REMOVE = 'remove_host_label', HOST_MAINTENANCE_ENTER = 'enter_host_maintenance', HOST_MAINTENANCE_EXIT = 'exit_host_maintenance', + HOST_FACTS = 'get_facts', SERVICE_LIST = 'describe_service', SERVICE_CREATE = 'apply', diff --git a/src/pybind/mgr/dashboard/tests/test_host.py b/src/pybind/mgr/dashboard/tests/test_host.py index a74d7d8e0aa02..e15885a56951a 100644 --- a/src/pybind/mgr/dashboard/tests/test_host.py +++ b/src/pybind/mgr/dashboard/tests/test_host.py @@ -13,12 +13,13 @@ from . import ControllerTestCase # pylint: disable=no-name-in-module @contextlib.contextmanager -def patch_orch(available: bool, hosts: Optional[List[HostSpec]] = None, +def patch_orch(available: bool, missing_features: Optional[List[str]] = None, + hosts: Optional[List[HostSpec]] = None, inventory: Optional[List[dict]] = None): with mock.patch('dashboard.controllers.orchestrator.OrchClient.instance') as instance: fake_client = mock.Mock() fake_client.available.return_value = available - fake_client.get_missing_features.return_value = [] + fake_client.get_missing_features.return_value = missing_features if hosts is not None: fake_client.hosts.list.return_value = hosts @@ -161,6 +162,13 @@ class HostControllerTest(ControllerTestCase): 'application/vnd.ceph.api.v1.1+json') self.assertJsonBody(hosts_without_facts) + # test with orchestrator available but orch backend!=cephadm + with patch_orch(True, missing_features=['get_facts']) as fake_client: + mock_get_hosts.return_value = hosts_without_facts + # test with ?facts=true + self._get('{}?facts=true'.format(self.URL_HOST), version=APIVersion(1, 1)) + self.assertStatus(400) + # test with no orchestrator available with patch_orch(False): mock_get_hosts.return_value = hosts_without_facts -- 2.39.5