Master shares view of cluster
-----------------------------

NotifyPartitionTable            ; whole partTab         = SendPartitionTable

NotifyPartitionChanges          ; δ(partTab)
NotifyNodeInformation           ; δ(nodeTab)
NotifyClusterState              ; δ(clusterState)



On iteration
------------

All ZODB/NEO iterations can be unified into one scheme:

- iterator():	tid↑	flags = objects intermixed
- history(oid): tid↓	flags = filter by oid (on server)
- undoLog:	tid↓	flags = no objects content needed;
				which tid is undoable is computed on _client_
- replication:	tid↑	flags = objects intermixed

each iteration yields all information about txn:

  - tid
  - meta (user/description/ext)	(+ status ?)
  - []oid changed by this txn

via 1 packet per 1 txn.

Objects content is sent via 1 obj packet via 1 object:

  - tid:oid
  - data	(+ sha1 ?)


~~~

- iterator()	- not used at all in Zope/ERP5
- history(oid)	- used in "ZODB history" tab & like
- undoLog	- (zope/src/App/Undo.py)
- replication	- internal to NEO


Storage related messages
------------------------

NotifyReady		S -> M

Recovery		PM -> S		S -> PM
LastIDs			PM -> S		S -> PM

PartitionTable		PM -> S		S -> PM
NotifyPartitionTable	PM -> S, C
PartitionChanges	PM -> S, C		// subset of NotifyPartitionTable (?)

StartOperation		PM -> S
StopOperation		PM -> S

UnfinishedTransactions	S -> PM		PM -> S
LockedTransactions	PM -> S		S -> PM

FinalTID		* -> S , C -> PM (?)
ValidateTransaction	PM -> S

NotifyTransactionFinished M -> S

LockInformation		PM -> S		S -> PM
UnlockInformation	PM -> S

StoreObject  C -> S
	.oid		OID
	.serial		TID	// original serial
	.data		String
	.data_serial	TID
	.tid		tid	// current txn id
	.unlock		bool

AnswerStoreObject  S -> C
	.conflicting	bool
	.oid		OID
	.serial		TID

GetObject  C -> S
	.oid
	.serial
	.tid

AnswerObject  S -> C
	.oid		OID
	.serial_start	TID
	.serial_end	TID
	.data		String
	.data_serial	TID


AbortTransaction	C -> S, PM
	.tid

StoreTransaction	C -> S		S -> C
	.tid			TID
	user, description, ext	String
	oidv			[]OID


VoteTransaction		C -> S		S -> C
	.tid


TIDList  C -> S
	first, last	Index	// u64 [first, last)
	partition	Number	// u32

AnswerTIDList  S -> C
	tidv	[]TID

TIDListFrom  C -> S
	min_tid, max_tid  TID
	length		  Number
	partition	  Number

AnswerTIDListFrom  S -> C
	tidv	[]TID

TransactionInformation  * -> S
	tid	TID

AnswerTransactionInformation  S -> *
	tid	TID
	user, description, extension	String
	packed	bool
	oidv	[]OID

ObjectHistory  C -> S
	oid	OID
	first, last	Index	// [first, last]

AnswerObjectHistory
	oid	  OID
	historyv  []strict{serial TID; size Number}

ObjectUndoSerial  C -> S
	tid		TID
	ltid		TID	// ?
	undone_tid	TID	// ?
	oidv		[]OID

AnswerObjectUndoSerial  S -> C
	{} oid ->
		.current_serial	TID
		.undo_serial	TID
		.is_current	bool

HasLock  C -> S
	tid	TID
	oid	OID

AnswerHasLock  S -> C
	oid		OID
	lock_state	LockState	// not_locket, granted, granted_to_other

CheckCurrentSerial  C -> S	AnswerCheckCurrentSerial  S -> C
	tid	TID			conflicting	bool
	serial	TID			oid		OID
	oid	OID			serial		TID

Pack	C -> M -> S
	tid	TID

CheckPartition	M -> S
	partition		Number
	upstream_name		String
	address			Address
	min_tid, max_tid	TID

CheckTIDRange	S -> S				AnswerCheckTIDRange	S -> S
	partition		Number			count		Number
	length			Number			checksum	Checksum
	min_tid, max_tid	TID			max_tid		TID

CheckSerialRange  S -> S

