diff --git a/patroni/dcs/__init__.py b/patroni/dcs/__init__.py index ce4020af..df8901a2 100644 --- a/patroni/dcs/__init__.py +++ b/patroni/dcs/__init__.py @@ -3,6 +3,7 @@ import dateutil import importlib import inspect import json +import logging import os import pkgutil import six @@ -14,6 +15,8 @@ from random import randint from six.moves.urllib_parse import urlparse, urlunparse, parse_qsl from threading import Event, Lock +logger = logging.getLogger(__name__) + def parse_connection_string(value): """Original Governor stores connection strings for each cluster members if a following format: @@ -51,18 +54,22 @@ def dcs_modules(): def get_dcs(config): available_implementations = set() for module_name in dcs_modules(): - module = importlib.import_module(module_name) - for name in filter(lambda name: not name.startswith('__'), dir(module)): # iterate through module content - value = getattr(module, name) - name = name.lower() - # try to find implementation of AbstractDCS interface, class name must match with module_name - if inspect.isclass(value) and issubclass(value, AbstractDCS) and __package__ + '.' + name == module_name: - available_implementations.add(name) - if name in config: # which has configuration section in the config file - # 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 value(config[name]) + 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 + # 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: + if not config.get('patronictl'): + logger.info('Failed to import %s', module_name) raise PatroniException("""Can not find suitable configuration of distributed configuration store Available implementations: """ + ', '.join(available_implementations)) diff --git a/tests/test_ha.py b/tests/test_ha.py index 71d33d25..0a899823 100644 --- a/tests/test_ha.py +++ b/tests/test_ha.py @@ -135,6 +135,7 @@ class TestHa(unittest.TestCase): @patch('socket.getaddrinfo', socket_getaddrinfo) @patch('psycopg2.connect', psycopg2_connect) + @patch('patroni.dcs.dcs_modules', Mock(return_value=['foo', 'patroni.dcs.etcd'])) @patch.object(etcd.Client, 'read', etcd_read) def setUp(self): with patch.object(Client, 'machines') as mock_machines: