]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Merge OrchClient with OrchestratorClientMixin
authorSebastian Wagner <sebastian.wagner@suse.com>
Fri, 8 Feb 2019 17:45:22 +0000 (18:45 +0100)
committerSebastian Wagner <sebastian.wagner@suse.com>
Tue, 21 May 2019 13:04:49 +0000 (15:04 +0200)
Fixed missing error propagation from
  the orchestrator to the dashboard

Adapted orchestrator integration in NFS

Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
src/pybind/mgr/dashboard/controllers/iscsi.py
src/pybind/mgr/dashboard/services/ganesha.py
src/pybind/mgr/dashboard/services/iscsi_config.py
src/pybind/mgr/dashboard/services/orchestrator.py
src/pybind/mgr/dashboard/tests/test_ganesha.py
src/pybind/mgr/dashboard/tests/test_iscsi.py
src/pybind/mgr/dashboard/tools.py
src/pybind/mgr/orchestrator.py

index da25f4c70a6d9717b304306cf982666c15e49e08..9f0e9829014520964722a6ea9a4402c9645f96e9 100644 (file)
@@ -36,7 +36,7 @@ class IscsiUi(BaseController):
             status['message'] = 'There are no gateways defined'
             return status
         try:
-            for gateway in gateways.keys():
+            for gateway in gateways:
                 try:
                     IscsiClient.instance(gateway_name=gateway).ping()
                 except RequestException:
@@ -67,7 +67,7 @@ class IscsiUi(BaseController):
     def portals(self):
         portals = []
         gateways_config = IscsiGatewaysConfig.get_gateways_config()
-        for name in gateways_config['gateways'].keys():
+        for name in gateways_config['gateways']:
             ip_addresses = IscsiClient.instance(gateway_name=name).get_ip_addresses()
             portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
         return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
index 8a81e5bb35b05a1694d899681098c685a8af1393..4053d20c1ca10c6659db0d29cee250dfcb499bec 100644 (file)
@@ -3,6 +3,7 @@ from __future__ import absolute_import
 
 import re
 
+from orchestrator import OrchestratorError
 from .cephfs import CephFS
 from .cephx import CephX
 from .orchestrator import OrchClient
@@ -65,11 +66,18 @@ class Ganesha(object):
     def get_ganesha_clusters(cls):
         return [cluster_id for cluster_id in cls._get_clusters_locations()]
 
+    @staticmethod
+    def _get_orch_nfs_instances():
+        try:
+            return OrchClient().list_service_info("nfs")
+        except (RuntimeError, OrchestratorError, ImportError):
+            return []
+
     @classmethod
     def get_daemons_status(cls):
-        if not OrchClient.instance().available():
+        instances = cls._get_orch_nfs_instances()
+        if not instances:
             return None
-        instances = OrchClient.instance().list_service_info("nfs")
 
         result = {}
         for instance in instances:
@@ -105,14 +113,13 @@ class Ganesha(object):
 
     @classmethod
     def get_pool_and_namespace(cls, cluster_id):
