Commit c42272a3 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge remote-tracking branch 'origin/master' into t

* origin/master:
  neomigrate: fix typo in a warning message
  neoctl: make cell padding consistent when displaying the partition table
  storage: fix possible crash when delaying replication requests
  qa: bug found in assignment of storage node ids, add test
  Update comment of RECOVERING state
  Add support for OpenSSL >= 1.1
parents 9e7fb465 4feea25f
...@@ -307,7 +307,10 @@ class _SSLHandshake(_SSL): ...@@ -307,7 +307,10 @@ class _SSLHandshake(_SSL):
except ssl.SSLWantWriteError: except ssl.SSLWantWriteError:
return read_buf is not None return read_buf is not None
except socket.error, e: except socket.error, e:
self._error('send' if read_buf is None else 'recv', e) # OpenSSL 1.1 may raise socket.error(0)
# where previous versions raised SSLEOFError.
self._error('send' if read_buf is None else 'recv',
e if e.errno else None)
if not self.queued[0]: if not self.queued[0]:
del self.queued[0] del self.queued[0]
del self.receive, self.send del self.receive, self.send
......
...@@ -76,13 +76,13 @@ def ErrorCodes(): ...@@ -76,13 +76,13 @@ def ErrorCodes():
@Enum @Enum
def ClusterStates(): def ClusterStates():
# Once the primary master is elected, the cluster has a state, which is # The cluster is initially in the RECOVERING state, and it goes back to
# initially RECOVERING, during which the master: # this state whenever the partition table becomes non-operational again.
# An election of the primary master always happens, in case of a network
# cut between a primary master and all other nodes. The primary master:
# - first recovers its own data by reading it from storage nodes; # - first recovers its own data by reading it from storage nodes;
# - waits for the partition table be operational; # - waits for the partition table be operational;
# - automatically switch to VERIFYING if the cluster can be safely started. # - automatically switch to VERIFYING if the cluster can be safely started.
# Whenever the partition table becomes non-operational again, the cluster
# goes back to this state.
RECOVERING RECOVERING
# Transient state, used to: # Transient state, used to:
# - replay the transaction log, in case of unclean shutdown; # - replay the transaction log, in case of unclean shutdown;
......
...@@ -67,8 +67,8 @@ class TerminalNeoCTL(object): ...@@ -67,8 +67,8 @@ class TerminalNeoCTL(object):
asNode = staticmethod(uuid_int) asNode = staticmethod(uuid_int)
def formatRowList(self, row_list): def formatRowList(self, row_list):
return '\n'.join('%03d | %s' % (offset, return '\n'.join('%03d |%s' % (offset,
''.join('%s - %s |' % (uuid_str(uuid), state) ''.join(' %s - %s |' % (uuid_str(uuid), state)
for (uuid, state) in cell_list)) for (uuid, state) in cell_list))
for (offset, cell_list) in row_list) for (offset, cell_list) in row_list)
......
...@@ -45,7 +45,7 @@ def main(args=None): ...@@ -45,7 +45,7 @@ def main(args=None):
from neo.client.Storage import Storage as NEOStorage from neo.client.Storage import Storage as NEOStorage
if os.path.exists(source): if os.path.exists(source):
print("WARNING: This is not the recommended way to import data to NEO:" print("WARNING: This is not the recommended way to import data to NEO:"
" you should use Imported backend instead.\n" " you should use the Importer backend instead.\n"
"NEO also does not implement IStorageRestoreable interface," "NEO also does not implement IStorageRestoreable interface,"
" which means that undo information is not preserved when using" " which means that undo information is not preserved when using"
" this tool: conflict resolution could happen when undoing an" " this tool: conflict resolution could happen when undoing an"
......
...@@ -142,7 +142,7 @@ class StorageOperationHandler(EventHandler): ...@@ -142,7 +142,7 @@ class StorageOperationHandler(EventHandler):
# if client tasks are finished) # if client tasks are finished)
def getEventQueue(self): def getEventQueue(self):
return self.app.tm return self.app.tm.read_queue
@checkFeedingConnection(check=True) @checkFeedingConnection(check=True)
def askCheckTIDRange(self, conn, *args): def askCheckTIDRange(self, conn, *args):
......
...@@ -32,7 +32,7 @@ from neo.storage.transactions import TransactionManager, ConflictError ...@@ -32,7 +32,7 @@ from neo.storage.transactions import TransactionManager, ConflictError
from neo.lib.connection import ConnectionClosed, \ from neo.lib.connection import ConnectionClosed, \
ServerConnection, MTClientConnection ServerConnection, MTClientConnection
from neo.lib.exception import DatabaseFailure, StoppedOperation from neo.lib.exception import DatabaseFailure, StoppedOperation
from neo.lib.handler import DelayEvent from neo.lib.handler import DelayEvent, EventHandler
from neo.lib import logging from neo.lib import logging
from neo.lib.protocol import (CellStates, ClusterStates, NodeStates, NodeTypes, from neo.lib.protocol import (CellStates, ClusterStates, NodeStates, NodeTypes,
Packets, Packet, uuid_str, ZERO_OID, ZERO_TID, MAX_TID) Packets, Packet, uuid_str, ZERO_OID, ZERO_TID, MAX_TID)
...@@ -2322,6 +2322,36 @@ class Test(NEOThreadedTest): ...@@ -2322,6 +2322,36 @@ class Test(NEOThreadedTest):
self.assertFalse(m1.primary) self.assertFalse(m1.primary)
self.assertTrue(m1.is_alive()) self.assertTrue(m1.is_alive())
@with_cluster(start_cluster=0, master_count=2,
partitions=2, storage_count=2, autostart=2)
def testSplitBrainAtCreation(self, cluster):
"""
Check cluster creation when storage nodes are identified before all
masters see each other and elect a primary.
XXX: Do storage nodes need a node id before the cluster is created ?
Another solution is that they treat their ids as temporary as long
as the partition table is empty.
"""
for m, s in zip((min, max), cluster.storage_list):
s.nm.remove(m(s.nm.getMasterList(),
key=lambda node: node.getAddress()))
with ConnectionFilter() as f:
f.add(lambda conn, packet:
isinstance(packet, Packets.RequestIdentification)
and packet.decode()[0] == NodeTypes.MASTER)
cluster.start(recovering=True)
neoctl = cluster.neoctl
getClusterState = neoctl.getClusterState
getStorageList = lambda: neoctl.getNodeList(NodeTypes.STORAGE)
self.assertEqual(getClusterState(), ClusterStates.RECOVERING)
self.assertEqual(1, len(getStorageList()))
with Patch(EventHandler, protocolError=lambda *_: sys.exit()):
self.tic()
expectedFailure(self.assertEqual)(neoctl.getClusterState(),
ClusterStates.RUNNING)
self.assertEqual({1: NodeStates.RUNNING, 2: NodeStates.RUNNING},
{x[2]: x[3] for x in neoctl.getNodeList(NodeTypes.STORAGE)})
@with_cluster(partitions=2, storage_count=2) @with_cluster(partitions=2, storage_count=2)
def testStorageBackendLastIDs(self, cluster): def testStorageBackendLastIDs(self, cluster):
""" """
......
...@@ -314,12 +314,27 @@ class ReplicationTests(NEOThreadedTest): ...@@ -314,12 +314,27 @@ class ReplicationTests(NEOThreadedTest):
the backup cluster reacts very quickly to a new transaction. the backup cluster reacts very quickly to a new transaction.
""" """
upstream = backup.upstream upstream = backup.upstream
t1, c1 = upstream.getTransaction()
ob = c1.root()[''] = PCounterWithResolution()
t1.commit()
ob.value += 2
t2, c2 = upstream.getTransaction()
c2.root()[''].value += 3
self.tic()
with upstream.master.filterConnection(upstream.storage) as f: with upstream.master.filterConnection(upstream.storage) as f:
f.delayNotifyUnlockInformation() delay = f.delayNotifyUnlockInformation()
upstream.importZODB()(1) t1.commit()
self.tic() self.tic()
def storeObject(orig, *args, **kw):
p.revert()
f.remove(delay)
return orig(*args, **kw)
with Patch(upstream.storage.tm, storeObject=storeObject) as p:
t2.commit()
self.tic() self.tic()
# TODO check tids # TODO check tids
t1.begin()
self.assertEqual(5, ob.value)
self.assertEqual(1, self.checkBackup(backup)) self.assertEqual(1, self.checkBackup(backup))
@with_cluster() @with_cluster()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment