]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: gather facts should only be fetched when orch backend is cephadm 43603/head
authorAvan Thakkar <athakkar@redhat.com>
Wed, 20 Oct 2021 14:07:26 +0000 (19:37 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Tue, 26 Oct 2021 11:07:40 +0000 (16:37 +0530)
Fixes: https://tracker.ceph.com/issues/52981
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
Gather facts in UI should only be fetched if there is orch available and
get_facts feature is available for that orch backend.

src/pybind/mgr/dashboard/controllers/host.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/orchestrator.enum.ts
src/pybind/mgr/dashboard/tests/test_host.py

index 0a897002a4a1cb29b84f6d59e044fc4a963678e6..9d63d0a96ef1f09f0e253293980ffeeb72107487 100644 (file)
@@ -287,9 +287,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 "
index 2c6a4db6983a132af7ef94f8668d9d5a76cf01f5..4535a55911b87250449feb76ea6929dd10f23693 100644 (file)
@@ -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
index fe0b797bd1d7cbd9b91f674f2172897bc51ad778..4e9c36b8e5c09e477071a20dab6829f45a41ab6e 100644 (file)
@@ -389,8 +389,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'];
@@ -423,8 +434,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) => {
index cf4ba76a9cf0960beef7ef9486606e5d697d3a9a..545d072fc45f415cc48a9d1f4e59d58af56950de 100644 (file)
@@ -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',
index a74d7d8e0aa02dd26d93a34cb645a53c71cfdb47..e15885a56951a9eefd1cf282508f5396e3133395 100644 (file)
@@ -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