ParitionCorrupted  S -> M
	partition	Number
	uuidv		[]UUID



~~~~~~~~
(?)
NotifyNodeIntormation
NodeInformation

TODO


Storage communications
----------------------

* bootstrap (connect to primary master):

>M	RequestIdentification
<M	AcceptIdentification	(AnswerRequestIdentification)

* init (under master command go through recovery / verification)

(BaseMasterHandler)
<M	AskRecovery
>M	AnswerRecovery		(ptid, backup_tid, truncate_tid)

<M	AskPartitionTable
>M	AnswerPartitionTable	(ptid, []PtRow)

# neoctl start
<M	NotifyNodeInformation	(S1.state=RUNNING)
# S: "I was told I'm RUNNING"

<M	NotifyClusterInformation	(state=VERIFYING)
<M	NotifyNodeInformation		(S1.state=RUNNING)	# again, why?
# S: "I was told I'm RUNNING"

<M	NotifyPartitionTable	(ptid=1, `node 0: S1, R`)
# S saves PT info locally

# M asks about unfinished transactions
<M	AskLockedTransactions
>M	AnswerLockedTransactions	{} ttid -> tid	# in example we have empty

<M	AskLastIDs
>M	AnswerLastIDs			(last_oid, last_tid)

<M	NotifyClusterInformation	(state=RUNNING)

<M	StartOperation
>M	NotifyReady

--- also in base/init handlers:
<M	StopOperation
<M	AskFinalTID
# >M	answerUnfinishedTransactions (only upon S request)
# <M	ReelectPrimary			?
<M	Truncate
<M	Validatetransactions


* operation (serve clients):

<C	RequestIdentification
>C	AnswerRequestIdentification

# XXX why here? -> root initialization (?)
<C	GetObject
>C	Error

<C	StoreObject
>C	AnswerStoreObject

<C	StoreTransaction
>C	AnswerStoreTransaction

<M	LockInformation
>M	AnswerLockInformation
<M	UnlockInformation

<M	NotifyNodeInformation	# C1 disconnects


Commit communications
---------------------

* tpc_begin

  C -> M	AskBeginTransaction(tid?)
  C <- M	AnswerBeginTransaction(ttid)

* store(oid, serial, data, ...)

  for S in storagesFor(oid):
	C -> S	AskStoreObject(oid, serial, ttid ...)

--- eventually ---

  some S from ^^^:
	C <- S	AnswerStoreObject(conflicting, oid, serial)

* tpc_vote

  - wait for all responses from S (see "eventually" ^^^)
    if conflicting:
	resolve conflicts

  for S' in storagesFor(ttid):
	C -> S'	  AskStoreTransaction(ttid)	# empty answer

  for S" in (storagesFor(oid) - storagesFor):
	C -> S"  AskVoteTransaction(ttid)	# empty answer

  - wait for all responses from S' and S" ^^^

* tpc_finish

C -> M  AskFinishTransaction(ttid, cache_dict, checked_list)

     for S in M.allStorages:
	M -> S  AskLockInformation(ttid, tid)
	...

     eventually:
	M <- S	AnswerInformationLocked(ttid)

C <- M	AnswerTransactionFinished(ttid, tid)

     for S in M.allStorages:
	M -> S  NotifyUnlockInformation(ttid)



* tpc_abort

C -> Sv(participating for oidv & tidv) + M
	AbortTransaction(ttid)


Client communication
--------------------

PACKET    x          #0x0001 RequestIdentification          > None (127.0.0.1:26361)
PACKET    x          #0x0001 AnswerRequestIdentification    < None (127.0.0.1:26361)
INFO      x          connected to a primary master node
INFO      x          Got a new UUID: C1
PACKET    x          #0x0000 NotifyNodeInformation          < M1 (127.0.0.1:26361)
PACKET    x           ! C1 | CLIENT  |                 | RUNNING | 2017-08-29 14:55:03.666895
PACKET    x           ! M1 | MASTER  | 127.0.0.1:26361 | RUNNING | None
PACKET    x           ! S1 | STORAGE | 127.0.0.1:44529 | RUNNING | 2017-08-29 14:50:16.415306
INFO      x          Initializing from master
PACKET    x          #0x0003 PartitionTable                 > M1 (127.0.0.1:26361)
PACKET    x          #0x0003 AnswerPartitionTable           < M1 (127.0.0.1:26361)
DEBUG     x          partition table loaded (ptid=1)
DEBUG     x          pt: node 0: S1, R
DEBUG     x          pt: 0: U
PACKET    x          #0x0005 LastTransaction                > M1 (127.0.0.1:26361)
PACKET    x          #0x0005 AnswerLastTransaction          < M1 (127.0.0.1:26361)
INFO      x          Connected and ready
PACKET    x          #0x0007 Ping                           > M1 (127.0.0.1:26361)
PACKET    x          #0x0007 AnswerPing                     < M1 (127.0.0.1:26361)

PACKET    x          #0x0001 RequestIdentification          > S1 (127.0.0.1:44529)
PACKET    x          #0x0001 AnswerRequestIdentification    < S1 (127.0.0.1:44529)
PACKET    x          #0x0003 GetObject                      > S1 (127.0.0.1:44529)
...


Py: out-of-order answers
------------------------

- AnswerTransactionFinished	master.onTransactionCommitted
- AnswerPack			master.StorageServiceHandler.answerPack
- AnswerCheckTIDRange		storage.StorageOperationHandler .askCheckTIDRange
- AnswerCheckSerialRange	----//----			.askCheckSerialRange
- Error.Replication_Error	----//----			.askFetchTransactions
- AddTransaction		----//----
- AnswerFetchTransactions	----//----
- AddObject			----//----			.askFetchObjects
- AnswerFetchObjects		----//----


Tables
------

- config
	.name	text !null	PK
	.value	text

	(name, nid, partitions, ptid, replicas, version, zodb=pickle...)

# partition table
- pt
	.rid	int !null	// = row id = part of oid space
	.nid	int !null
	.state	tinyint	// = cell state

	PK (rid, nid)

# committed txns
- trans
	.partition	smallint
	.tid		bigint
	.packed		bool
	.oids		mediumblob	// []oid
	.user		blob
	.description	blob
	.ext		blob
	.ttid		bigint		// XXX ?

	pkey (partition, tid)

# committed object metadata
- obj
	.partition	smallint
	.oid		bigint
	.tid		bigint
	.data_id	bigint | NULL	// -> data.id
	.value_tid	bigint | NULL	// XXX ?	(у нас NULL)

	pkey (partition, tid, oid)
	key (partition, oid, tid)
	key data_id

# object data
- data
	.id		bigint
	.hash		sha1		// UNIQUE (hash, compression)
	.compression	tinyint
	.value		mediumblob

	key id
	key (hash, compression)	// <- from UNIQUE ^^^

- (bigdata)

# uncommitted transactions
# (= trans)
- ttrans
	.partition	smallint
	.tid		bigint
	.packed		bool
	.oids		mediumblob	// []oid
	.user		blob
	.description	blob
	.ext		blob
	ttid		bigint		// XXX ?

# uncommitted object metadata
# (= obj)
- tobj


Partition Table (general & current-py)
--------------------------------------

	#Np (how-many partitions)    #R (replication factor)
Cell
	.node   (-> .nodeid, .addr)
	.cell_state

	.backup_tid         # last tid this cell has all data for
	.replicating        # currently replicating up to this (.replicating) tid

PT
	.id↑
	.partition_list [#Np] of []Cell
	.count_dict     {} node -> #node_used_in_pt


	 Pt
	+-+
	| |
	+-+  +----------+ +------------+ +-----+
	| |  |node,state| |node2,state2| |cell3| ...
	+-+  +----------+ +------------+ +-----+
     Np	| |
	+-+
	| |
	+-+     oid -> PTentry (as PT[oid % Np]
	| |     tid
	+-+


----------------------------------------

BaseApplication
    .em     EventManager
    .nm     NodeManager

(master) Application < BaseApplication
    .tm     (master) TransactionManager
    .pt     (master) PartitionTable

    .listening_conn         ListeningConnection
    .primary                bool    # "am I primary"
    .primary_master_node    ?
    .cluster_state

    .current_manager    None | RecoveryManager | VerificationManager
    .backup_app         BackupApplication

    .backup_tid
    .truncate_tid

    .admin_handler              AdministrationHandler
    .secondary_master_handler   SecondaryMasterHandler
    .client_service_handler     ClientServiceHandler
    .storage_service_handler    StorageServiceHandler

(storage) Application < BaseApplication
    .tm     (storage) TransactionManager
    .dm     DatabaseManager     (Importer | MySQL | SQLite)

    .pt     ? PartitionTable

    .checker        Checker         # checks sha1([]tid  -- XXX only tid list, not other metadata & data)
    .replicator     Replicator

    .listening_conn ListeningConnection
    .master_conn
    .master_node

    # (queued events are executed at some txn boundaries & locks)
    .event_queue        deque   (key, callable, msg_id, conn, args)
    .event_queue_dict   {}      key -> count(.event_queue, key)

    .task_queue         deque

    .operational        bool

    .ready              bool    # when .operational=T and "got all informations"


Handlers
--------

AdministrationHandler < MasterHandler
    .connectionLost(...)
    .setClusterSate(...)
    .setNodeState(...)
    .addPendingNodes(...)
    .tweakPartitionTable(...)
    .truncate(conn, tid)
    .checkReplicas(...)

SecondaryMasterHandler < MasterHandler
    .connectionLost(...)
    .announcePrimary(...)
    .reelectPrimary(...)

ClientServiceHandler < MasterHandler
    .connectionLost(...)
    .askNodeInformation(conn)           # send informations about master and storages only
    .askBeginTransaction(conn, tid)
    .askNewOIDs(conn, num_oids)
    .askFinalTID(conn, ttid)
    .askPack(conn, tid)
    .abortTransaction(conn, tid)

StorageServiceHandler < BaseServiceHandler
    .connectionCompleted(conn, new)
    .connectionLost(conn, new_state)
    .askUnfinishedTransactions(conn)
    .answerInformationLocked(conn, ttid)
    .notifyPartitionCorrupted(conn, partition, cell_list)
    .notifyReplicationDone(conn, offset, tid)
    .answerPack(conn, status)


MasterHandler < EventHandler
    .connectionCompleted(...)
    .requestIdentification(...)
    .askClusterState(...)
    .askRecovery(...)
    .askLastIDs(...)
    .askLastTransaction(...)
    .askNodeInformation(...)
    .askPartitionTable(...)

BaseServiceHandler < MasterHandler
    .connectionCompleted(...)
    .connectionLost(...)
    .notifyReady(conn)

EventHandler
    .packetReceived(conn, pkt, **kw)
        -> .dispatch(conn, pkt, **kw)
            -> conn.setPeerId(pkt.getId())
            -> getattr(self, pkt.handler_method_name) (conn, *pkt.decode(), **kw)

           except ...:
               reply error



IdentificationHandler < MasterHandler

Connections
-----------

ListeningConnection < BaseConnection

    .__init__(self, app, handler, addr):
        connector = self.ConnectorClass(addr)
        BaseConnection.__init__(self, app, connector, addr)
        connector.makeListeningConnection()                     # ~= self.socket.bind(addr) + listen()
        self.em.register(self)

    .readable(self):
        connector, addr = self.connector.accept()
        conn = ServerConnection(self.em, self._handlers.getHandler(), connector, addr)
        conn._connected()


BaseConnection
    .ConnectorClass = SocketConnector   # by default

    .em             EventManager
    .connector      self.ConnectorClass()   # by default ?
    .addr
    ._handlers      HandlerSwitcher


HandlerSwitcher
    # pending handlers and related requests
    ._pending       [] of [{}(msg_id -> answer_class, timeout, on_timeout, kw), handler]

    .emit(request, timeout, on_timeout, kw)
    .handle(connection, pkt)
        -> ._handle(connection, pkt)

    ._handle(connection, pkt):
        msg_id = pkt.getId()
        request_dict, handler = ._pending[0]
        if !pkt.isResponse():
            handler.packetReceived(connection, pkt)

        klass, _, _, kw = request_pop(msg_id)
        if klass && isinstance(pkt, klass) || pkt.isError():
            handler.packetReceived(connection, packet, kw)
        else:
            # unexpected answer

        # skip all intermediate handlers and apply to "latest" (fff6cf45)
        while len(._pending) > 1 && !._pending[0].handler:
            del ._pending[0]


ServerConnection <Connection
    KEEP_ALIVE = Connection.KEEP_ALIVE + 5
    server = True

# main worker class for handling data exchanging
Connection <BaseConnection
    .read_buf       ReadBuffer  ([][]byte  = iovec)
    ._queue         []
    ._parser_state  ParserState

    .writable()
        -> .connector.send()

    .readable()
        -> .connector.receive(.read_buf)
           while .read_buf:
               pkt = Packets.parse(.read_buf, ._parser_state)
               ._queue.append(pkt)
           return bool(._queue)

    # called from under EventManager.poll()     # incoming messages
    .process()
        -> ._handlers.handle(self, ._queue.pop(0))

    ._addPacket(pkt):
        .connector.queue(pkt.encode())
        .em.addWriter(self)

    .notify(pkt):
        pkt.setId(next-msg-id)
        ._addPacket(pkt)

    .ask(pkt, timeout=30, on_timeout=None, **kw):
        .notify(pkt)
        ._handlers.emit(pkt, timeout, on_timeout, kw)

    .answer(pkt, msg_id=None):
        pkt.setId(msgid ?: self.getPeerId())
        ._addPacket(pkt)


------------

EventManager
    .connection_dict        {}  fd -> Connection
    .reader_set             set
    .writer_set             set
    .epoll
    ._pending_processing    []Connection    # fetched from .epoll

    .register(conn)
    .unregister(conn, close=False)

    .poll(blocking=1):
        ._poll(blocking)
        to_process = ._pending_processing.pop(0)
        to_process.process()

    ._poll(blocking):
        event_list = .epoll.poll(blocking)
        for fd, event in event_list:
            conn = .connection_dict[fd]
            if event & EPOLLIN:
                if conn.readable():
                    ._addPendingConnection(conn)    # queues to ._pending_processing
            # EPOLLOUT -> wlist -> conn.writable()
            # EPOLLERR -> elist -> conn.readable() -> ._addPendingConnection(conn)

    .addReader(conn)
    .removeReader(conn)
    .addWriter(conn)
    .removeWriter(conn)


--------

# M waits for S* to connect so PT is filled
RecoveryManager < MasterHandler
    ...

# Finish all transactions for which we know that tpc_finish was called but not fully processed
#
# ( abort(?) / commit half-way finished transactions  (commit if we know tpc_finish was started)
#   XXX no actual abort happens since .verifyData():
#     Transaction not locked. No need to tell nodes to delete it,
#     since they drop any unfinished data just before being
#     operational. )
VerificationManager < BaseServiceHandler
    ...


--------

Node
	.state
	.address
	.nodeid
	.manager	# -> NodeManager

	.connection
	._identified

NodeManager
	[]Node
	{} address	-> Node
	{} nodeid	-> Node
	{} type		-> set<Node>
	{} state	-> set<Node>


Wire proto
----------

# all in network byte-order

PktHeader
	.id	u32	// autoincrement on ask (?)
	.code	u16	// defined by seqno if register(...) call in Packets
	.length	u32	// whole pkt length

# response_code = 0x8000 | code


List
	.len	u32
	[len]items

Dict
	.len	u32
	[len] key, value

Enum
	.value	s32	// None -> -1

String
	.len	u32
	[len] strdata

Address
	.host	String
	.port	u16	// present only if .host != ''

Bool
	.value	u8

Number
	.value	u32

Index
	.value	u64

PTID
	.value	u64	// 0 <-> None

Protocol
	Number		// encode <- version, decode -> check version

Checksum
	.checksum [20]u8

NodeID  // UUID
	.nodeid	s32	// 0 <-> None

TID
	.tid	[8]byte	// None <-> \xff*8

POID = PTID

Float
	.value	float64	// '\xff' * 8 if None


~~~~~~~~

NodeList []
	type		NodeType
	address		Address
	nodeid		NodeID
	state		NodeState
	id_timestamp	Float

CellList []
	nodeid	NodeID
	state	CellState

RowList  []
	offset	Number	// u32
	cell_list CellList

HistoryList []
	serial	TID
	size	Number	// u32

UUIDList []
	nodeid	NodeID

TidList []
	tid	TID

OidList	[]
	oid	OID

~~~~~~~~

Notify
	.message	String

Error
	.code		Number
	.message	String

Ping
	ø
	_answer = PFEmpty

CloseClient
	ø

RequestIdentification
	.protocol_version	PProtocol
	.node_type		NodeType
	.nodeid			NodeID
	.address		Address
	.name			String
	.id_timestamp		Float

TODO
