mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Fix issues with raft traffic encryption (#1919)
and run raft behave tests with encryption enabled. Using the new `pysyncobj` release allowed us to get rid of a lot of hacks with accessing private properties and methods of the parent class and reduce the size of the `raft.py`. Close https://github.com/zalando/patroni/issues/1746
This commit is contained in:
@@ -22,7 +22,7 @@ def install_requirements(what):
|
|||||||
for r in read('requirements.txt').split('\n'):
|
for r in read('requirements.txt').split('\n'):
|
||||||
r = r.strip()
|
r = r.strip()
|
||||||
if r != '':
|
if r != '':
|
||||||
extras = {e for e, v in EXTRAS_REQUIRE.items() if v and r.startswith(v[0])}
|
extras = {e for e, v in EXTRAS_REQUIRE.items() if v and any(r.startswith(x) for x in v)}
|
||||||
if not extras or what == 'all' or what in extras:
|
if not extras or what == 'all' or what in extras:
|
||||||
requirements.append(r)
|
requirements.append(r)
|
||||||
|
|
||||||
|
|||||||
@@ -591,10 +591,12 @@ class ExhibitorController(ZooKeeperController):
|
|||||||
class RaftController(AbstractDcsController):
|
class RaftController(AbstractDcsController):
|
||||||
|
|
||||||
CONTROLLER_ADDR = 'localhost:1234'
|
CONTROLLER_ADDR = 'localhost:1234'
|
||||||
|
PASSWORD = '12345'
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
super(RaftController, self).__init__(context)
|
super(RaftController, self).__init__(context)
|
||||||
os.environ.update(PATRONI_RAFT_PARTNER_ADDRS="'" + self.CONTROLLER_ADDR + "'", RAFT_PORT='1234')
|
os.environ.update(PATRONI_RAFT_PARTNER_ADDRS="'" + self.CONTROLLER_ADDR + "'",
|
||||||
|
PATRONI_RAFT_PASSWORD=self.PASSWORD, RAFT_PORT='1234')
|
||||||
self._raft = None
|
self._raft = None
|
||||||
|
|
||||||
def _start(self):
|
def _start(self):
|
||||||
@@ -615,18 +617,16 @@ class RaftController(AbstractDcsController):
|
|||||||
|
|
||||||
def cleanup_service_tree(self):
|
def cleanup_service_tree(self):
|
||||||
from patroni.dcs.raft import KVStoreTTL
|
from patroni.dcs.raft import KVStoreTTL
|
||||||
from pysyncobj import SyncObjConf
|
|
||||||
|
|
||||||
if self._raft:
|
if self._raft:
|
||||||
self._raft.destroy()
|
self._raft.destroy()
|
||||||
self._raft._SyncObj__thread.join()
|
|
||||||
self.stop()
|
self.stop()
|
||||||
os.makedirs(self._work_directory)
|
os.makedirs(self._work_directory)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
ready_event = threading.Event()
|
ready_event = threading.Event()
|
||||||
conf = SyncObjConf(appendEntriesUseBatch=False, dynamicMembershipChange=True, onReady=ready_event.set)
|
self._raft = KVStoreTTL(ready_event.set, None, None, partner_addrs=[self.CONTROLLER_ADDR], password=self.PASSWORD)
|
||||||
self._raft = KVStoreTTL(None, [self.CONTROLLER_ADDR], conf)
|
self._raft.startAutoTick()
|
||||||
ready_event.wait()
|
ready_event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+86
-126
@@ -5,7 +5,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from pysyncobj import SyncObj, SyncObjConf, replicated, FAIL_REASON
|
from pysyncobj import SyncObj, SyncObjConf, replicated, FAIL_REASON
|
||||||
from pysyncobj.transport import Node, TCPTransport, CONNECTION_STATE
|
from pysyncobj.transport import TCPTransport, CONNECTION_STATE
|
||||||
|
from pysyncobj.utility import TcpUtility, UtilityException
|
||||||
|
|
||||||
from . import AbstractDCS, ClusterConfig, Cluster, Failover, Leader, Member, SyncState, TimelineHistory
|
from . import AbstractDCS, ClusterConfig, Cluster, Failover, Leader, Member, SyncState, TimelineHistory
|
||||||
from ..utils import validate_directory
|
from ..utils import validate_directory
|
||||||
@@ -13,122 +14,97 @@ from ..utils import validate_directory
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MessageNode(Node):
|
class _TCPTransport(TCPTransport):
|
||||||
|
|
||||||
def __init__(self, address):
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
|
|
||||||
class UtilityTransport(TCPTransport):
|
|
||||||
|
|
||||||
def __init__(self, syncObj, selfNode, otherNodes):
|
def __init__(self, syncObj, selfNode, otherNodes):
|
||||||
super(UtilityTransport, self).__init__(syncObj, selfNode, otherNodes)
|
super(_TCPTransport, self).__init__(syncObj, selfNode, otherNodes)
|
||||||
self._selfIsReadonlyNode = False
|
self.setOnUtilityMessageCallback('members', syncObj.getMembers)
|
||||||
|
|
||||||
def _connectIfNecessarySingle(self, node):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def connectionState(self, node):
|
|
||||||
return self._connections[node].state
|
|
||||||
|
|
||||||
def isDisconnected(self, node):
|
|
||||||
return self.connectionState(node) == CONNECTION_STATE.DISCONNECTED
|
|
||||||
|
|
||||||
def connectIfRequiredSingle(self, node):
|
|
||||||
if self.isDisconnected(node):
|
|
||||||
return self._connections[node].connect(node.ip, node.port)
|
|
||||||
|
|
||||||
def disconnectSingle(self, node):
|
|
||||||
self._connections[node].disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
class SyncObjUtility(SyncObj):
|
class SyncObjUtility(object):
|
||||||
|
|
||||||
def __init__(self, otherNodes, conf):
|
def __init__(self, otherNodes, conf):
|
||||||
autoTick = conf.autoTick
|
self._nodes = otherNodes
|
||||||
conf.autoTick = False
|
self._utility = TcpUtility(conf.password)
|
||||||
super(SyncObjUtility, self).__init__(None, otherNodes, conf, transportClass=UtilityTransport)
|
|
||||||
conf.autoTick = autoTick
|
|
||||||
self._SyncObj__transport.setOnMessageReceivedCallback(self._onMessageReceived)
|
|
||||||
self.__result = None
|
|
||||||
|
|
||||||
def setPartnerNode(self, partner):
|
def executeCommand(self, command):
|
||||||
self.__node = partner
|
try:
|
||||||
|
return self._utility.executeCommand(self.__node, command)
|
||||||
|
except UtilityException:
|
||||||
|
return None
|
||||||
|
|
||||||
def sendMessage(self, message):
|
def getMembers(self):
|
||||||
# Abuse the fact that node address is send as a first message
|
for self.__node in self._nodes:
|
||||||
self._SyncObj__transport._selfNode = MessageNode(message)
|
response = self.executeCommand(['members'])
|
||||||
self._SyncObj__transport.connectIfRequiredSingle(self.__node)
|
if response:
|
||||||
while not self._SyncObj__transport.isDisconnected(self.__node):
|
return [member['addr'] for member in response]
|
||||||
self._poller.poll(0.5)
|
|
||||||
return self.__result
|
|
||||||
|
|
||||||
def _onMessageReceived(self, _, message):
|
|
||||||
self.__result = message
|
|
||||||
self._SyncObj__transport.disconnectSingle(self.__node)
|
|
||||||
|
|
||||||
|
|
||||||
class MyTCPTransport(TCPTransport):
|
|
||||||
|
|
||||||
def _onIncomingMessageReceived(self, conn, message):
|
|
||||||
if self._syncObj.encryptor and not conn.sendRandKey:
|
|
||||||
conn.sendRandKey = message
|
|
||||||
conn.recvRandKey = os.urandom(32)
|
|
||||||
conn.send(conn.recvRandKey)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Utility messages
|
|
||||||
if isinstance(message, list) and message[0] == 'members':
|
|
||||||
conn.send(self._syncObj._get_members())
|
|
||||||
return True
|
|
||||||
|
|
||||||
return super(MyTCPTransport, self)._onIncomingMessageReceived(conn, message)
|
|
||||||
|
|
||||||
|
|
||||||
class DynMemberSyncObj(SyncObj):
|
class DynMemberSyncObj(SyncObj):
|
||||||
|
|
||||||
def __init__(self, selfAddress, partnerAddrs, conf):
|
def __init__(self, selfAddress, partnerAddrs, conf):
|
||||||
add_self = False
|
self.__early_apply_local_log = selfAddress is not None
|
||||||
|
self.applied_local_log = False
|
||||||
|
|
||||||
utility = SyncObjUtility(partnerAddrs, conf)
|
utility = SyncObjUtility(partnerAddrs, conf)
|
||||||
for node in utility._SyncObj__otherNodes:
|
members = utility.getMembers()
|
||||||
utility.setPartnerNode(node)
|
add_self = members and selfAddress not in members
|
||||||
response = utility.sendMessage(['members'])
|
|
||||||
if response:
|
partnerAddrs = [member for member in (members or partnerAddrs) if member != selfAddress]
|
||||||
partnerAddrs = [member['addr'] for member in response if member['addr'] != selfAddress]
|
|
||||||
add_self = selfAddress and len(partnerAddrs) == len(response)
|
super(DynMemberSyncObj, self).__init__(selfAddress, partnerAddrs, conf, transportClass=_TCPTransport)
|
||||||
break
|
|
||||||
|
|
||||||
super(DynMemberSyncObj, self).__init__(selfAddress, partnerAddrs, conf, transportClass=MyTCPTransport)
|
|
||||||
if add_self:
|
if add_self:
|
||||||
threading.Thread(target=utility.sendMessage, args=(['add', selfAddress],)).start()
|
thread = threading.Thread(target=utility.executeCommand, args=(['add', selfAddress],))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def _get_members(self):
|
def getMembers(self, args, callback):
|
||||||
ret = [{'addr': node.id, 'leader': node == self._getLeader(),
|
callback([{'addr': node.id, 'leader': node == self._getLeader(), 'status': CONNECTION_STATE.CONNECTED
|
||||||
'status': CONNECTION_STATE.CONNECTED if node in self._SyncObj__connectedNodes
|
if self.isNodeConnected(node) else CONNECTION_STATE.DISCONNECTED} for node in self.otherNodes] +
|
||||||
else CONNECTION_STATE.DISCONNECTED} for node in self._SyncObj__otherNodes]
|
[{'addr': self.selfNode.id, 'leader': self._isLeader(), 'status': CONNECTION_STATE.CONNECTED}], None)
|
||||||
ret.append({'addr': self._SyncObj__selfNode.id, 'leader': self._isLeader(),
|
|
||||||
'status': CONNECTION_STATE.CONNECTED})
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _SyncObj__doChangeCluster(self, request, reverse=False):
|
def _onTick(self, timeToWait=0.0):
|
||||||
ret = False
|
super(DynMemberSyncObj, self)._onTick(timeToWait)
|
||||||
if not self._SyncObj__selfNode or request[0] != 'add' or reverse or request[1] != self._SyncObj__selfNode.id:
|
|
||||||
ret = super(DynMemberSyncObj, self)._SyncObj__doChangeCluster(request, reverse)
|
# The SyncObj calls onReady callback only when cluster got the leader and is ready for writes.
|
||||||
if ret:
|
# In some cases for us it is safe to "signal" the Raft object when the local log is fully applied.
|
||||||
self.forceLogCompaction()
|
# We are using the `applied_local_log` property for that, but not calling the callback function.
|
||||||
return ret
|
if self.__early_apply_local_log and not self.applied_local_log and self.raftLastApplied == self.raftCommitIndex:
|
||||||
|
self.applied_local_log = True
|
||||||
|
|
||||||
|
|
||||||
class KVStoreTTL(DynMemberSyncObj):
|
class KVStoreTTL(DynMemberSyncObj):
|
||||||
|
|
||||||
def __init__(self, selfAddress, partnerAddrs, conf, on_set=None, on_delete=None):
|
def __init__(self, on_ready, on_set, on_delete, **config):
|
||||||
|
self.__thread = None
|
||||||
self.__on_set = on_set
|
self.__on_set = on_set
|
||||||
self.__on_delete = on_delete
|
self.__on_delete = on_delete
|
||||||
self.__limb = {}
|
self.__limb = {}
|
||||||
self.__retry_timeout = None
|
self.__retry_timeout = None
|
||||||
self.__early_apply_local_log = selfAddress is not None
|
|
||||||
self.applied_local_log = False
|
self_addr = config.get('self_addr')
|
||||||
super(KVStoreTTL, self).__init__(selfAddress, partnerAddrs, conf)
|
partner_addrs = set(config.get('partner_addrs', []))
|
||||||
|
if config.get('patronictl'):
|
||||||
|
if self_addr:
|
||||||
|
partner_addrs.add(self_addr)
|
||||||
|
self_addr = None
|
||||||
|
|
||||||
|
# Create raft data_dir if necessary
|
||||||
|
raft_data_dir = config.get('data_dir', '')
|
||||||
|
if raft_data_dir != '':
|
||||||
|
validate_directory(raft_data_dir)
|
||||||
|
|
||||||
|
file_template = (self_addr or '')
|
||||||
|
file_template = file_template.replace(':', '_') if os.name == 'nt' else file_template
|
||||||
|
file_template = os.path.join(raft_data_dir, file_template)
|
||||||
|
conf = SyncObjConf(password=config.get('password'), autoTick=False, appendEntriesUseBatch=False,
|
||||||
|
bindAddress=config.get('bind_addr'), commandsWaitLeader=config.get('commandsWaitLeader'),
|
||||||
|
fullDumpFile=(file_template + '.dump' if self_addr else None),
|
||||||
|
journalFile=(file_template + '.journal' if self_addr else None),
|
||||||
|
onReady=on_ready, dynamicMembershipChange=True)
|
||||||
|
|
||||||
|
super(KVStoreTTL, self).__init__(self_addr, partner_addrs, conf)
|
||||||
self.__data = {}
|
self.__data = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -175,7 +151,7 @@ class KVStoreTTL(DynMemberSyncObj):
|
|||||||
|
|
||||||
if old_value and old_value['created'] != value['created']:
|
if old_value and old_value['created'] != value['created']:
|
||||||
value['created'] = value['updated']
|
value['created'] = value['updated']
|
||||||
value['index'] = self._SyncObj__raftLastApplied + 1
|
value['index'] = self.raftLastApplied + 1
|
||||||
|
|
||||||
self.__data[key] = value
|
self.__data[key] = value
|
||||||
if self.__on_set:
|
if self.__on_set:
|
||||||
@@ -242,27 +218,29 @@ class KVStoreTTL(DynMemberSyncObj):
|
|||||||
return {k: v for k, v in self.__data.items() if k.startswith(key)}
|
return {k: v for k, v in self.__data.items() if k.startswith(key)}
|
||||||
|
|
||||||
def _onTick(self, timeToWait=0.0):
|
def _onTick(self, timeToWait=0.0):
|
||||||
# The SyncObj starts applying the local log only when there is at least one node connected.
|
|
||||||
# We want to change this behavior and apply the local log even when there is nobody except us.
|
|
||||||
# It gives us at least some picture about the last known cluster state.
|
|
||||||
if self.__early_apply_local_log and not self.applied_local_log and self._SyncObj__needLoadDumpFile:
|
|
||||||
self._SyncObj__raftCommitIndex = self._SyncObj__getCurrentLogIndex()
|
|
||||||
self._SyncObj__raftCurrentTerm = self._SyncObj__getCurrentLogTerm()
|
|
||||||
|
|
||||||
super(KVStoreTTL, self)._onTick(timeToWait)
|
super(KVStoreTTL, self)._onTick(timeToWait)
|
||||||
|
|
||||||
# The SyncObj calls onReady callback only when cluster got the leader and is ready for writes.
|
|
||||||
# In some cases for us it is safe to "signal" the Raft object when the local log is fully applied.
|
|
||||||
# We are using the `applied_local_log` property for that, but not calling the callback function.
|
|
||||||
if self.__early_apply_local_log and not self.applied_local_log and self._SyncObj__raftCommitIndex != 1 and \
|
|
||||||
self._SyncObj__raftLastApplied == self._SyncObj__raftCommitIndex:
|
|
||||||
self.applied_local_log = True
|
|
||||||
|
|
||||||
if self._isLeader():
|
if self._isLeader():
|
||||||
self.__expire_keys()
|
self.__expire_keys()
|
||||||
else:
|
else:
|
||||||
self.__limb.clear()
|
self.__limb.clear()
|
||||||
|
|
||||||
|
def _autoTickThread(self):
|
||||||
|
self.__destroying = False
|
||||||
|
while not self.__destroying:
|
||||||
|
self.doTick(self.conf.autoTickPeriod)
|
||||||
|
|
||||||
|
def startAutoTick(self):
|
||||||
|
self.__thread = threading.Thread(target=self._autoTickThread)
|
||||||
|
self.__thread.daemon = True
|
||||||
|
self.__thread.start()
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
if self.__thread:
|
||||||
|
self.__destroying = True
|
||||||
|
self.__thread.join()
|
||||||
|
super(KVStoreTTL, self).destroy()
|
||||||
|
|
||||||
|
|
||||||
class Raft(AbstractDCS):
|
class Raft(AbstractDCS):
|
||||||
|
|
||||||
@@ -270,34 +248,16 @@ class Raft(AbstractDCS):
|
|||||||
super(Raft, self).__init__(config)
|
super(Raft, self).__init__(config)
|
||||||
self._ttl = int(config.get('ttl') or 30)
|
self._ttl = int(config.get('ttl') or 30)
|
||||||
|
|
||||||
self_addr = config.get('self_addr')
|
|
||||||
partner_addrs = config.get('partner_addrs', [])
|
|
||||||
if self._ctl:
|
|
||||||
if self_addr:
|
|
||||||
partner_addrs.append(self_addr)
|
|
||||||
self_addr = None
|
|
||||||
|
|
||||||
# Create raft data_dir if necessary
|
|
||||||
raft_data_dir = config.get('data_dir', '')
|
|
||||||
if raft_data_dir != '':
|
|
||||||
validate_directory(raft_data_dir)
|
|
||||||
|
|
||||||
ready_event = threading.Event()
|
ready_event = threading.Event()
|
||||||
file_template = os.path.join(config.get('data_dir', ''), (self_addr or ''))
|
self._sync_obj = KVStoreTTL(ready_event.set, self._on_set, self._on_delete, commandsWaitLeader=False, **config)
|
||||||
conf = SyncObjConf(password=config.get('password'), appendEntriesUseBatch=False,
|
self._sync_obj.startAutoTick()
|
||||||
bindAddress=config.get('bind_addr'), commandsWaitLeader=False,
|
|
||||||
fullDumpFile=(file_template + '.dump' if self_addr else None),
|
|
||||||
journalFile=(file_template + '.journal' if self_addr else None),
|
|
||||||
onReady=ready_event.set, dynamicMembershipChange=True)
|
|
||||||
|
|
||||||
self._sync_obj = KVStoreTTL(self_addr, partner_addrs, conf, self._on_set, self._on_delete)
|
|
||||||
while True:
|
while True:
|
||||||
ready_event.wait(5)
|
ready_event.wait(5)
|
||||||
if ready_event.isSet() or self._sync_obj.applied_local_log:
|
if ready_event.isSet() or self._sync_obj.applied_local_log:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logger.info('waiting on raft')
|
logger.info('waiting on raft')
|
||||||
self._sync_obj.forceLogCompaction()
|
|
||||||
self.set_retry_timeout(int(config.get('retry_timeout') or 10))
|
self.set_retry_timeout(int(config.get('retry_timeout') or 10))
|
||||||
|
|
||||||
def _on_set(self, key, value):
|
def _on_set(self, key, value):
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from patroni.daemon import AbstractPatroniDaemon, abstract_main
|
from .daemon import AbstractPatroniDaemon, abstract_main
|
||||||
from patroni.dcs.raft import KVStoreTTL
|
from .dcs.raft import KVStoreTTL
|
||||||
from pysyncobj import SyncObjConf
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -13,16 +11,13 @@ class RaftController(AbstractPatroniDaemon):
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(RaftController, self).__init__(config)
|
super(RaftController, self).__init__(config)
|
||||||
|
|
||||||
raft_config = self.config.get('raft')
|
config = self.config.get('raft')
|
||||||
self_addr = raft_config['self_addr']
|
assert 'self_addr' in config
|
||||||
template = os.path.join(raft_config.get('data_dir', ''), self_addr)
|
self._raft = KVStoreTTL(None, None, None, **config)
|
||||||
self._syncobj_config = SyncObjConf(autoTick=False, appendEntriesUseBatch=False, dynamicMembershipChange=True,
|
|
||||||
journalFile=template + '.journal', fullDumpFile=template + '.dump')
|
|
||||||
self._raft = KVStoreTTL(self_addr, raft_config.get('partner_addrs', []), self._syncobj_config)
|
|
||||||
|
|
||||||
def _run_cycle(self):
|
def _run_cycle(self):
|
||||||
try:
|
try:
|
||||||
self._raft.doTick(self._syncobj_config.autoTickPeriod)
|
self._raft.doTick(self._raft.conf.autoTickPeriod)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception('doTick')
|
logger.exception('doTick')
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -9,6 +9,7 @@ python-consul>=0.7.1
|
|||||||
click>=4.1
|
click>=4.1
|
||||||
prettytable>=0.7
|
prettytable>=0.7
|
||||||
python-dateutil
|
python-dateutil
|
||||||
pysyncobj>=0.3.7
|
pysyncobj>=0.3.8
|
||||||
|
cryptography>=1.4
|
||||||
psutil>=2.0.0
|
psutil>=2.0.0
|
||||||
ydiff>=1.2.0
|
ydiff>=1.2.0
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ AUTHOR_EMAIL = '[email protected], [email protected], alexk
|
|||||||
KEYWORDS = 'etcd governor patroni postgresql postgres ha haproxy confd' +\
|
KEYWORDS = 'etcd governor patroni postgresql postgres ha haproxy confd' +\
|
||||||
' zookeeper exhibitor consul streaming replication kubernetes k8s'
|
' zookeeper exhibitor consul streaming replication kubernetes k8s'
|
||||||
|
|
||||||
EXTRAS_REQUIRE = {'aws': ['boto'], 'etcd': ['python-etcd'], 'etcd3': ['python-etcd'], 'consul': ['python-consul'],
|
EXTRAS_REQUIRE = {'aws': ['boto'], 'etcd': ['python-etcd'], 'etcd3': ['python-etcd'],
|
||||||
'exhibitor': ['kazoo'], 'zookeeper': ['kazoo'], 'kubernetes': ['ipaddress'], 'raft': ['pysyncobj']}
|
'consul': ['python-consul'], 'exhibitor': ['kazoo'], 'zookeeper': ['kazoo'],
|
||||||
|
'kubernetes': ['ipaddress'], 'raft': ['pysyncobj', 'cryptography']}
|
||||||
COVERAGE_XML = True
|
COVERAGE_XML = True
|
||||||
COVERAGE_HTML = False
|
COVERAGE_HTML = False
|
||||||
|
|
||||||
@@ -171,10 +172,16 @@ def setup_package(version):
|
|||||||
if r == '':
|
if r == '':
|
||||||
continue
|
continue
|
||||||
extra = False
|
extra = False
|
||||||
for e, v in EXTRAS_REQUIRE.items():
|
for e, deps in EXTRAS_REQUIRE.items():
|
||||||
if v and r.startswith(v[0]):
|
for i, v in enumerate(deps):
|
||||||
EXTRAS_REQUIRE[e] = [r] if e != 'kubernetes' or sys.version_info < (3, 0, 0) else []
|
if r.startswith(v):
|
||||||
extra = True
|
if e != 'kubernetes' or sys.version_info < (3, 0, 0):
|
||||||
|
deps[i] = r
|
||||||
|
else:
|
||||||
|
deps = []
|
||||||
|
EXTRAS_REQUIRE[e] = deps
|
||||||
|
extra = True
|
||||||
|
break
|
||||||
if not extra:
|
if not extra:
|
||||||
install_requires.append(r)
|
install_requires.append(r)
|
||||||
|
|
||||||
|
|||||||
+18
-32
@@ -3,13 +3,13 @@ import unittest
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from mock import Mock, patch
|
from mock import Mock, PropertyMock, patch
|
||||||
from patroni.dcs.raft import DynMemberSyncObj, KVStoreTTL, Raft, SyncObjUtility
|
from patroni.dcs.raft import DynMemberSyncObj, KVStoreTTL, Raft, SyncObjUtility
|
||||||
from pysyncobj import SyncObjConf, FAIL_REASON
|
from pysyncobj import SyncObjConf, FAIL_REASON
|
||||||
|
|
||||||
|
|
||||||
def remove_files(prefix):
|
def remove_files(prefix):
|
||||||
for f in ('journal', 'dump'):
|
for f in ('journal', 'journal.meta', 'dump'):
|
||||||
f = prefix + f
|
f = prefix + f
|
||||||
if os.path.isfile(f):
|
if os.path.isfile(f):
|
||||||
for i in range(0, 15):
|
for i in range(0, 15):
|
||||||
@@ -31,50 +31,38 @@ class TestDynMemberSyncObj(unittest.TestCase):
|
|||||||
self.conf = SyncObjConf(appendEntriesUseBatch=False, dynamicMembershipChange=True, autoTick=False)
|
self.conf = SyncObjConf(appendEntriesUseBatch=False, dynamicMembershipChange=True, autoTick=False)
|
||||||
self.so = DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
self.so = DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
||||||
|
|
||||||
@patch.object(SyncObjUtility, 'sendMessage')
|
@patch.object(SyncObjUtility, 'executeCommand')
|
||||||
def test_add_member(self, mock_send_message):
|
def test_add_member(self, mock_execute_command):
|
||||||
mock_send_message.return_value = [{'addr': '127.0.0.1:1235'}, {'addr': '127.0.0.1:1236'}]
|
mock_execute_command.return_value = [{'addr': '127.0.0.1:1235'}, {'addr': '127.0.0.1:1236'}]
|
||||||
mock_send_message.ver = 0
|
mock_execute_command.ver = 0
|
||||||
DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
||||||
self.conf.dynamicMembershipChange = False
|
self.conf.dynamicMembershipChange = False
|
||||||
DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
DynMemberSyncObj('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
||||||
|
|
||||||
def test___onUtilityMessage(self):
|
def test_getMembers(self):
|
||||||
self.so._SyncObj__encryptor = Mock()
|
|
||||||
mock_conn = Mock()
|
mock_conn = Mock()
|
||||||
mock_conn.sendRandKey = None
|
|
||||||
self.so._SyncObj__transport._onIncomingMessageReceived(mock_conn, 'randkey')
|
|
||||||
self.so._SyncObj__transport._onIncomingMessageReceived(mock_conn, ['members'])
|
self.so._SyncObj__transport._onIncomingMessageReceived(mock_conn, ['members'])
|
||||||
self.so._SyncObj__transport._onIncomingMessageReceived(mock_conn, ['status'])
|
|
||||||
|
|
||||||
def test__SyncObj__doChangeCluster(self):
|
def test__SyncObj__doChangeCluster(self):
|
||||||
self.so._SyncObj__doChangeCluster(['add', '127.0.0.1:1236'])
|
self.so._SyncObj__doChangeCluster(['add', '127.0.0.1:1236'])
|
||||||
|
|
||||||
def test_utility(self):
|
|
||||||
utility = SyncObjUtility(['127.0.0.1:1235'], self.conf)
|
|
||||||
utility.setPartnerNode(list(utility._SyncObj__otherNodes)[0])
|
|
||||||
utility.sendMessage(['members'])
|
|
||||||
utility._onMessageReceived(0, '')
|
|
||||||
|
|
||||||
|
|
||||||
|
@patch.object(SyncObjConf, 'fullDumpFile', PropertyMock(return_value=None), create=True)
|
||||||
|
@patch.object(SyncObjConf, 'journalFile', PropertyMock(return_value=None), create=True)
|
||||||
class TestKVStoreTTL(unittest.TestCase):
|
class TestKVStoreTTL(unittest.TestCase):
|
||||||
|
|
||||||
|
@patch.object(SyncObjConf, 'fullDumpFile', PropertyMock(return_value=None), create=True)
|
||||||
|
@patch.object(SyncObjConf, 'journalFile', PropertyMock(return_value=None), create=True)
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.conf = SyncObjConf(appendEntriesUseBatch=False, appendEntriesPeriod=0.001,
|
|
||||||
raftMinTimeout=0.004, raftMaxTimeout=0.005, autoTickPeriod=0.001)
|
|
||||||
callback = Mock()
|
callback = Mock()
|
||||||
callback.replicated = False
|
callback.replicated = False
|
||||||
self.so = KVStoreTTL('127.0.0.1:1234', [], self.conf, on_set=callback, on_delete=callback)
|
self.so = KVStoreTTL(None, callback, callback, self_addr='127.0.0.1:1234')
|
||||||
|
self.so.startAutoTick()
|
||||||
self.so.set_retry_timeout(10)
|
self.so.set_retry_timeout(10)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def destroy(so):
|
|
||||||
so.destroy()
|
|
||||||
so._SyncObj__thread.join()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if self.so:
|
if self.so:
|
||||||
self.destroy(self.so)
|
self.so.destroy()
|
||||||
|
|
||||||
def test_set(self):
|
def test_set(self):
|
||||||
self.assertTrue(self.so.set('foo', 'bar', prevExist=False, ttl=30))
|
self.assertTrue(self.so.set('foo', 'bar', prevExist=False, ttl=30))
|
||||||
@@ -83,7 +71,7 @@ class TestKVStoreTTL(unittest.TestCase):
|
|||||||
self.assertTrue(self.so.retry(self.so._set, 'foo', {'value': 'buz', 'created': 1, 'updated': 1}))
|
self.assertTrue(self.so.retry(self.so._set, 'foo', {'value': 'buz', 'created': 1, 'updated': 1}))
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
self.conf.autoTickPeriod = 0.1
|
self.so.autoTickPeriod = 0.2
|
||||||
self.so.set('foo', 'bar')
|
self.so.set('foo', 'bar')
|
||||||
self.so.set('fooo', 'bar')
|
self.so.set('fooo', 'bar')
|
||||||
self.assertFalse(self.so.delete('foo', prevValue='buz'))
|
self.assertFalse(self.so.delete('foo', prevValue='buz'))
|
||||||
@@ -111,11 +99,10 @@ class TestKVStoreTTL(unittest.TestCase):
|
|||||||
|
|
||||||
def test_on_ready_override(self):
|
def test_on_ready_override(self):
|
||||||
self.assertTrue(self.so.set('foo', 'bar'))
|
self.assertTrue(self.so.set('foo', 'bar'))
|
||||||
self.destroy(self.so)
|
self.so.destroy()
|
||||||
self.so = None
|
self.so = None
|
||||||
self.conf.onReady = Mock()
|
so = KVStoreTTL(Mock(), None, None, self_addr='127.0.0.1:1234',
|
||||||
self.conf.autoTick = False
|
partner_addrs=['127.0.0.1:1235'], patronictl=True)
|
||||||
so = KVStoreTTL('127.0.0.1:1234', ['127.0.0.1:1235'], self.conf)
|
|
||||||
so.doTick(0)
|
so.doTick(0)
|
||||||
so.destroy()
|
so.destroy()
|
||||||
|
|
||||||
@@ -150,7 +137,6 @@ class TestRaft(unittest.TestCase):
|
|||||||
self.assertTrue(raft.take_leader())
|
self.assertTrue(raft.take_leader())
|
||||||
raft.watch(None, 0.001)
|
raft.watch(None, 0.001)
|
||||||
raft._sync_obj.destroy()
|
raft._sync_obj.destroy()
|
||||||
raft._sync_obj._SyncObj__thread.join()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
remove_files(os.path.join(self._TMP, '127.0.0.1:1234.'))
|
remove_files(os.path.join(self._TMP, '127.0.0.1:1234.'))
|
||||||
|
|||||||
Reference in New Issue
Block a user