-        if OrchClient.instance().available():
-            instances = OrchClient.instance().list_service_info("nfs")
-            # we assume that every instance stores there configuration in the
-            # same RADOS pool/namespace
-            if instances:
-                location = instances[0].rados_config_location
-                pool, ns, _ = cls.parse_rados_url(location)
-                return pool, ns
+        instances = cls._get_orch_nfs_instances()
+        # we assume that every instance stores there configuration in the
+        # same RADOS pool/namespace
+        if instances:
+            location = instances[0].rados_config_location
+            pool, ns, _ = cls.parse_rados_url(location)
+            return pool, ns
         locations = cls._get_clusters_locations()
         if cluster_id not in locations:
             raise NFSException("Cluster not found: cluster_id={}"
@@ -122,7 +129,7 @@ class Ganesha(object):
     @classmethod
     def reload_daemons(cls, cluster_id, daemons_id):
         logger.debug("[NFS] issued reload of daemons: %s", daemons_id)
-        if not OrchClient.instance().available():
+        if not OrchClient().available():
             logger.debug("[NFS] orchestrator not available")
             return
         reload_list = []
@@ -135,7 +142,7 @@ class Ganesha(object):
                 continue
             if daemons[cluster_id][daemon_id] == 1:
                 reload_list.append((cluster_id, daemon_id))
-        OrchClient.instance().reload_service("nfs", reload_list)
+        OrchClient().reload_service("nfs", reload_list)
 
     @classmethod
     def fsals_available(cls):
index e4a78f6b626ef6f6eb57061bb1871ab2942ca669..bb6de51a99e822eb3b46e0984d3d8f4f07234364 100644 (file)
@@ -49,7 +49,7 @@ _ISCSI_STORE_KEY = "_iscsi_config"
 class IscsiGatewaysConfig(object):
     @classmethod
     def _load_config(cls):
-        if OrchClient.instance().available():
+        if OrchClient().available():
             raise ManagedByOrchestratorException()
         json_db = mgr.get_store(_ISCSI_STORE_KEY,
                                 '{"gateways": {}}')
@@ -89,7 +89,7 @@ class IscsiGatewaysConfig(object):
             config = cls._load_config()
         except ManagedByOrchestratorException:
             config = {'gateways': {}}
-            instances = OrchClient.instance().list_service_info("iscsi")
+            instances = OrchClient().list_service_info("iscsi")
             for instance in instances:
                 config['gateways'][instance.nodename] = {
                     'service_url': instance.service_url
index 4a19afef9d48e8ee704a76d215d003ea7c48f72b..366ff7de735c531e18661c07618e1469f3840427 100644 (file)
@@ -1,55 +1,38 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
 
-import time
-
+from orchestrator import OrchestratorClientMixin, raise_if_exception, OrchestratorError
 from .. import mgr, logger
 
 
-class NoOrchestratorConfiguredException(Exception):
-    pass
-
-
-class OrchClient(object):
-    _instance = None
-
-    @classmethod
-    def instance(cls):
-        if cls._instance is None:
-            cls._instance = OrchClient()
-        return cls._instance
-
-    def _call(self, method, *args, **kwargs):
-        _backend = mgr.get_module_option_ex("orchestrator_cli", "orchestrator")
-        if not _backend:
-            raise NoOrchestratorConfiguredException()
-        return mgr.remote(_backend, method, *args, **kwargs)
-
-    def _wait(self, completions):
-        while not self._call("wait", completions):
-            if any(c.should_wait for c in completions):
-                time.sleep(5)
-            else:
-                break
+# pylint: disable=abstract-method
+class OrchClient(OrchestratorClientMixin):
+    def __init__(self):
+        super(OrchClient, self).__init__()
+        self.set_mgr(mgr)
 
     def list_service_info(self, service_type):
-        completion = self._call("describe_service", service_type, None, None)
-        self._wait([completion])
+        # type: (str) -> list
+        completion = self.describe_service(service_type, None, None)
+        self._orchestrator_wait([completion])
+        raise_if_exception(completion)
         return completion.result
 
     def available(self):
-        _backend = mgr.get_module_option_ex("orchestrator_cli", "orchestrator")
-        if not _backend:
+        try:
+            status, desc = super(OrchClient, self).available()
+            logger.info("[ORCH] is orchestrator available: %s, %s", status, desc)
+            return status
+        except (RuntimeError, OrchestratorError, ImportError):
             return False
-        status, desc = self._call("available")
-        logger.info("[ORCH] is orchestrator available: %s, %s", status, desc)
-        return status
 
     def reload_service(self, service_type, service_ids):
         if not isinstance(service_ids, list):
             service_ids = [service_ids]
 
-        completion_list = [self._call("service_action", 'reload', service_type,
-                                      service_name, service_id)
+        completion_list = [self.service_action('reload', service_type,
+                                               service_name, service_id)
                            for service_name, service_id in service_ids]
-        self._wait(completion_list)
+        self._orchestrator_wait(completion_list)
+        for c in completion_list:
+            raise_if_exception(c)
index aacbfcf5c2aef7347d508fb611b13b7faf4bb1a0..5dced126424b6fa3e2e07554d4c354848f5add2f 100644 (file)
@@ -5,6 +5,7 @@ import unittest
 
 from mock import MagicMock, Mock
 
+import orchestrator
 from . import KVStoreMockMixin
 from .. import mgr
 from ..settings import Settings
@@ -127,7 +128,9 @@ EXPORT
 
         mgr.rados = MagicMock()
         mgr.rados.open_ioctx.return_value = ioctx_mock
-        mgr.remote.return_value = False, None
+
+        # pylint: disable=protected-access
+        mgr._select_orchestrator.side_effect = orchestrator.NoOrchestrator()
 
         ganesha.CephX = MagicMock()
         ganesha.CephX.list_clients.return_value = ['ganesha']
index 949bf629b50bf9d6144ff89dc3508f0a366251f7..e371ef368622acd709260edd2952ff813aa07e3d 100644 (file)
@@ -17,7 +17,7 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
 
     @classmethod
     def setup_server(cls):
-        OrchClient.instance().available = lambda: False
+        OrchClient().available = lambda: False
         mgr.rados.side_effect = None
         # pylint: disable=protected-access
         Iscsi._cp_config['tools.authenticate.on'] = False
index f88092750e73c2a9f97f3b81bb1a1ed005c982f1..defe2d352dca983edc90e19d805b62e14c353a8e 100644 (file)
@@ -670,7 +670,12 @@ def build_url(host, scheme=None, port=None):
     :rtype: str
     """
     try:
-        ipaddress.IPv6Address(six.u(host))
+        try:
+            u_host = six.u(host)
+        except TypeError:
+            u_host = host
+
+        ipaddress.IPv6Address(u_host)
         netloc = '[{}]'.format(host)
     except ValueError:
         netloc = host
index 4a23f1be28d6197791d032ab91379294d05c162e..d0b0f5839ab18005ddb9772da846d6ef6c905a19 100644 (file)
@@ -11,6 +11,7 @@ import uuid
 
 import six
 
+from mgr_module import MgrModule
 from mgr_util import format_bytes
 
 try:
@@ -230,7 +231,7 @@ class Orchestrator(object):
         return True
 
     def available(self):
-        # type: () -> Tuple[Optional[bool], Optional[str]]
+        # type: () -> Tuple[bool, str]
         """
         Report whether we can talk to the orchestrator.  This is the
         place to give the user a meaningful message if the orchestrator
@@ -242,13 +243,16 @@ class Orchestrator(object):
         (e.g. based on a periodic background ping of the orchestrator)
         if that's necessary to make this method fast.
 
-        Do not override this method if you don't have a meaningful
-        status to return: the default None, None return value is used
-        to indicate that a module is unable to indicate its availability.
+        ..note:: `True` doesn't mean that the desired functionality
+            is actually available in the orchestrator. I.e. this
+            won't work as expected::
+
+                >>> if OrchestratorClientMixin().available()[0]:  # wrong.
+                ...     OrchestratorClientMixin().get_hosts()
 
         :return: two-tuple of boolean, string
         """
-        return None, None
+        raise NotImplementedError()
 
     def wait(self, completions):
         """
@@ -305,7 +309,7 @@ class Orchestrator(object):
         raise NotImplementedError()
 
     def describe_service(self, service_type=None, service_id=None, node_name=None):
-        # type: (str, str, str) -> ReadCompletion[List[ServiceDescription]]
+        # type: (Optional[str], Optional[str], Optional[str]) -> ReadCompletion[List[ServiceDescription]]
         """
         Describe a service (of any kind) that is already configured in
         the orchestrator.  For example, when viewing an OSD in the dashboard
@@ -611,7 +615,7 @@ class DriveGroupSpec(object):
     def __init__(self, host_pattern, data_devices=None, db_devices=None, wal_devices=None, journal_devices=None,
                  data_directories=None, osds_per_device=None, objectstore='bluestore', encrypted=False,
                  db_slots=None, wal_slots=None):
-        # type: (str, Optional[DeviceSelection], Optional[DeviceSelection], Optional[DeviceSelection], Optional[DeviceSelection], Optional[List[str]], int, str, bool, int, int) -> ()
+        # type: (str, Optional[DeviceSelection], Optional[DeviceSelection], Optional[DeviceSelection], Optional[DeviceSelection], Optional[List[str]], int, str, bool, int, int) -> None
 
         # concept of applying a drive group to a (set) of hosts is tightly
         # linked to the drive group itself
@@ -873,24 +877,37 @@ class OrchestratorClientMixin(Orchestrator):
     ...        self.log.debug(completion.result)
 
     """
+
+    def set_mgr(self, mgr):
+        # type: (MgrModule) -> None
+        """
+        Useable in the Dashbord that uses a global ``mgr``
+        """
+
+        self.__mgr = mgr  # Make sure we're not overwriting any other `mgr` properties
+
     def _oremote(self, meth, args, kwargs):
         """
         Helper for invoking `remote` on whichever orchestrator is enabled
 
         :raises RuntimeError: If the remote method failed.
-        :raises NoOrchestrator:
+        :raises OrchestratorError: orchestrator failed to perform
         :raises ImportError: no `orchestrator_cli` module or backend not found.
         """
         try:
-            o = self._select_orchestrator()
+            mgr = self.__mgr
+        except AttributeError:
+            mgr = self
+        try:
+            o = mgr._select_orchestrator()
         except AttributeError:
-            o = self.remote('orchestrator_cli', '_select_orchestrator')
+            o = mgr.remote('orchestrator_cli', '_select_orchestrator')
 
         if o is None:
             raise NoOrchestrator()
 
-        self.log.debug("_oremote {} -> {}.{}(*{}, **{})".format(self.module_name, o, meth, args, kwargs))
-        return self.remote(o, meth, *args, **kwargs)
+        mgr.log.debug("_oremote {} -> {}.{}(*{}, **{})".format(mgr.module_name, o, meth, args, kwargs))
+        return mgr.remote(o, meth, *args, **kwargs)
 
     def _update_completion_progress(self, completion, force_progress=None):
         # type: (WriteCompletion, Optional[float]) -> None