]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add backend unit tests for orchestrator controllers
authorKiefer Chang <kiefer.chang@suse.com>
Tue, 6 Aug 2019 09:55:40 +0000 (17:55 +0800)
committerKiefer Chang <kiefer.chang@suse.com>
Fri, 30 Aug 2019 05:36:21 +0000 (13:36 +0800)
Signed-off-by: Kiefer Chang <kiefer.chang@suse.com>
src/pybind/mgr/dashboard/controllers/orchestrator.py
src/pybind/mgr/dashboard/tests/test_host.py [new file with mode: 0644]
src/pybind/mgr/dashboard/tests/test_orchestrator.py [new file with mode: 0644]

index 1f0839cc4d8b566f9cef8b92ca23f1f7daf0308b..7406d46ea6aa01a4c79d0d7555323fe7e344fbae 100644 (file)
@@ -1,16 +1,29 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
 
+import cherrypy
+
 from . import ApiController, Endpoint, ReadPermission
 from . import RESTController, Task
 from ..security import Scope
 from ..services.orchestrator import OrchClient
+from ..tools import wraps
 
 
 def orchestrator_task(name, metadata, wait_for=2.0):
     return Task("orchestrator/{}".format(name), metadata, wait_for)
 
 
+def raise_if_no_orchestrator(method):
+    @wraps(method)
+    def inner(self, *args, **kwargs):
+        orch = OrchClient.instance()
+        if not orch.available():
+            raise cherrypy.HTTPError(503)
+        return method(self, *args, **kwargs)
+    return inner
+
+
 @ApiController('/orchestrator')
 class Orchestrator(RESTController):
 
@@ -23,24 +36,18 @@ class Orchestrator(RESTController):
 @ApiController('/orchestrator/inventory', Scope.HOSTS)
 class OrchestratorInventory(RESTController):
 
+    @raise_if_no_orchestrator
     def list(self, hostname=None):
         orch = OrchClient.instance()
-        result = []
-
-        if orch.available():
-            hosts = [hostname] if hostname else None
-            inventory_nodes = orch.inventory.list(hosts)
-            result = [node.to_json() for node in inventory_nodes]
-        return result
+        hosts = [hostname] if hostname else None
+        inventory_nodes = orch.inventory.list(hosts)
+        return [node.to_json() for node in inventory_nodes]
 
 
 @ApiController('/orchestrator/service', Scope.HOSTS)
 class OrchestratorService(RESTController):
-    def list(self, service_type=None, service_id=None, hostname=None):
-        orch = OrchClient.instance()
-        services = []
 
-        if orch.available():
-            services = [service.to_json() for service in orch.services.list(
-                service_type, service_id, hostname)]
-        return services
+    @raise_if_no_orchestrator
+    def list(self, hostname=None):
+        orch = OrchClient.instance()
+        return [service.to_json() for service in orch.services.list(None, None, hostname)]
diff --git a/src/pybind/mgr/dashboard/tests/test_host.py b/src/pybind/mgr/dashboard/tests/test_host.py
new file mode 100644 (file)
index 0000000..23bd753
--- /dev/null
@@ -0,0 +1,96 @@
+import unittest
+
+try:
+    import mock
+except ImportError:
+    from unittest import mock
+
+from orchestrator import InventoryNode
+
+from . import ControllerTestCase
+from ..controllers.host import get_hosts, Host
+from .. import mgr
+
+
+class HostControllerTest(ControllerTestCase):
+    URL_HOST = '/api/host'
+
+    @classmethod
+    def setup_server(cls):
+        # pylint: disable=protected-access
+        Host._cp_config['tools.authenticate.on'] = False
+        cls.setup_controllers([Host])
+
+    @mock.patch('dashboard.controllers.host.get_hosts')
+    def test_host_list(self, mock_get_hosts):
+        hosts = [
+            {
+                'hostname': 'host-0',
+                'sources': {
+                    'ceph': True, 'orchestrator': False
+                }
+            },
+            {
+                'hostname': 'host-1',
+                'sources': {
+                    'ceph': False, 'orchestrator': True
+                }
+            },
+            {
+                'hostname': 'host-2',
+                'sources': {
+                    'ceph': True, 'orchestrator': True
+                }
+            }
+        ]
+
+        def _get_hosts(from_ceph=True, from_orchestrator=True):
+            _hosts = []
+            if from_ceph:
+                _hosts.append(hosts[0])
+            if from_orchestrator:
+                _hosts.append(hosts[1])
+                _hosts.append(hosts[2])
+            return _hosts
+        mock_get_hosts.side_effect = _get_hosts
+
+        self._get(self.URL_HOST)
+        self.assertStatus(200)
+        self.assertJsonBody(hosts)
+
+        self._get('{}?sources=ceph'.format(self.URL_HOST))
+        self.assertStatus(200)
+        self.assertJsonBody([hosts[0]])
+
+        self._get('{}?sources=orchestrator'.format(self.URL_HOST))
+        self.assertStatus(200)
+        self.assertJsonBody(hosts[1:])
+
+        self._get('{}?sources=ceph,orchestrator'.format(self.URL_HOST))
+        self.assertStatus(200)
+        self.assertJsonBody(hosts)
+
+
+class TestHosts(unittest.TestCase):
+
+    @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
+    def test_get_hosts(self, instance):
+        mgr.list_servers.return_value = [{'hostname': 'node1'}, {'hostname': 'localhost'}]
+
+        fake_client = mock.Mock()
+        fake_client.available.return_value = True
+        fake_client.hosts.list.return_value = [
+            InventoryNode('node1', []), InventoryNode('node2', [])]
+        instance.return_value = fake_client
+
+        hosts = get_hosts()
+        self.assertEqual(len(hosts), 3)
+        check_sources = {
+            'localhost': {'ceph': True, 'orchestrator': False},
+            'node1': {'ceph': True, 'orchestrator': True},
+            'node2': {'ceph': False, 'orchestrator': True}
+        }
+        for host in hosts:
+            hostname = host['hostname']
+            sources = host['sources']
+            self.assertDictEqual(sources, check_sources[hostname])
diff --git a/src/pybind/mgr/dashboard/tests/test_orchestrator.py b/src/pybind/mgr/dashboard/tests/test_orchestrator.py
new file mode 100644 (file)
index 0000000..fb46003
--- /dev/null
@@ -0,0 +1,127 @@
+try:
+    import mock
+except ImportError:
+    from unittest import mock
+
+from orchestrator import InventoryNode, ServiceDescription
+
+from . import ControllerTestCase
+from ..controllers.orchestrator import Orchestrator
+from ..controllers.orchestrator import OrchestratorInventory
+from ..controllers.orchestrator import OrchestratorService
+
+
+class OrchestratorControllerTest(ControllerTestCase):
+    URL_STATUS = '/api/orchestrator/status'
+    URL_INVENTORY = '/api/orchestrator/inventory'
+    URL_SERVICE = '/api/orchestrator/service'
+
+    @classmethod
+    def setup_server(cls):
+        # pylint: disable=protected-access
+        Orchestrator._cp_config['tools.authenticate.on'] = False
+        OrchestratorInventory._cp_config['tools.authenticate.on'] = False
+        OrchestratorService._cp_config['tools.authenticate.on'] = False
+        cls.setup_controllers([Orchestrator, OrchestratorInventory, OrchestratorService])
+
+    @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
+    def test_status_get(self, instance):
+        status = {'available': False, 'description': ''}
+
+        fake_client = mock.Mock()
+        fake_client.status.return_value = status
+        instance.return_value = fake_client
+
+        self._get(self.URL_STATUS)
+        self.assertStatus(200)
+        self.assertJsonBody(status)
+
+    def _set_inventory(self, mock_instance, inventory):
+        # pylint: disable=unused-argument
+        def _list_inventory(hosts=None, refresh=False):
+            nodes = []
+            for node in inventory:
+                if hosts is None or node['name'] in hosts:
+                    nodes.append(InventoryNode(node['name'], node['devices']))
+            return nodes
+        mock_instance.inventory.list.side_effect = _list_inventory
+
+    @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
+    def test_inventory_list(self, instance):
+        inventory = [dict(name='host-{}'.format(i), devices=[]) for i in range(3)]
+
+        fake_client = mock.Mock()
+        fake_client.available.return_value = True
+        self._set_inventory(fake_client, inventory)
+        instance.return_value = fake_client
+
+        # list
+        self._get(self.URL_INVENTORY)
+        self.assertStatus(200)
+        self.assertJsonBody(inventory)
+
+        # list with existent hostname
+        self._get('{}?hostname=host-0'.format(self.URL_INVENTORY))
+        self.assertStatus(200)
+        self.assertJsonBody([inventory[0]])
+
+        # list with non-existent inventory
+        self._get('{}?hostname=host-10'.format(self.URL_INVENTORY))
+        self.assertStatus(200)
+        self.assertJsonBody([])
+
+        # list without orchestrator service
+        fake_client.available.return_value = False
+        self._get(self.URL_INVENTORY)
+        self.assertStatus(503)
+
+    def _set_services(self, mock_instance, services):
+        # pylint: disable=unused-argument
+        def _list_services(service_type=None, service_id=None, node_name=None):
+            service_descs = []
+            for service in services:
+                if node_name is None or service['nodename'] == node_name:
+                    desc = ServiceDescription(nodename=service['nodename'],
+                                              service_type=service['service_type'],
+                                              service_instance=service['service_instance'])
+                    service_descs.append(desc)
+            return service_descs
+        mock_instance.services.list.side_effect = _list_services
+
+    @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
+    def test_service_list(self, instance):
+        services = []
+        for i in range(3):
+            for service_type in ['mon', 'mgr', 'osd']:
+                services.append(
+                    {
+                        'nodename': 'host-{}'.format(i),
+                        'service_type': service_type,
+                        'service_instance': 'x'
+                    }
+                )
+
+        fake_client = mock.Mock()
+        fake_client.available.return_value = True
+        self._set_services(fake_client, services)
+        instance.return_value = fake_client
+
+        # list
+        self._get(self.URL_SERVICE)
+        self.assertStatus(200)
+        self.assertJsonBody(services)
+
+        # list with existent service
+        self._get('{}?hostname=host-0'.format(self.URL_SERVICE))
+        self.assertStatus(200)
+        self.assertJsonBody([svc for svc in services if svc['nodename'] == 'host-0'])
+
+        # list with non-existent service
+        self._get('{}?hostname=host-10'.format(self.URL_SERVICE))
+        self.assertStatus(200)
+        self.assertJsonBody([])
+
+        # list without orchestrator service
+        fake_client.available.return_value = False
+        self._get(self.URL_SERVICE)
+        self.assertStatus(503)