diff --git a/patroni/dcs/__init__.py b/patroni/dcs/__init__.py index 384fc413..a0364d7f 100644 --- a/patroni/dcs/__init__.py +++ b/patroni/dcs/__init__.py @@ -73,26 +73,34 @@ def dcs_modules(): def get_dcs(config): - available_implementations = set() - for module_name in dcs_modules(): - try: - module = importlib.import_module(module_name) - for name in filter(lambda name: not name.startswith('__'), dir(module)): # iterate through module content - item = getattr(module, name) - name = name.lower() - # try to find implementation of AbstractDCS interface, class name must match with module_name - if inspect.isclass(item) and issubclass(item, AbstractDCS) and __package__ + '.' + name == module_name: - available_implementations.add(name) - if name in config: # which has configuration section in the config file + modules = dcs_modules() + + for module_name in modules: + name = module_name.split('.')[-1] + if name in config: # we will try to import only modules which have configuration section in the config file + try: + module = importlib.import_module(module_name) + for key, item in module.__dict__.items(): # iterate through the module content + # try to find implementation of AbstractDCS interface, class name must match with module_name + if key.lower() == name and inspect.isclass(item) and issubclass(item, AbstractDCS): # propagate some parameters config[name].update({p: config[p] for p in ('namespace', 'name', 'scope', 'loop_wait', 'patronictl', 'ttl', 'retry_timeout') if p in config}) return item(config[name]) + except ImportError: + logger.debug('Failed to import %s', module_name) + + available_implementations = [] + for module_name in modules: + name = module_name.split('.')[-1] + try: + module = importlib.import_module(module_name) + available_implementations.extend(name for key, item in module.__dict__.items() if key.lower() == name + and inspect.isclass(item) and issubclass(item, AbstractDCS)) except ImportError: - if not config.get('patronictl'): - logger.info('Failed to import %s', module_name) + logger.info('Failed to import %s', module_name) raise PatroniException("""Can not find suitable configuration of distributed configuration store -Available implementations: """ + ', '.join(available_implementations)) +Available implementations: """ + ', '.join(sorted(set(available_implementations)))) class Member(namedtuple('Member', 'index,name,session,data')): diff --git a/tests/test_ctl.py b/tests/test_ctl.py index d269cdcc..10b40433 100644 --- a/tests/test_ctl.py +++ b/tests/test_ctl.py @@ -159,6 +159,7 @@ class TestCtl(unittest.TestCase): result = self.runner.invoke(ctl, ['failover', 'dummy'], input='\n') assert 'Failover could be performed only to a specific candidate' in result.output + @patch('patroni.dcs.dcs_modules', Mock(return_value=['patroni.dcs.dummy', 'patroni.dcs.etcd'])) def test_get_dcs(self): self.assertRaises(PatroniCtlException, get_dcs, {'dummy': {}}, 'dummy') diff --git a/tests/test_ha.py b/tests/test_ha.py index 0772cec6..db0e0b33 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -177,7 +177,7 @@ def run_async(self, func, args=()): class TestHa(PostgresInit): @patch('socket.getaddrinfo', socket_getaddrinfo) - @patch('patroni.dcs.dcs_modules', Mock(return_value=['patroni.dcs.foo', 'patroni.dcs.etcd'])) + @patch('patroni.dcs.dcs_modules', Mock(return_value=['patroni.dcs.etcd'])) @patch.object(etcd.Client, 'read', etcd_read) def setUp(self): super(TestHa, self).setUp()