mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Add Etcd v3 protocol support via api gRPC-gateway (#1162)
The only python-etcd3 client working directly via gRPC still supports only a single endpoint, which is not very nice for high-availability. Since Patroni is already using a heavily hacked version of python-etcd with smart retries and auto-discovery out-of-the-box, I decided to enhance the existing code with limited support of v3 protocol via gRPC-gateway. Unfortunately, watches via gRPC-gateway requires us to open and keep the second connection to the etcd. Known limitations: * The very minimal supported version is 3.0.4. On earlier versions transactions don't work due to bugs in grpc-gateway. Without transactions we can't do atomic operations, i.e. leader locks. * Watches work only starting from 3.1.0 * Authentication works only starting from 3.3.0 * gRPC-gateway does not support authentication using TLS Common Name. This is because gRPC-proxy terminates TLS from its client so all the clients share a cert of the proxy: https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/authentication.md#using-tls-common-name
This commit is contained in:
@@ -169,6 +169,7 @@ class PostgresInit(unittest.TestCase):
|
||||
'stats_temp_directory': '/tmp'}
|
||||
|
||||
@patch('psycopg2.connect', psycopg2_connect)
|
||||
@patch('patroni.postgresql.CallbackExecutor', Mock())
|
||||
@patch.object(ConfigHandler, 'write_postgresql_conf', Mock())
|
||||
@patch.object(ConfigHandler, 'replace_pg_hba', Mock())
|
||||
@patch.object(ConfigHandler, 'replace_pg_ident', Mock())
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ from mock import patch, Mock
|
||||
from patroni.ctl import ctl, store_config, load_config, output_members, get_dcs, parse_dcs, \
|
||||
get_all_members, get_any_member, get_cursor, query_member, configure, PatroniCtlException, apply_config_changes, \
|
||||
format_config_for_editing, show_diff, invoke_editor, format_pg_version, find_executable, CONFIG_FILE_PATH
|
||||
from patroni.dcs.etcd import Client, Failover
|
||||
from patroni.dcs.etcd import AbstractEtcdClientWithFailover, Failover
|
||||
from patroni.utils import tzutc
|
||||
from psycopg2 import OperationalError
|
||||
from urllib3 import PoolManager
|
||||
@@ -37,7 +37,7 @@ class TestCtl(unittest.TestCase):
|
||||
|
||||
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
||||
def setUp(self):
|
||||
with patch.object(Client, 'machines') as mock_machines:
|
||||
with patch.object(AbstractEtcdClientWithFailover, 'machines') as mock_machines:
|
||||
mock_machines.__get__ = Mock(return_value=['http://remotehost:2379'])
|
||||
self.runner = CliRunner()
|
||||
self.e = get_dcs({'etcd': {'ttl': 30, 'host': 'ok:2379', 'retry_timeout': 10}}, 'foo')
|
||||
|
||||
+21
-18
@@ -5,7 +5,7 @@ import unittest
|
||||
|
||||
from dns.exception import DNSException
|
||||
from mock import Mock, patch
|
||||
from patroni.dcs.etcd import AbstractDCS, Client, Cluster, Etcd, EtcdError, DnsCachingResolver
|
||||
from patroni.dcs.etcd import AbstractDCS, EtcdClient, Cluster, Etcd, EtcdError, DnsCachingResolver
|
||||
from patroni.exceptions import DCSError
|
||||
from patroni.utils import Retry
|
||||
from urllib3.exceptions import ReadTimeoutError
|
||||
@@ -123,9 +123,9 @@ class TestClient(unittest.TestCase):
|
||||
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
||||
@patch('patroni.dcs.etcd.requests_get', requests_get)
|
||||
def setUp(self):
|
||||
with patch.object(Client, 'machines') as mock_machines:
|
||||
with patch.object(EtcdClient, 'machines') as mock_machines:
|
||||
mock_machines.__get__ = Mock(return_value=['http://localhost:2379', 'http://localhost:4001'])
|
||||
self.client = Client({'srv': 'test', 'retry_timeout': 3}, DnsCachingResolver())
|
||||
self.client = EtcdClient({'srv': 'test', 'retry_timeout': 3}, DnsCachingResolver())
|
||||
self.client.http.request = http_request
|
||||
self.client.http.request_encode_body = http_request
|
||||
|
||||
@@ -143,10 +143,9 @@ class TestClient(unittest.TestCase):
|
||||
except Exception:
|
||||
self.assertIsNone(machines)
|
||||
|
||||
@patch.object(Client, 'machines')
|
||||
@patch.object(EtcdClient, 'machines')
|
||||
def test_api_execute(self, mock_machines):
|
||||
mock_machines.__get__ = Mock(return_value=['http://localhost:4001', 'http://localhost:2379'])
|
||||
self.assertRaises(ValueError, self.client.api_execute, '', '')
|
||||
self.client._base_uri = 'http://localhost:4001'
|
||||
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', 'POST', timeout=0)
|
||||
self.client._base_uri = 'http://localhost:4001'
|
||||
@@ -157,18 +156,17 @@ class TestClient(unittest.TestCase):
|
||||
self.client._machines_cache = [self.client._base_uri]
|
||||
self.assertRaises(etcd.EtcdWatchTimedOut, self.client.api_execute, '/timeout', 'POST', params={'wait': 'true'})
|
||||
self.assertRaises(etcd.EtcdWatchTimedOut, self.client.api_execute, '/timeout', 'POST', params={'wait': 'true'})
|
||||
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', '')
|
||||
|
||||
with patch.object(Client, '_calculate_timeouts', Mock(side_effect=[(1, 1, 0), (1, 1, 0), (0, 1, 0)])),\
|
||||
patch.object(Client, '_load_machines_cache', Mock(side_effect=Exception)):
|
||||
with patch.object(EtcdClient, '_calculate_timeouts', Mock(side_effect=[(1, 1, 0), (1, 1, 0), (0, 1, 0)])),\
|
||||
patch.object(EtcdClient, '_load_machines_cache', Mock(side_effect=Exception)):
|
||||
self.client.http.request = Mock(side_effect=socket.error)
|
||||
self.assertRaises(etcd.EtcdException, rtry, self.client.api_execute, '/', 'GET', params={'retry': rtry})
|
||||
|
||||
with patch.object(Client, '_calculate_timeouts', Mock(side_effect=[(1, 1, 0), (1, 1, 0), (0, 1, 0)])),\
|
||||
patch.object(Client, '_load_machines_cache', Mock(return_value=True)):
|
||||
with patch.object(EtcdClient, '_calculate_timeouts', Mock(side_effect=[(1, 1, 0), (1, 1, 0), (0, 1, 0)])),\
|
||||
patch.object(EtcdClient, '_load_machines_cache', Mock(return_value=True)):
|
||||
self.assertRaises(etcd.EtcdException, rtry, self.client.api_execute, '/', 'GET', params={'retry': rtry})
|
||||
|
||||
with patch.object(Client, '_do_http_request', Mock(side_effect=etcd.EtcdException)):
|
||||
with patch.object(EtcdClient, '_do_http_request', Mock(side_effect=etcd.EtcdException)):
|
||||
self.client._read_timeout = 0.01
|
||||
self.assertRaises(etcd.EtcdException, self.client.api_execute, '/', 'GET')
|
||||
|
||||
@@ -184,7 +182,7 @@ class TestClient(unittest.TestCase):
|
||||
def test__get_machines_cache_from_dns(self):
|
||||
self.client._get_machines_cache_from_dns('error', 2379)
|
||||
|
||||
@patch.object(Client, 'machines')
|
||||
@patch.object(EtcdClient, 'machines')
|
||||
def test__refresh_machines_cache(self, mock_machines):
|
||||
mock_machines.__get__ = Mock(side_effect=etcd.EtcdConnectionFailed)
|
||||
self.assertIsNone(self.client._refresh_machines_cache())
|
||||
@@ -205,6 +203,10 @@ class TestClient(unittest.TestCase):
|
||||
timeout=1, source_address=('localhost', 53333),
|
||||
socket_options=[(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)])
|
||||
|
||||
def test___del__(self):
|
||||
self.client.http.clear = Mock(side_effect=TypeError)
|
||||
del self.client
|
||||
|
||||
|
||||
@patch('patroni.dcs.etcd.requests_get', requests_get)
|
||||
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
||||
@@ -215,7 +217,7 @@ class TestEtcd(unittest.TestCase):
|
||||
|
||||
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
||||
def setUp(self):
|
||||
with patch.object(Client, 'machines') as mock_machines:
|
||||
with patch.object(EtcdClient, 'machines') as mock_machines:
|
||||
mock_machines.__get__ = Mock(return_value=['http://localhost:2379', 'http://localhost:4001'])
|
||||
self.etcd = Etcd({'namespace': '/patroni/', 'ttl': 30, 'retry_timeout': 10,
|
||||
'host': 'localhost:2379', 'scope': 'test', 'name': 'foo'})
|
||||
@@ -226,17 +228,18 @@ class TestEtcd(unittest.TestCase):
|
||||
@patch('dns.resolver.query', dns_query)
|
||||
def test_get_etcd_client(self):
|
||||
with patch('time.sleep', Mock(side_effect=SleepException)),\
|
||||
patch.object(Client, 'machines') as mock_machines:
|
||||
patch.object(EtcdClient, 'machines') as mock_machines:
|
||||
mock_machines.__get__ = Mock(side_effect=etcd.EtcdException)
|
||||
self.assertRaises(SleepException, self.etcd.get_etcd_client,
|
||||
{'discovery_srv': 'test', 'retry_timeout': 10, 'cacert': '1', 'key': '1', 'cert': 1})
|
||||
{'discovery_srv': 'test', 'retry_timeout': 10, 'cacert': '1', 'key': '1', 'cert': 1},
|
||||
EtcdClient)
|
||||
self.assertRaises(SleepException, self.etcd.get_etcd_client,
|
||||
{'url': 'https://test:2379', 'retry_timeout': 10})
|
||||
{'url': 'https://test:2379', 'retry_timeout': 10}, EtcdClient)
|
||||
self.assertRaises(SleepException, self.etcd.get_etcd_client,
|
||||
{'hosts': 'foo:4001,bar', 'retry_timeout': 10})
|
||||
{'hosts': 'foo:4001,bar', 'retry_timeout': 10}, EtcdClient)
|
||||
mock_machines.__get__ = Mock(return_value=[])
|
||||
self.assertRaises(SleepException, self.etcd.get_etcd_client,
|
||||
{'proxy': 'https://user:password@test:2379', 'retry_timeout': 10})
|
||||
{'proxy': 'https://user:password@test:2379', 'retry_timeout': 10}, EtcdClient)
|
||||
|
||||
def test_get_cluster(self):
|
||||
cluster = self.etcd.get_cluster()
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import etcd
|
||||
import json
|
||||
import unittest
|
||||
import urllib3
|
||||
|
||||
from mock import Mock, patch
|
||||
from patroni.dcs.etcd3 import PatroniEtcd3Client, Cluster, Etcd3, Etcd3Error, Etcd3ClientError, RetryFailedError,\
|
||||
InvalidAuthToken, Unavailable, Unknown, UnsupportedEtcdVersion, UserEmpty, base64_encode
|
||||
from threading import Thread
|
||||
|
||||
from . import SleepException, MockResponse
|
||||
|
||||
|
||||
def mock_urlopen(self, method, url, **kwargs):
|
||||
ret = MockResponse()
|
||||
if method == 'GET' and url.endswith('/version'):
|
||||
ret.content = '{"etcdserver": "3.3.13", "etcdcluster": "3.3.0"}'
|
||||
elif method != 'POST':
|
||||
raise Exception('Unexpected request method: {0} {1} {2}'.format(method, url, kwargs))
|
||||
elif url.endswith('/cluster/member/list'):
|
||||
ret.content = '{"members":[{"clientURLs":["http://localhost:2379", "http://localhost:4001"]}]}'
|
||||
elif url.endswith('/auth/authenticate'):
|
||||
ret.content = '{"token":"authtoken"}'
|
||||
elif url.endswith('/lease/grant'):
|
||||
ret.content = '{"ID": "123"}'
|
||||
elif url.endswith('/lease/keepalive'):
|
||||
ret.content = '{"result":{"TTL":30}}'
|
||||
elif url.endswith('/kv/range'):
|
||||
ret.content = json.dumps({
|
||||
"header": {"revision": "1"},
|
||||
"kvs": [
|
||||
{"key": base64_encode('/patroni/test/leader'),
|
||||
"value": base64_encode('foo'), "lease": "bla", "mod_revision": '1'},
|
||||
{"key": base64_encode('/patroni/test/members/foo'),
|
||||
"value": base64_encode('{}'), "lease": "123", "mod_revision": '1'},
|
||||
{"key": base64_encode('/patroni/test/failover'), "value": base64_encode('{}'), "mod_revision": '1'}
|
||||
]
|
||||
})
|
||||
elif url.endswith('/watch'):
|
||||
key = base64_encode('/patroni/test/config')
|
||||
ret.read_chunked = Mock(return_value=[json.dumps({
|
||||
'result': {'events': [
|
||||
{'kv': {'key': key, 'value': base64_encode('bar'), 'mod_revision': '2'}},
|
||||
{'kv': {'key': key, 'value': base64_encode('buzz'), 'mod_revision': '3'}},
|
||||
{'type': 'DELETE', 'kv': {'key': key, 'mod_revision': '4'}},
|
||||
{'kv': {'key': base64_encode('/patroni/test/optime/leader'),
|
||||
'value': base64_encode('1234567'), 'mod_revision': '5'}},
|
||||
]}
|
||||
})[:-1].encode('utf-8'), b'}{"error":{"grpc_code":14,"message":"","http_code":503}}'])
|
||||
elif url.endswith('/kv/put') or url.endswith('/kv/txn'):
|
||||
ret.status_code = 400
|
||||
ret.content = '{"code":5,"error":"etcdserver: requested lease not found"}'
|
||||
elif not url.endswith('/kv/deleterange'):
|
||||
raise Exception('Unexpected url: {0} {1} {2}'.format(method, url, kwargs))
|
||||
return ret
|
||||
|
||||
|
||||
class BaseTestEtcd3(unittest.TestCase):
|
||||
|
||||
@patch.object(Thread, 'start', Mock())
|
||||
@patch.object(urllib3.PoolManager, 'urlopen', mock_urlopen)
|
||||
def setUp(self):
|
||||
self.etcd3 = Etcd3({'namespace': '/patroni/', 'ttl': 30, 'retry_timeout': 10,
|
||||
'host': 'localhost:2378', 'scope': 'test', 'name': 'foo',
|
||||
'username': 'etcduser', 'password': 'etcdpassword'})
|
||||
self.client = self.etcd3._client
|
||||
self.kv_cache = self.client._kv_cache
|
||||
|
||||
|
||||
class TestKVCache(BaseTestEtcd3):
|
||||
|
||||
def test__do_watch(self):
|
||||
self.client.watchprefix = Mock(return_value=False)
|
||||
self.assertRaises(AttributeError, self.kv_cache._do_watch, '1')
|
||||
|
||||
@patch('time.sleep', Mock(side_effect=SleepException))
|
||||
@patch('patroni.dcs.etcd3.KVCache._build_cache', Mock(side_effect=Exception))
|
||||
def test_run(self):
|
||||
self.assertRaises(SleepException, self.kv_cache.run)
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen', mock_urlopen)
|
||||
def test_kill_stream(self):
|
||||
self.assertRaises(Unavailable, self.kv_cache._do_watch, '1')
|
||||
self.kv_cache.kill_stream()
|
||||
with patch.object(MockResponse, 'connection', create=True) as mock_conn:
|
||||
self.kv_cache.kill_stream()
|
||||
mock_conn.sock.close.side_effect = Exception
|
||||
self.kv_cache.kill_stream()
|
||||
|
||||
|
||||
class TestPatroniEtcd3Client(BaseTestEtcd3):
|
||||
|
||||
@patch('patroni.dcs.etcd3.Etcd3Client.authenticate', Mock(side_effect=Exception))
|
||||
def test__init__(self):
|
||||
self.assertRaises(SystemExit, self.setUp)
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen')
|
||||
def test_call_rpc(self, mock_urlopen):
|
||||
request = {'key': base64_encode('/patroni/test/leader')}
|
||||
mock_urlopen.return_value = MockResponse()
|
||||
mock_urlopen.return_value.content = '{"succeeded":true,"header":{"revision":"1"}}'
|
||||
self.client.call_rpc('/kv/txn', {'success': [{'request_delete_range': request}]})
|
||||
self.client.call_rpc('/kv/put', request)
|
||||
self.client.call_rpc('/kv/deleterange', request)
|
||||
|
||||
@patch('time.time', Mock(side_effect=[1, 10.9, 100]))
|
||||
def test__wait_cache(self):
|
||||
with self.kv_cache.condition:
|
||||
self.assertRaises(RetryFailedError, self.client._wait_cache, 10)
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen')
|
||||
def test__restart_watcher(self, mock_urlopen):
|
||||
mock_urlopen.return_value = MockResponse()
|
||||
mock_urlopen.return_value.status_code = 400
|
||||
mock_urlopen.return_value.content = '{"code":9,"error":"etcdserver: authentication is not enabled"}'
|
||||
self.client.authenticate()
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen')
|
||||
def test__handle_auth_errors(self, mock_urlopen):
|
||||
mock_urlopen.return_value = MockResponse()
|
||||
mock_urlopen.return_value.content = '{"code":3,"error":"etcdserver: user name is empty"}'
|
||||
mock_urlopen.return_value.status_code = 403
|
||||
self.client._cluster_version = (3, 1, 5)
|
||||
self.assertRaises(UnsupportedEtcdVersion, self.client.deleteprefix, 'foo')
|
||||
self.client._cluster_version = (3, 3, 13)
|
||||
self.assertRaises(UserEmpty, self.client.deleteprefix, 'foo')
|
||||
mock_urlopen.return_value.content = '{"code":16,"error":"etcdserver: invalid auth token"}'
|
||||
self.assertRaises(InvalidAuthToken, self.client.deleteprefix, 'foo')
|
||||
with patch.object(PatroniEtcd3Client, 'authenticate', Mock(return_value=True)):
|
||||
self.assertRaises(InvalidAuthToken, self.client.deleteprefix, 'foo')
|
||||
self.client.username = None
|
||||
self.assertRaises(InvalidAuthToken, self.client.deleteprefix, 'foo')
|
||||
|
||||
def test__handle_server_response(self):
|
||||
response = MockResponse()
|
||||
response.content = '{"code":0,"error":"'
|
||||
self.assertRaises(etcd.EtcdException, self.client._handle_server_response, response)
|
||||
response.status_code = 400
|
||||
self.assertRaises(Unknown, self.client._handle_server_response, response)
|
||||
response.content = '{"error":{"grpc_code":0,"message":"","http_code":400}}'
|
||||
try:
|
||||
self.client._handle_server_response(response)
|
||||
except Unknown as e:
|
||||
self.assertEqual(e.as_dict(), {'code': 2, 'codeText': 'OK', 'error': u'', 'status': 400})
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen')
|
||||
def test__ensure_version_prefix(self, mock_urlopen):
|
||||
self.client.version_prefix = None
|
||||
mock_urlopen.return_value = MockResponse()
|
||||
mock_urlopen.return_value.content = '{"etcdserver": "3.0.3", "etcdcluster": "3.0.0"}'
|
||||
self.assertRaises(UnsupportedEtcdVersion, self.client._ensure_version_prefix, '')
|
||||
mock_urlopen.return_value.content = '{"etcdserver": "3.0.4", "etcdcluster": "3.0.0"}'
|
||||
self.client._ensure_version_prefix('')
|
||||
self.assertEqual(self.client.version_prefix, '/v3alpha')
|
||||
mock_urlopen.return_value.content = '{"etcdserver": "3.4.4", "etcdcluster": "3.4.0"}'
|
||||
self.client._ensure_version_prefix('')
|
||||
self.assertEqual(self.client.version_prefix, '/v3')
|
||||
|
||||
|
||||
@patch.object(urllib3.PoolManager, 'urlopen', mock_urlopen)
|
||||
class TestEtcd3(BaseTestEtcd3):
|
||||
|
||||
@patch.object(Thread, 'start', Mock())
|
||||
@patch.object(urllib3.PoolManager, 'urlopen', mock_urlopen)
|
||||
def setUp(self):
|
||||
super(TestEtcd3, self).setUp()
|
||||
self.assertRaises(AttributeError, self.kv_cache._build_cache)
|
||||
self.kv_cache._is_ready = True
|
||||
self.etcd3.get_cluster()
|
||||
|
||||
def test_get_cluster(self):
|
||||
self.assertIsInstance(self.etcd3.get_cluster(), Cluster)
|
||||
self.client._kv_cache = None
|
||||
with patch.object(urllib3.PoolManager, 'urlopen') as mock_urlopen:
|
||||
mock_urlopen.side_effect = UnsupportedEtcdVersion('')
|
||||
self.assertRaises(UnsupportedEtcdVersion, self.etcd3.get_cluster)
|
||||
mock_urlopen.side_effect = SleepException()
|
||||
self.assertRaises(Etcd3Error, self.etcd3.get_cluster)
|
||||
|
||||
def test_touch_member(self):
|
||||
self.etcd3.touch_member({})
|
||||
self.etcd3._lease = 'bla'
|
||||
self.etcd3.touch_member({})
|
||||
with patch.object(PatroniEtcd3Client, 'lease_grant', Mock(side_effect=Etcd3ClientError)):
|
||||
self.etcd3.touch_member({})
|
||||
|
||||
def test__update_leader(self):
|
||||
self.etcd3._lease = None
|
||||
self.etcd3.update_leader('123')
|
||||
self.etcd3.update_leader('124')
|
||||
|
||||
def test_attempt_to_acquire_leader(self):
|
||||
self.etcd3._lease = None
|
||||
self.assertFalse(self.etcd3.attempt_to_acquire_leader())
|
||||
|
||||
def test_set_ttl(self):
|
||||
self.etcd3.set_ttl(20)
|
||||
|
||||
@patch.object(PatroniEtcd3Client, 'lease_keepalive', Mock(return_value=False))
|
||||
def test_refresh_lease(self):
|
||||
self.etcd3._last_lease_refresh = 0
|
||||
self.etcd3.refresh_lease()
|
||||
|
||||
@patch('time.sleep', Mock(side_effect=SleepException))
|
||||
@patch.object(PatroniEtcd3Client, 'lease_keepalive', Mock(return_value=False))
|
||||
@patch.object(PatroniEtcd3Client, 'lease_grant', Mock(side_effect=Etcd3ClientError))
|
||||
def test_create_lease(self):
|
||||
self.etcd3._lease = None
|
||||
self.etcd3._last_lease_refresh = 0
|
||||
self.assertRaises(SleepException, self.etcd3.create_lease)
|
||||
|
||||
def test_set_failover_value(self):
|
||||
self.etcd3.set_failover_value('', 1)
|
||||
|
||||
def test_set_config_value(self):
|
||||
self.etcd3.set_config_value('')
|
||||
|
||||
def test_initialize(self):
|
||||
self.etcd3.initialize()
|
||||
|
||||
def test_cancel_initialization(self):
|
||||
self.etcd3.cancel_initialization()
|
||||
|
||||
def test_delete_leader(self):
|
||||
self.etcd3.delete_leader()
|
||||
|
||||
def test_delete_cluster(self):
|
||||
self.etcd3.delete_cluster()
|
||||
|
||||
def test_set_history_value(self):
|
||||
self.etcd3.set_history_value('')
|
||||
|
||||
def test_set_sync_state_value(self):
|
||||
self.etcd3.set_sync_state_value('')
|
||||
|
||||
def test_delete_sync_state(self):
|
||||
self.etcd3.delete_sync_state()
|
||||
|
||||
def test_watch(self):
|
||||
self.etcd3.set_ttl(10)
|
||||
self.etcd3.watch(None, 0)
|
||||
self.etcd3.watch(None, 0)
|
||||
|
||||
def test_set_socket_options(self):
|
||||
with patch('socket.SIO_KEEPALIVE_VALS', 1, create=True):
|
||||
self.etcd3.set_socket_options(Mock(), None)
|
||||
+2
-2
@@ -6,7 +6,7 @@ import sys
|
||||
from mock import Mock, MagicMock, PropertyMock, patch, mock_open
|
||||
from patroni.config import Config
|
||||
from patroni.dcs import Cluster, ClusterConfig, Failover, Leader, Member, get_dcs, SyncState, TimelineHistory
|
||||
from patroni.dcs.etcd import Client
|
||||
from patroni.dcs.etcd import AbstractEtcdClientWithFailover
|
||||
from patroni.exceptions import DCSError, PostgresConnectionException, PatroniFatalException
|
||||
from patroni.ha import Ha, _MemberStatus
|
||||
from patroni.postgresql import Postgresql
|
||||
@@ -185,7 +185,7 @@ class TestHa(PostgresInit):
|
||||
@patch.object(etcd.Client, 'read', etcd_read)
|
||||
def setUp(self):
|
||||
super(TestHa, self).setUp()
|
||||
with patch.object(Client, 'machines') as mock_machines:
|
||||
with patch.object(AbstractEtcdClientWithFailover, 'machines') as mock_machines:
|
||||
mock_machines.__get__ = Mock(return_value=['http://remotehost:2379'])
|
||||
self.p.set_state('running')
|
||||
self.p.set_role('replica')
|
||||
|
||||
@@ -9,7 +9,7 @@ import patroni.config as config
|
||||
from mock import Mock, PropertyMock, patch
|
||||
from patroni.api import RestApiServer
|
||||
from patroni.async_executor import AsyncExecutor
|
||||
from patroni.dcs.etcd import Client
|
||||
from patroni.dcs.etcd import AbstractEtcdClientWithFailover
|
||||
from patroni.exceptions import DCSError
|
||||
from patroni.postgresql import Postgresql
|
||||
from patroni.postgresql.config import ConfigHandler
|
||||
@@ -53,7 +53,7 @@ class TestPatroni(unittest.TestCase):
|
||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
||||
@patch.object(etcd.Client, 'read', etcd_read)
|
||||
@patch.object(Thread, 'start', Mock())
|
||||
@patch.object(Client, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
||||
@patch.object(AbstractEtcdClientWithFailover, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
||||
def setUp(self):
|
||||
self._handlers = logging.getLogger().handlers[:]
|
||||
RestApiServer._BaseServer__is_shut_down = Mock()
|
||||
@@ -75,7 +75,7 @@ class TestPatroni(unittest.TestCase):
|
||||
@patch('sys.argv', ['patroni.py', 'postgres0.yml'])
|
||||
@patch('time.sleep', Mock(side_effect=SleepException))
|
||||
@patch.object(etcd.Client, 'delete', Mock())
|
||||
@patch.object(Client, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
||||
@patch.object(AbstractEtcdClientWithFailover, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
||||
@patch.object(Thread, 'join', Mock())
|
||||
def test_patroni_patroni_main(self):
|
||||
with patch('subprocess.call', Mock(return_value=1)):
|
||||
|
||||
@@ -98,7 +98,6 @@ class TestPostgresql(BaseTestPostgresql):
|
||||
def setUp(self):
|
||||
super(TestPostgresql, self).setUp()
|
||||
self.p.config.write_postgresql_conf()
|
||||
self.p._callback_executor = Mock()
|
||||
|
||||
@patch('subprocess.Popen')
|
||||
@patch.object(Postgresql, 'wait_for_startup')
|
||||
|
||||
Reference in New Issue
Block a user