# -*- 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):
@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)]
--- /dev/null
+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])
--- /dev/null
+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)