pax_global_header 0000666 0000000 0000000 00000000064 11227104631 0014507 g ustar 00root root 0000000 0000000 52 comment=078c7786374475060d0f338fb18521a32282c5d4
neoppod-078c7786374475060d0f338fb18521a32282c5d4/ 0000775 0000000 0000000 00000000000 11227104631 0017441 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/README 0000664 0000000 0000000 00000003047 11227104631 0020325 0 ustar 00root root 0000000 0000000 This is the second prototype of NEO. The design is described in:
http://www.nexedi.org/workspaces/neo
TODO
- Handling connection timeouts.
- Handling write timeouts.
- IdleEvent for a certain message type as well as a message ID.
- Flushing write buffers only without reading packets.
- Garbage collection of unused nodes.
- Stopping packet processing by returning a boolean value from
a handler, otherwise too tricky to exchange a handler with another.
- History.
- Multiple undo.
- Expiration of temporariry down nodes.
Requirements
- Python 2.4 or later
- ctypes http://python.net/crew/theller/ctypes/
- MySQLdb http://sourceforge.net/projects/mysql-python
- Zope 2.8 or later
Installation
1. In zope:
a. Copy neo directory to /path/to/your/zope/lib/python
b. Edit your zope.conf, remove the `zodb_zb` section that refers to
FileStorage, and replace it with :
%import neo
# Main FileStorage database
master_nodes 127.0.0.1:10010 127.0.0.1:10011
name main
mount-point /
c. Start zope
2. In a python script:
a. Set your PYTHONPATH in order to be able to import Zope lib:
$ export PYTHONPATH=/path/to/your/zope/lib/python:$PYTHONPATH
b. Just create the storage object and play with it:
from neo.client.NEOStorare import NEOStorage
s = NEOStorage(master_addr="127.0.0.1", master_port=10010, name="main")
...
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo.conf 0000664 0000000 0000000 00000001225 11227104631 0021071 0 ustar 00root root 0000000 0000000 # Default parameters.
[DEFAULT]
# The cluster name
name: main
# The list of master nodes.
master_nodes: 127.0.0.1:10010 127.0.0.1:10011
# Partition table configuration
replicas: 1
partitions: 20
# The database credentials
user: neo
password: neo
# The type of connection among nodes
connector: SocketConnector
# Admin node
[admin]
server: 127.0.0.1:5555
# Master nodes
[master1]
server: 127.0.0.1:10010
[master2]
server: 127.0.0.1:10011
# Storage nodes
[storage1]
database: neo1
server: 127.0.0.1:10020
[storage2]
database: neo2
server: 127.0.0.1:10021
[storage3]
database: neo3
server: 127.0.0.1:10022
[storage4]
database: neo4
server: 127.0.0.1:10023
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/ 0000775 0000000 0000000 00000000000 11227104631 0020222 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/__init__.py 0000664 0000000 0000000 00000000315 11227104631 0022332 0 ustar 00root root 0000000 0000000 DEFAULT_FORMAT = ' [%(module)14s:%(lineno)3d] %(levelname)8s: %(message)s'
def buildFormatString(app_name):
app_name = '%-8s' % app_name.upper()
return '%(asctime)s - ' + app_name + DEFAULT_FORMAT
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/admin/ 0000775 0000000 0000000 00000000000 11227104631 0021312 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/admin/__init__.py 0000664 0000000 0000000 00000000000 11227104631 0023411 0 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/admin/app.py 0000664 0000000 0000000 00000014561 11227104631 0022453 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from time import time
from neo.config import ConfigurationManager
from neo.protocol import INVALID_UUID, INVALID_PTID, MASTER_NODE_TYPE
from neo.node import NodeManager, MasterNode
from neo.event import EventManager
from neo.connection import ListeningConnection, ClientConnection
from neo.exception import PrimaryFailure
from neo.admin.handler import MasterMonitoringEventHandler, AdminEventHandler, \
MasterEventHandler, MasterRequestEventHandler
from neo.connector import getConnectorHandler, ConnectorConnectionClosedException
from neo.bootstrap import BootstrapManager
from neo.pt import PartitionTable
from neo import protocol
class Dispatcher:
"""Dispatcher use to redirect master request to handler"""
def __init__(self):
# associate conn/message_id to dispatch
# message to connection
self.message_table = {}
def register(self, msg_id, conn, kw=None):
self.message_table[msg_id] = conn, kw
def retrieve(self, msg_id):
return self.message_table.pop(msg_id, None)
def registered(self, msg_id):
return self.message_table.has_key(msg_id)
class Application(object):
"""The storage node application."""
def __init__(self, file, section):
config = ConfigurationManager(file, section)
self.name = config.getName()
logging.debug('the name is %s', self.name)
self.connector_handler = getConnectorHandler(config.getConnector())
self.server = config.getServer()
logging.debug('IP address is %s, port is %d', *(self.server))
self.master_node_list = config.getMasterNodeList()
logging.debug('master nodes are %s', self.master_node_list)
# Internal attributes.
self.em = EventManager()
self.nm = NodeManager()
# The partition table is initialized after getting the number of
# partitions.
self.pt = None
self.uuid = INVALID_UUID
self.primary_master_node = None
self.ptid = INVALID_PTID
self.monitoring_handler = MasterMonitoringEventHandler(self)
self.request_handler = MasterRequestEventHandler(self)
self.dispatcher = Dispatcher()
self.cluster_state = None
def run(self):
"""Make sure that the status is sane and start a loop."""
if len(self.name) == 0:
raise RuntimeError, 'cluster name must be non-empty'
for server in self.master_node_list:
self.nm.add(MasterNode(server = server))
# Make a listening port.
handler = AdminEventHandler(self)
ListeningConnection(self.em, handler, addr = self.server,
connector_handler = self.connector_handler)
# Connect to a primary master node, verify data, and
# start the operation. This cycle will be executed permentnly,
# until the user explicitly requests a shutdown.
while 1:
self.connectToPrimaryMaster()
try:
while 1:
self.em.poll(1)
except PrimaryFailure:
logging.error('primary master is down')
# do not trust any longer our informations
self.pt.clear()
self.nm.clear(filter = lambda node: node.getNodeType() != MASTER_NODE_TYPE)
def connectToPrimaryMaster(self):
"""Find a primary master node, and connect to it.
If a primary master node is not elected or ready, repeat
the attempt of a connection periodically.
Note that I do not accept any connection from non-master nodes
at this stage."""
# First of all, make sure that I have no connection.
for conn in self.em.getConnectionList():
if not conn.isListeningConnection():
conn.close()
# search, find, connect and identify to the primary master
bootstrap = BootstrapManager(self, self.name, protocol.ADMIN_NODE_TYPE,
self.uuid, self.server)
data = bootstrap.getPrimaryConnection(self.connector_handler)
(node, conn, uuid, num_partitions, num_replicas) = data
self.master_node = node
self.master_conn = conn
self.uuid = uuid
if self.pt is None:
self.pt = PartitionTable(num_partitions, num_replicas)
elif self.pt.getPartitions() != num_partitions:
raise RuntimeError('the number of partitions is inconsistent')
elif self.pt.getReplicas() != num_replicas:
raise RuntimeError('the number of replicas is inconsistent')
# passive handler
self.master_conn.setHandler(MasterEventHandler(self))
self.master_conn.ask(protocol.askNodeInformation())
self.master_conn.ask(protocol.askPartitionTable([]))
def sendPartitionTable(self, conn, min_offset, max_offset, uuid, msg_id):
# we have a pt
self.pt.log()
row_list = []
if max_offset == 0:
max_offset = self.pt.getPartitions()
try:
for offset in xrange(min_offset, max_offset):
row = []
try:
for cell in self.pt.getCellList(offset):
if uuid != INVALID_UUID and cell.getUUID() != uuid:
continue
else:
row.append((cell.getUUID(), cell.getState()))
except TypeError:
pass
row_list.append((offset, row))
except IndexError:
p = protocol.protocolError('invalid partition table offset')
conn.notify(p)
return
p = protocol.answerPartitionList(self.ptid, row_list)
conn.notify(p, msg_id)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/admin/handler.py 0000664 0000000 0000000 00000030200 11227104631 0023274 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.handler import EventHandler
from neo.protocol import INVALID_UUID, \
MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, \
ADMIN_NODE_TYPE, TEMPORARILY_DOWN_STATE
from neo.node import MasterNode, StorageNode, ClientNode, AdminNode
from neo import protocol
from neo.protocol import UnexpectedPacketError
from neo.pt import PartitionTable
from neo.exception import PrimaryFailure
from neo.util import dump
class AdminEventHandler(EventHandler):
"""This class deals with events for administrating cluster."""
def connectionAccepted(self, conn, s, addr):
"""Called when a connection is accepted."""
# we only accept connection from command tool
EventHandler.connectionAccepted(self, conn, s, addr)
def handleAskPartitionList(self, conn, packet, min_offset, max_offset, uuid):
logging.info("ask partition list from %s to %s for %s" %(min_offset, max_offset, dump(uuid)))
app = self.app
# check we have one pt otherwise ask it to PMN
if len(app.pt.getNodeList()) == 0:
master_conn = self.app.master_conn
p = protocol.askPartitionTable([])
msg_id = master_conn.ask(p)
app.dispatcher.register(msg_id, conn, {'min_offset' : min_offset,
'max_offset' : max_offset,
'uuid' : uuid,
'msg_id' : packet.getId()})
else:
app.sendPartitionTable(conn, min_offset, max_offset, uuid, packet.getId())
def handleAskNodeList(self, conn, packet, node_type):
logging.info("ask node list for %s" %(node_type))
def node_filter(n):
return n.getNodeType() is node_type
node_list = self.app.nm.getNodeList(node_filter)
node_information_list = []
for node in node_list:
try:
ip, port = node.getServer()
except TypeError:
ip = "0.0.0.0"
port = 0
node_information_list.append((node.getNodeType(), ip, port, node.getUUID(), node.getState()))
p = protocol.answerNodeList(node_information_list)
conn.answer(p, packet)
def handleSetNodeState(self, conn, packet, uuid, state, modify_partition_table):
logging.info("set node state for %s-%s" %(dump(uuid), state))
node = self.app.nm.getNodeByUUID(uuid)
if node is None:
p = protocol.protocolError('invalid uuid')
conn.notify(p)
return
if node.getState() == state and modify_partition_table is False:
# no change
p = protocol.answerNodeState(node.getUUID(), node.getState())
conn.answer(p, packet)
return
# forward to primary master node
master_conn = self.app.master_conn
p = protocol.setNodeState(uuid, state, modify_partition_table)
msg_id = master_conn.ask(p)
self.app.dispatcher.register(msg_id, conn, {'msg_id' : packet.getId()})
def handleSetClusterState(self, conn, packet, name, state):
self.checkClusterName(name)
# forward to primary
master_conn = self.app.master_conn
p = protocol.setClusterState(name, state)
msg_id = master_conn.ask(p)
self.app.dispatcher.register(msg_id, conn, {'msg_id' : packet.getId()})
def handleAddPendingNodes(self, conn, packet, uuid_list):
uuids = ', '.join([dump(uuid) for uuid in uuid_list])
logging.info('Add nodes %s' % uuids)
uuid = conn.getUUID()
node = self.app.nm.getNodeByUUID(uuid)
# forward the request to primary
master_conn = self.app.master_conn
msg_id = master_conn.ask(protocol.addPendingNodes(uuid_list))
self.app.dispatcher.register(msg_id, conn, {'msg_id' : packet.getId()})
def handleAskClusterState(self, conn, packet):
if self.app.cluster_state is None:
# required it from PMN first
msg_id = self.app.master_conn.ask(protocol.askClusterState())
self.app.dispatcher.register(msg_id, conn, {'msg_id' : packet.getId()})
return
conn.answer(protocol.answerClusterState(self.app.cluster_state), packet)
class MasterEventHandler(EventHandler):
""" This class is just used to dispacth message to right handler"""
def _connectionLost(self, conn):
raise PrimaryFailure
def connectionFailed(self, conn):
self._connectionLost(conn)
EventHandler.connectionFailed(self, conn)
def timeoutExpired(self, conn):
self._connectionLost(conn)
EventHandler.timeoutExpired(self, conn)
def connectionClosed(self, conn):
self._connectionLost(conn)
EventHandler.connectionClosed(self, conn)
def peerBroken(self, conn):
self._connectionLost(conn)
EventHandler.peerBroken(self, conn)
def dispatch(self, conn, packet):
if self.app.dispatcher.registered(packet.getId()):
# answer to a request
self.app.request_handler.dispatch(conn, packet)
else:
# monitoring phase
self.app.monitoring_handler.dispatch(conn, packet)
class MasterBaseEventHandler(EventHandler):
""" This is the base class for connection to primary master node"""
def handleNotifyClusterInformation(self, con, packet, cluster_state):
self.app.cluster_state = cluster_state
def handleNotifyNodeInformation(self, conn, packet, node_list):
uuid = conn.getUUID()
app = self.app
nm = app.nm
node = nm.getNodeByUUID(uuid)
# This must be sent only by a primary master node.
# Note that this may be sent before I know that it is
# a primary master node.
if node.getNodeType() != MASTER_NODE_TYPE:
logging.warn('ignoring notify node information from %s',
dump(uuid))
return
for node_type, ip_address, port, uuid, state in node_list:
# Register/update nodes.
addr = (ip_address, port)
# Try to retrieve it from nm
n = None
if uuid != INVALID_UUID:
n = nm.getNodeByUUID(uuid)
if n is None:
n = nm.getNodeByServer(addr)
if n is not None and uuid != INVALID_UUID:
# node only exists by address, remove it
nm.remove(n)
n = None
elif n.getServer() != addr:
# same uuid but different address, remove it
nm.remove(n)
n = None
if node_type == MASTER_NODE_TYPE:
if n is None:
n = MasterNode(server = addr)
nm.add(n)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if n.getUUID() is None:
n.setUUID(uuid)
else:
n.setUUID(INVALID_UUID)
elif node_type in (STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, ADMIN_NODE_TYPE):
if uuid == INVALID_UUID:
# No interest.
continue
if n is None:
if node_type == STORAGE_NODE_TYPE:
n = StorageNode(server = addr, uuid = uuid)
elif node_type == CLIENT_NODE_TYPE:
n = ClientNode(server = addr, uuid = uuid)
elif node_type == ADMIN_NODE_TYPE:
n = AdminNode(server = addr, uuid = uuid)
nm.add(n)
else:
logging.warning("unknown node type %s" %(node_type))
continue
n.setState(state)
self.app.notified = True
class MasterRequestEventHandler(MasterBaseEventHandler):
""" This class handle all answer from primary master node"""
def handleAnswerClusterState(self, conn, packet, state):
logging.info("handleAnswerClusterState for a conn")
self.app.cluster_state = state
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
client_conn.notify(protocol.answerClusterState(state), kw['msg_id'])
def handleAnswerNewNodes(self, conn, packet, uuid_list):
logging.info("handleAnswerNewNodes for a conn")
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
client_conn.notify(protocol.answerNewNodes(uuid_list), kw['msg_id'])
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
logging.info("handleAnswerPartitionTable for a conn")
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
# sent client the partition table
self.app.sendPartitionTable(client_conn, **kw)
def handleAnswerNodeState(self, conn, packet, uuid, state):
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
p = protocol.answerNodeState(uuid, state)
client_conn.notify(p, kw['msg_id'])
def handleNoError(self, conn, packet, msg):
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
p = protocol.noError(msg)
client_conn.notify(p, kw['msg_id'])
def handleProtocolError(self, conn, packet, msg):
client_conn, kw = self.app.dispatcher.retrieve(packet.getId())
p = protocol.protocolError(msg)
client_conn.notify(p, kw['msg_id'])
class MasterMonitoringEventHandler(MasterBaseEventHandler):
"""This class deals with events for monitoring cluster."""
def handleAnswerNodeInformation(self, conn, packet, node_list):
logging.info("handleAnswerNodeInformation")
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
logging.info("handleAnswerPartitionTable")
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
app = self.app
nm = app.nm
pt = app.pt
uuid = conn.getUUID()
node = app.nm.getNodeByUUID(uuid)
# This must be sent only by primary master node
if node.getNodeType() != MASTER_NODE_TYPE \
or app.primary_master_node is None \
or app.primary_master_node.getUUID() != uuid:
return
if app.ptid >= ptid:
# Ignore this packet.
return
app.ptid = ptid
for offset, uuid, state in cell_list:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
pt.log()
def handleSendPartitionTable(self, conn, packet, ptid, row_list):
uuid = conn.getUUID()
app = self.app
nm = app.nm
pt = app.pt
node = app.nm.getNodeByUUID(uuid)
# This must be sent only by primary master node
if node.getNodeType() != MASTER_NODE_TYPE:
return
if app.ptid != ptid:
app.ptid = ptid
pt.clear()
for offset, row in row_list:
for uuid, state in row:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
pt.log()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/bootstrap.py 0000664 0000000 0000000 00000011434 11227104631 0022614 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from time import sleep
from neo.handler import EventHandler
from neo.node import MasterNode
from neo import protocol
from neo.pt import PartitionTable
from neo.util import dump
from neo.connection import ClientConnection
NO_SERVER = ('0.0.0.0', 0)
class BootstrapManager(EventHandler):
"""
Manage the bootstrap stage, lookup for the primary master then connect to it
"""
def __init__(self, app, name, node_type, uuid=protocol.INVALID_UUID, server=NO_SERVER):
EventHandler.__init__(self, app)
self.primary = None
self.server = server
self.node_type = node_type
self.uuid = uuid
self.name = name
def connectionCompleted(self, conn):
EventHandler.connectionCompleted(self, conn)
conn.ask(protocol.askPrimaryMaster())
def connectionFailed(self, conn):
EventHandler.connectionFailed(self, conn)
self.current = None
def connectionClosed(self, conn):
EventHandler.connectionClosed(self, conn)
self.current = None
def timeoutExpired(self, conn):
EventHandler.timeoutExpired(self, conn)
self.current = None
def peerBroken(self, conn):
EventHandler.peerBroken(self, conn)
self.current = None
def handleNotReady(self, conn, packet, message):
# master are still electing on of them
self.current = None
conn.close()
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid, known_master_list):
nm = self.app.nm
# Register new master nodes.
# TODO: this job should be done by the node manager
for ip_address, port, uuid in known_master_list:
addr = (ip_address, port)
node = nm.getNodeByServer(addr)
if node is None:
node = MasterNode(server=addr)
nm.add(node)
node.setUUID(uuid)
self.primary = nm.getNodeByUUID(primary_uuid)
if self.primary is None or self.current is not self.primary:
# three cases here:
# - something goes wrong (unknown UUID)
# - this master doesn't know who's the primary
# - got the primary's uuid, so cut here
self.current = None
conn.close()
return
logging.info('connected to a primary master node')
conn.ask(protocol.requestNodeIdentification(self.node_type,
self.uuid, self.server[0], self.server[1], self.name))
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, num_partitions, num_replicas, your_uuid):
self.num_partitions = num_partitions
self.num_replicas = num_replicas
if self.uuid != your_uuid:
# got an uuid from the primary master
self.uuid = your_uuid
logging.info('Got a new UUID : %s' % dump(self.uuid))
conn.setUUID(uuid)
def getPrimaryConnection(self, connector_handler):
logging.info('connecting to a primary master node')
em, nm = self.app.em, self.app.nm
index = 0
self.current = nm.getMasterNodeList()[0]
conn = None
# retry until identified to the primary
while self.primary is None or conn.getUUID() != self.primary.getUUID():
if self.current is None:
# conn closed
conn = None
if self.current is None:
# select a master
master_list = nm.getMasterNodeList()
index = (index + 1) % len(master_list)
self.current = master_list[index]
if index == 0:
# tried all known masters, sleep a bit
sleep(1)
if conn is None:
# open the connection
addr = self.current.getServer()
conn = ClientConnection(em, self, addr, connector_handler)
# still processing
em.poll(1)
node = nm.getNodeByUUID(conn.getUUID())
return (node, conn, self.uuid, self.num_partitions, self.num_replicas)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/ 0000775 0000000 0000000 00000000000 11227104631 0021500 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/Storage.py 0000664 0000000 0000000 00000013732 11227104631 0023464 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from ZODB import BaseStorage, ConflictResolution, POSException
import logging
from neo.client.app import Application
from neo.client.exception import NEOStorageConflictError, NEOStorageNotFoundError
class Storage(BaseStorage.BaseStorage,
ConflictResolution.ConflictResolvingStorage):
"""Wrapper class for neoclient."""
__name__ = 'NEOStorage'
def __init__(self, master_nodes, name, connector, read_only=False, **kw):
BaseStorage.BaseStorage.__init__(self, name)
self._is_read_only = read_only
self.app = Application(master_nodes, name, connector)
def load(self, oid, version=None):
try:
return self.app.load(oid=oid)
except NEOStorageNotFoundError:
raise POSException.POSKeyError(oid)
def close(self):
return self.app.close()
def cleanup(self):
# Used in unit tests to remove local database files.
# We have no such thing, so make this method a no-op.
pass
def lastSerial(self):
# does not seem to be used
raise NotImplementedError
def lastTransaction(self):
# does not seem to be used
raise NotImplementedError
def new_oid(self):
if self._is_read_only:
raise POSException.ReadOnlyError()
return self.app.new_oid()
def tpc_begin(self, transaction, tid=None, status=' '):
if self._is_read_only:
raise POSException.ReadOnlyError()
return self.app.tpc_begin(transaction=transaction, tid=tid, status=status)
def tpc_vote(self, transaction):
if self._is_read_only:
raise POSException.ReadOnlyError()
return self.app.tpc_vote(transaction=transaction)
def tpc_abort(self, transaction):
if self._is_read_only:
raise POSException.ReadOnlyError()
return self.app.tpc_abort(transaction=transaction)
def tpc_finish(self, transaction, f=None):
return self.app.tpc_finish(transaction=transaction, f=f)
def store(self, oid, serial, data, version, transaction):
app = self.app
if self._is_read_only:
raise POSException.ReadOnlyError()
try:
return app.store(oid = oid, serial = serial,
data = data, version = version,
transaction = transaction)
except NEOStorageConflictError:
conflict_serial = app.getConflictSerial()
tid = app.getTID()
if conflict_serial <= tid:
# Try to resolve conflict only if conflicting serial is older
# than the current transaction ID
new_data = self.tryToResolveConflict(oid,
conflict_serial,
serial, data)
if new_data is not None:
# Try again after conflict resolution
self.store(oid, conflict_serial,
new_data, version, transaction)
return ConflictResolution.ResolvedSerial
raise POSException.ConflictError(oid=oid,
serials=(tid,
serial),data=data)
def _clear_temp(self):
raise NotImplementedError
def getSerial(self, oid):
try:
return self.app.getSerial(oid = oid)
except NEOStorageNotFoundError:
raise POSException.POSKeyError(oid)
# mutliple revisions
def loadSerial(self, oid, serial):
try:
return self.app.loadSerial(oid=oid, serial=serial)
except NEOStorageNotFoundError:
raise POSException.POSKeyError (oid, serial)
def loadBefore(self, oid, tid):
try:
return self.app.loadBefore(oid=oid, tid=tid)
except NEOStorageNotFoundError:
raise POSException.POSKeyError (oid, tid)
def iterator(self, start=None, stop=None):
raise NotImplementedError
# undo
def undo(self, transaction_id, txn):
if self._is_read_only:
raise POSException.ReadOnlyError()
try:
return self.app.undo(transaction_id = transaction_id,
txn = txn, wrapper = self)
except NEOStorageConflictError:
raise POSException.ConflictError
def undoLog(self, first, last, filter):
if self._is_read_only:
raise POSException.ReadOnlyError()
return self.app.undoLog(first, last, filter)
def supportsUndo(self):
return 0
def abortVersion(self, src, transaction):
return '', []
def commitVersion(self, src, dest, transaction):
return '', []
def set_max_oid(self, possible_new_max_oid):
# seems to be only use by FileStorage
raise NotImplementedError
def copyTransactionsFrom(self, other, verbose=0):
raise NotImplementedError
def __len__(self):
# XXX bogus but how to implement this?
return 0
def registerDB(self, db, limit):
self.app.registerDB(db, limit)
def history(self, oid, version, length=1, filter=None):
return self.app.history(oid, version, length, filter)
def sync(self):
self.app.sync()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/__init__.py 0000664 0000000 0000000 00000000000 11227104631 0023577 0 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/app.py 0000664 0000000 0000000 00000117537 11227104631 0022650 0 ustar 00root root 0000000 0000000 # Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from thread import get_ident
from cPickle import dumps
from zlib import compress, decompress
from Queue import Queue, Empty
from random import shuffle
from time import sleep
from neo.client.mq import MQ
from neo.node import NodeManager, MasterNode, StorageNode
from neo.connection import MTClientConnection
from neo import protocol
from neo.protocol import INVALID_UUID, INVALID_TID, INVALID_PARTITION, \
INVALID_PTID, CLIENT_NODE_TYPE, INVALID_SERIAL, \
DOWN_STATE, HIDDEN_STATE
from neo.client.handlers.master import PrimaryBootstrapHandler, \
PrimaryNotificationsHandler, PrimaryAnswersHandler
from neo.client.handlers.storage import StorageBootstrapHandler, \
StorageAnswersHandler, StorageEventHandler
from neo.client.exception import NEOStorageError, NEOStorageConflictError, \
NEOStorageNotFoundError
from neo.exception import NeoException
from neo.util import makeChecksum, dump
from neo.connector import getConnectorHandler
from neo.client.dispatcher import Dispatcher
from neo.client.poll import ThreadedPoll
from neo.event import EventManager
from neo.locking import RLock, Lock
from ZODB.POSException import UndoError, StorageTransactionError, ConflictError
class ConnectionClosed(Exception): pass
class ConnectionPool(object):
"""This class manages a pool of connections to storage nodes."""
def __init__(self, app, max_pool_size = 25):
self.app = app
self.max_pool_size = max_pool_size
self.connection_dict = {}
# Define a lock in order to create one connection to
# a storage node at a time to avoid multiple connections
# to the same node.
l = RLock()
self.connection_lock_acquire = l.acquire
self.connection_lock_release = l.release
def _initNodeConnection(self, node):
"""Init a connection to a given storage node."""
addr = node.getServer()
if addr is None:
return None
app = self.app
# Loop until a connection is obtained.
while True:
logging.info('trying to connect to %s - %s', node, node.getState())
app.setNodeReady()
conn = MTClientConnection(self.app.local_var, app.em, app.storage_event_handler,
addr, connector_handler=app.connector_handler, dispatcher=app.dispatcher)
conn.lock()
try:
if conn.getConnector() is None:
# This happens, if a connection could not be established.
logging.error('Connection to storage node %s failed', node)
return None
p = protocol.requestNodeIdentification(CLIENT_NODE_TYPE,
app.uuid, '0.0.0.0', 0, app.name)
msg_id = conn.ask(p)
finally:
conn.unlock()
try:
app._waitMessage(conn, msg_id, handler=app.storage_bootstrap_handler)
except ConnectionClosed:
logging.error('Connection to storage node %s failed', node)
return None
if app.isNodeReady():
logging.info('connected to storage node %s', node)
return conn
else:
logging.info('Storage node %s not ready', node)
return None
def _dropConnections(self):
"""Drop connections."""
for node_uuid, conn in self.connection_dict.items():
# Drop first connection which looks not used
conn.lock()
try:
if not conn.pending() and \
not self.app.dispatcher.registered(conn):
del self.connection_dict[conn.getUUID()]
conn.close()
logging.debug('_dropConnections : connection to storage node %s:%d closed',
*(conn.getAddress()))
if len(self.connection_dict) <= self.max_pool_size:
break
finally:
conn.unlock()
def _createNodeConnection(self, node):
"""Create a connection to a given storage node."""
if len(self.connection_dict) > self.max_pool_size:
# must drop some unused connections
self._dropConnections()
self.connection_lock_release()
try:
conn = self._initNodeConnection(node)
finally:
self.connection_lock_acquire()
if conn is None:
return None
# add node to node manager
if self.app.nm.getNodeByServer(node.getServer()) is None:
n = StorageNode(node.getServer())
self.app.nm.add(n)
self.connection_dict[node.getUUID()] = conn
conn.lock()
return conn
def getConnForCell(self, cell):
return self.getConnForNode(cell.getNode())
def getConnForNode(self, node):
"""Return a locked connection object to a given node
If no connection exists, create a new one"""
if node.getState() in (DOWN_STATE, HIDDEN_STATE):
return None
uuid = node.getUUID()
self.connection_lock_acquire()
try:
try:
conn = self.connection_dict[uuid]
# Already connected to node
conn.lock()
return conn
except KeyError:
# Create new connection to node
return self._createNodeConnection(node)
finally:
self.connection_lock_release()
def removeConnection(self, node):
"""Explicitly remove connection when a node is broken."""
self.connection_lock_acquire()
try:
try:
del self.connection_dict[node.getUUID()]
except KeyError:
pass
finally:
self.connection_lock_release()
class ThreadContext(object):
_threads_dict = {}
def __getThreadData(self):
id = get_ident()
try:
result = self._threads_dict[id]
except KeyError:
self.clear(id)
result = self._threads_dict[id]
return result
def __getattr__(self, name):
thread_data = self.__getThreadData()
try:
return thread_data[name]
except KeyError:
raise AttributeError, name
def __setattr__(self, name, value):
thread_data = self.__getThreadData()
thread_data[name] = value
def clear(self, id=None):
if id is None:
id = get_ident()
self._threads_dict[id] = {
'tid': None,
'txn': None,
'data_dict': {},
'object_stored': 0,
'txn_voted': False,
'txn_finished': False,
'queue': Queue(5),
}
class Application(object):
"""The client node application."""
def __init__(self, master_nodes, name, connector, **kw):
# XXX: use a configuration entry
from neo import buildFormatString
format = buildFormatString('CLIENT')
logging.basicConfig(level=logging.DEBUG, format=format)
em = EventManager()
# Start polling thread
self.poll_thread = ThreadedPoll(em)
# Internal Attributes common to all thread
self.name = name
self.em = em
self.connector_handler = getConnectorHandler(connector)
self.dispatcher = Dispatcher()
self.nm = NodeManager()
self.cp = ConnectionPool(self)
self.pt = None
self.master_conn = None
self.primary_master_node = None
self.trying_master_node = None
# XXX: this code duplicates neo.config.ConfigurationManager.getMasterNodeList
logging.debug('master node address are %s' % (master_nodes,))
self.master_node_list = master_node_list = []
for node in master_nodes.split():
if not node:
continue
if ':' in node:
ip_address, port = node.split(':')
port = int(port)
else:
ip_address = node
port = 10100 # XXX: default_master_port
server = (ip_address, port)
master_node_list.append(server)
self.nm.add(MasterNode(server=server))
# no self-assigned UUID, primary master will supply us one
self.uuid = INVALID_UUID
self.mq_cache = MQ()
self.new_oid_list = []
self.ptid = INVALID_PTID
self.storage_event_handler = StorageEventHandler(self, self.dispatcher)
self.storage_bootstrap_handler = StorageBootstrapHandler(self)
self.storage_handler = StorageAnswersHandler(self)
self.primary_handler = PrimaryAnswersHandler(self)
self.primary_bootstrap_handler = PrimaryBootstrapHandler(self)
self.notifications_handler = PrimaryNotificationsHandler(self, self.dispatcher)
# Internal attribute distinct between thread
self.local_var = ThreadContext()
# Lock definition :
# _load_lock is used to make loading and storing atomic
lock = Lock()
self._load_lock_acquire = lock.acquire
self._load_lock_release = lock.release
# _oid_lock is used in order to not call multiple oid
# generation at the same time
lock = Lock()
self._oid_lock_acquire = lock.acquire
self._oid_lock_release = lock.release
lock = Lock()
# _cache_lock is used for the client cache
self._cache_lock_acquire = lock.acquire
self._cache_lock_release = lock.release
lock = Lock()
# _connecting_to_master_node is used to prevent simultaneous master
# node connection attemps
self._connecting_to_master_node_acquire = lock.acquire
self._connecting_to_master_node_release = lock.release
# _nm ensure exclusive access to the node manager
lock = Lock()
self._nm_acquire = lock.acquire
self._nm_release = lock.release
def notifyDeadNode(self, conn):
""" Notify a storage failure to the primary master """
s_node = self.nm.getNodeByServer(conn.getAddress())
if s_node is None or s_node.getNodeType() != protocol.STORAGE_NODE_TYPE:
return
s_uuid = s_node.getUUID()
ip_address, port = s_node.getServer()
m_conn = self._getMasterConnection()
m_conn.lock()
try:
node_list = [(protocol.STORAGE_NODE_TYPE, ip_address, port, s_uuid, s_node.getState())]
m_conn.notify(protocol.notifyNodeInformation(node_list))
finally:
m_conn.unlock()
def _waitMessage(self, target_conn = None, msg_id = None, handler=None):
"""Wait for a message returned by the dispatcher in queues."""
local_queue = self.local_var.queue
while 1:
if msg_id is None:
try:
conn, packet = local_queue.get_nowait()
except Empty:
break
else:
conn, packet = local_queue.get()
# check fake packet
if packet is None:
if conn.getUUID() == target_conn.getUUID():
raise ConnectionClosed
else:
continue
# Guess the handler to use based on the type of node on the
# connection
if handler is None:
node = self.nm.getNodeByServer(conn.getAddress())
if node is None:
raise ValueError, 'Expecting an answer from a node ' \
'which type is not known... Is this right ?'
else:
node_type = node.getType()
if node_type == protocol.STORAGE_NODE_TYPE:
handler = self.storage_handler
elif node_type == protocol.MASTER_NODE_TYPE:
handler = self.primary_handler
else:
raise ValueError, 'Unknown node type: %r' % (
node_type, )
handler.dispatch(conn, packet)
if target_conn is conn and msg_id == packet.getId() \
and packet.getType() & 0x8000:
break
def _askStorage(self, conn, packet, timeout=5, additional_timeout=30):
""" Send a request to a storage node and process it's answer """
try:
msg_id = conn.ask(packet, timeout, additional_timeout)
finally:
# assume that the connection was already locked
conn.unlock()
self._waitMessage(conn, msg_id, self.storage_handler)
def _askPrimary(self, packet, timeout=5, additional_timeout=30):
""" Send a request to the primary master and process it's answer """
conn = self._getMasterConnection()
conn.lock()
try:
msg_id = conn.ask(packet, timeout, additional_timeout)
finally:
conn.unlock()
self._waitMessage(conn, msg_id, self.primary_handler)
def _getMasterConnection(self):
""" Connect to the primary master node on demand """
# acquire the lock to allow only one thread to connect to the primary
lock = self._connecting_to_master_node_acquire()
try:
if self.master_conn is None:
self.master_conn = self._connectToPrimaryMasterNode()
return self.master_conn
finally:
self._connecting_to_master_node_release()
def _getPartitionTable(self):
""" Return the partition table manager, reconnect the PMN if needed """
# this ensure the master connection is established and the partition
# table is up to date.
self._getMasterConnection()
return self.pt
def _getCellListForID(self, id, readable=False, writable=False):
""" Return the cells available for the specified (O|T)ID """
pt = self._getPartitionTable()
return pt.getCellListForID(id, readable, writable)
def _connectToPrimaryMasterNode(self):
logging.debug('connecting to primary master...')
ready = False
nm = self.nm
while not ready:
# Get network connection to primary master
index = 0
connected = False
while not connected:
if self.primary_master_node is not None:
# If I know a primary master node, pinpoint it.
self.trying_master_node = self.primary_master_node
self.primary_master_node = None
else:
# Otherwise, check one by one.
master_list = nm.getMasterNodeList()
try:
self.trying_master_node = master_list[index]
except IndexError:
index = 0
self.trying_master_node = master_list[0]
index += 1
# Connect to master
conn = MTClientConnection(self.local_var, self.em, self.notifications_handler,
addr=self.trying_master_node.getServer(),
connector_handler=self.connector_handler,
dispatcher=self.dispatcher)
# Query for primary master node
conn.lock()
try:
if conn.getConnector() is None:
# This happens, if a connection could not be established.
logging.error('Connection to master node %s failed',
self.trying_master_node)
continue
msg_id = conn.ask(protocol.askPrimaryMaster())
finally:
conn.unlock()
try:
self._waitMessage(conn, msg_id, handler=self.primary_bootstrap_handler)
except ConnectionClosed:
continue
# If we reached the primary master node, mark as connected
connected = self.primary_master_node is not None \
and self.primary_master_node is self.trying_master_node
logging.info('connected to a primary master node')
# Identify to primary master and request initial data
while conn.getUUID() is None:
conn.lock()
try:
if conn.getConnector() is None:
logging.error('Connection to master node %s lost',
self.trying_master_node)
self.primary_master_node = None
break
p = protocol.requestNodeIdentification(CLIENT_NODE_TYPE,
self.uuid, '0.0.0.0', 0, self.name)
msg_id = conn.ask(p)
finally:
conn.unlock()
try:
self._waitMessage(conn, msg_id, handler=self.primary_bootstrap_handler)
except ConnectionClosed:
self.primary_master_node = None
break
if conn.getUUID() is None:
# Node identification was refused by master.
# Sleep a bit an retry.
# XXX: This should be replaced by:
# - queuing requestNodeIdentification at master side
# - sending the acceptance from master when it becomes
# ready
# Thus removing the need to:
# - needlessly bother the primary master every 5 seconds
# (...per client)
# - have a sleep in the code (yuck !)
sleep(5)
if self.uuid != INVALID_UUID:
# TODO: pipeline those 2 requests
# This is currently impossible because _waitMessage can only
# wait on one message at a time
conn.lock()
try:
msg_id = conn.ask(protocol.askPartitionTable([]))
finally:
conn.unlock()
self._waitMessage(conn, msg_id, handler=self.primary_bootstrap_handler)
conn.lock()
try:
msg_id = conn.ask(protocol.askNodeInformation())
finally:
conn.unlock()
self._waitMessage(conn, msg_id, handler=self.primary_bootstrap_handler)
ready = self.uuid != INVALID_UUID and self.pt is not None \
and self.pt.operational()
logging.info("connected to primary master node %s" % self.primary_master_node)
return conn
def registerDB(self, db, limit):
self._db = db
def getDB(self):
return self._db
def new_oid(self):
"""Get a new OID."""
self._oid_lock_acquire()
try:
if len(self.new_oid_list) == 0:
# Get new oid list from master node
# we manage a list of oid here to prevent
# from asking too many time new oid one by one
# from master node
self._askPrimary(protocol.askNewOIDs(100))
if len(self.new_oid_list) <= 0:
raise NEOStorageError('new_oid failed')
return self.new_oid_list.pop()
finally:
self._oid_lock_release()
def getSerial(self, oid):
# Try in cache first
self._cache_lock_acquire()
try:
if oid in self.mq_cache:
return self.mq_cache[oid][0]
finally:
self._cache_lock_release()
# history return serial, so use it
hist = self.history(oid, length = 1, object_only = 1)
if len(hist) == 0:
raise NEOStorageNotFoundError()
if hist[0] != oid:
raise NEOStorageError('getSerial failed')
return hist[1][0][0]
def _load(self, oid, serial = INVALID_TID, tid = INVALID_TID, cache = 0):
"""Internal method which manage load ,loadSerial and loadBefore."""
cell_list = self._getCellListForID(oid, readable=True)
if len(cell_list) == 0:
# No cells available, so why are we running ?
logging.error('oid %s not found because no storage is available for it', dump(oid))
raise NEOStorageNotFoundError()
shuffle(cell_list)
self.local_var.asked_object = 0
for cell in cell_list:
logging.debug('trying to load %s from %s',
dump(oid), dump(cell.getUUID()))
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
try:
self._askStorage(conn, protocol.askObject(oid, serial, tid))
except ConnectionClosed:
continue
if self.local_var.asked_object == -1:
# OID not found
break
# Check data
noid, start_serial, end_serial, compression, checksum, data \
= self.local_var.asked_object
if noid != oid:
# Oops, try with next node
logging.error('got wrong oid %s instead of %s from node %s',
noid, dump(oid), cell.getServer())
self.local_var.asked_object = -1
continue
elif checksum != makeChecksum(data):
# Check checksum.
logging.error('wrong checksum from node %s for oid %s',
cell.getServer(), dump(oid))
self.local_var.asked_object = -1
continue
else:
# Everything looks alright.
break
if self.local_var.asked_object == 0:
# We didn't got any object from all storage node because of connection error
logging.warning('oid %s not found because of connection failure', dump(oid))
raise NEOStorageNotFoundError()
if self.local_var.asked_object == -1:
# We didn't got any object from all storage node
logging.info('oid %s not found', dump(oid))
raise NEOStorageNotFoundError()
# Uncompress data
if compression:
data = decompress(data)
# Put in cache only when using load
if cache:
self._cache_lock_acquire()
try:
self.mq_cache[oid] = start_serial, data
finally:
self._cache_lock_release()
if end_serial == INVALID_SERIAL:
end_serial = None
return data, start_serial, end_serial
def load(self, oid, version=None):
"""Load an object for a given oid."""
# First try from cache
self._load_lock_acquire()
try:
self._cache_lock_acquire()
try:
if oid in self.mq_cache:
logging.debug('load oid %s is cached', dump(oid))
return self.mq_cache[oid][1], self.mq_cache[oid][0]
finally:
self._cache_lock_release()
# Otherwise get it from storage node
return self._load(oid, cache=1)[:2]
finally:
self._load_lock_release()
def loadSerial(self, oid, serial):
"""Load an object for a given oid and serial."""
# Do not try in cache as it manages only up-to-date object
logging.debug('loading %s at %s', dump(oid), dump(serial))
return self._load(oid, serial=serial)[0]
def loadBefore(self, oid, tid):
"""Load an object for a given oid before tid committed."""
# Do not try in cache as it manages only up-to-date object
if tid is None:
tid = INVALID_TID
logging.debug('loading %s before %s', dump(oid), dump(tid))
data, start, end = self._load(oid, tid=tid)
if end is None:
# No previous version
return None
else:
return data, start, end
def tpc_begin(self, transaction, tid=None, status=' '):
"""Begin a new transaction."""
# First get a transaction, only one is allowed at a time
if self.local_var.txn is transaction:
# We already begin the same transaction
return
# Get a new transaction id if necessary
if tid is None:
self.local_var.tid = None
self._askPrimary(protocol.askNewTID())
if self.local_var.tid is None:
raise NEOStorageError('tpc_begin failed')
else:
self.local_var.tid = tid
self.local_var.txn = transaction
def store(self, oid, serial, data, version, transaction):
"""Store object."""
if transaction is not self.local_var.txn:
raise StorageTransactionError(self, transaction)
if serial is None:
serial = INVALID_SERIAL
logging.debug('storing oid %s serial %s',
dump(oid), dump(serial))
# Find which storage node to use
cell_list = self._getCellListForID(oid, writable=True)
if len(cell_list) == 0:
# FIXME must wait for cluster to be ready
raise NEOStorageError
# Store data on each node
compressed_data = compress(data)
checksum = makeChecksum(compressed_data)
self.local_var.object_stored_counter = 0
for cell in cell_list:
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
self.local_var.object_stored = 0
p = protocol.askStoreObject(oid, serial, 1,
checksum, compressed_data, self.local_var.tid)
try:
self._askStorage(conn, p)
except ConnectionClosed:
continue
# Check we don't get any conflict
if self.local_var.object_stored[0] == -1:
if self.local_var.data_dict.has_key(oid):
# One storage already accept the object, is it normal ??
# remove from dict and raise ConflictError, don't care of
# previous node which already store data as it would be resent
# again if conflict is resolved or txn will be aborted
del self.local_var.data_dict[oid]
self.conflict_serial = self.local_var.object_stored[1]
raise NEOStorageConflictError
# increase counter so that we know if a node has stored the object or not
self.local_var.object_stored_counter += 1
if self.local_var.object_stored_counter == 0:
# no storage nodes were available
raise NEOStorageError('tpc_store failed')
# Store object in tmp cache
self.local_var.data_dict[oid] = data
return self.local_var.tid
def tpc_vote(self, transaction):
"""Store current transaction."""
if transaction is not self.local_var.txn:
raise StorageTransactionError(self, transaction)
user = transaction.user
desc = transaction.description
ext = dumps(transaction._extension)
oid_list = self.local_var.data_dict.keys()
# Store data on each node
pt = self._getPartitionTable()
cell_list = self._getCellListForID(self.local_var.tid, writable=True)
self.local_var.voted_counter = 0
for cell in cell_list:
logging.info("voting object %s %s" %(cell.getServer(), cell.getState()))
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
self.local_var.txn_voted = False
p = protocol.askStoreTransaction(self.local_var.tid,
user, desc, ext, oid_list)
try:
self._askStorage(conn, p)
except ConnectionClosed:
continue
if not self.isTransactionVoted():
raise NEOStorageError('tpc_vote failed')
self.local_var.voted_counter += 1
# check at least one storage node accepted
if self.local_var.voted_counter == 0:
raise NEOStorageError('tpc_vote failed')
def tpc_abort(self, transaction):
"""Abort current transaction."""
if transaction is not self.local_var.txn:
return
cell_set = set()
# select nodes where objects were stored
for oid in self.local_var.data_dict.iterkeys():
cell_set |= set(self._getCellListForID(oid, writable=True))
# select nodes where transaction was stored
cell_set |= set(self._getCellListForID(self.local_var.tid, writable=True))
# cancel transaction one all those nodes
for cell in cell_set:
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
try:
conn.notify(protocol.abortTransaction(self.local_var.tid))
finally:
conn.unlock()
# Abort the transaction in the primary master node.
conn = self._getMasterConnection()
conn.lock()
try:
conn.notify(protocol.abortTransaction(self.local_var.tid))
finally:
conn.unlock()
self.local_var.clear()
def tpc_finish(self, transaction, f=None):
"""Finish current transaction."""
if self.local_var.txn is not transaction:
return
self._load_lock_acquire()
try:
# Call function given by ZODB
if f is not None:
f(self.local_var.tid)
# Call finish on master
oid_list = self.local_var.data_dict.keys()
p = protocol.finishTransaction(oid_list, self.local_var.tid)
self._askPrimary(p)
if not self.isTransactionFinished():
raise NEOStorageError('tpc_finish failed')
# Update cache
self._cache_lock_acquire()
try:
for oid in self.local_var.data_dict.iterkeys():
data = self.local_var.data_dict[oid]
# Now serial is same as tid
self.mq_cache[oid] = self.local_var.tid, data
finally:
self._cache_lock_release()
self.local_var.clear()
return self.local_var.tid
finally:
self._load_lock_release()
def undo(self, transaction_id, txn, wrapper):
if txn is not self.local_var.txn:
raise StorageTransactionError(self, transaction_id)
# First get transaction information from a storage node.
cell_list = self._getCellListForID(transaction_id, writable=True)
shuffle(cell_list)
for cell in cell_list:
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
self.local_var.txn_info = 0
try:
self._askStorage(conn, protocol.askTransactionInformation(transaction_id))
except ConnectionClosed:
continue
if self.local_var.txn_info == -1:
# Tid not found, try with next node
continue
elif isinstance(self.local_var.txn_info, dict):
break
else:
raise NEOStorageError('undo failed')
if self.local_var.txn_info in (-1, 0):
raise NEOStorageError('undo failed')
oid_list = self.local_var.txn_info['oids']
# Second get object data from storage node using loadBefore
data_dict = {}
for oid in oid_list:
try:
result = self.loadBefore(oid, transaction_id)
except NEOStorageNotFoundError:
# no previous revision, can't undo (as in filestorage)
raise UndoError("no previous record", oid)
data, start, end = result
# end must be TID we are going to undone otherwise it means
# a later transaction modify the object
if end != transaction_id:
raise UndoError("non-undoable transaction", oid)
data_dict[oid] = data
# Third do transaction with old data
oid_list = data_dict.keys()
for oid in oid_list:
data = data_dict[oid]
try:
self.store(oid, transaction_id, data, None, txn)
except NEOStorageConflictError, serial:
if serial <= self.local_var.tid:
new_data = wrapper.tryToResolveConflict(oid, self.local_var.tid,
serial, data)
if new_data is not None:
self.store(oid, self.local_var.tid, new_data, None, txn)
continue
raise ConflictError(oid = oid, serials = (self.local_var.tid, serial),
data = data)
return self.local_var.tid, oid_list
def undoLog(self, first, last, filter=None, block=0):
if last < 0:
# See FileStorage.py for explanation
last = first - last
# First get a list of transactions from all storage nodes.
# Each storage node will return TIDs only for UP_TO_DATE_STATE and
# FEEDING_STATE cells
pt = self._getPartitionTable()
storage_node_list = pt.getNodeList()
self.local_var.node_tids = {}
for storage_node in storage_node_list:
conn = self.cp.getConnForNode(storage_node)
if conn is None:
continue
try:
conn.ask(protocol.askTIDs(first, last, INVALID_PARTITION))
finally:
conn.unlock()
# Wait for answers from all storages.
# FIXME this is a busy loop.
while len(self.local_var.node_tids) != len(storage_node_list):
try:
self._waitMessage(handler=self.storage_handler)
except ConnectionClosed:
continue
# Reorder tids
ordered_tids = set()
update = ordered_tids.update
for tid_list in self.local_var.node_tids.itervalues():
update(tid_list)
ordered_tids = list(ordered_tids)
# XXX do we need a special cmp function here ?
ordered_tids.sort(reverse=True)
logging.debug("UndoLog, tids %s", ordered_tids)
# For each transaction, get info
undo_info = []
append = undo_info.append
for tid in ordered_tids:
cell_list = self._getCellListForID(tid, readable=True)
shuffle(cell_list)
for cell in cell_list:
conn = self.cp.getConnForCell(cell)
if conn is not None:
self.local_var.txn_info = 0
try:
self._askStorage(conn, protocol.askTransactionInformation(tid))
except ConnectionClosed:
continue
if isinstance(self.local_var.txn_info, dict):
break
if self.local_var.txn_info in (-1, 0):
# TID not found at all
raise NeoException, 'Data inconsistency detected: ' \
'transaction info for TID %r could not ' \
'be found' % (tid, )
if filter is None or filter(self.local_var.txn_info):
self.local_var.txn_info.pop("oids")
append(self.local_var.txn_info)
if len(undo_info) >= last - first:
break
# Check we return at least one element, otherwise call
# again but extend offset
if len(undo_info) == 0 and not block:
undo_info = self.undoLog(first=first, last=last*5, filter=filter, block=1)
return undo_info
# FIXME: filter function isn't used
def history(self, oid, version=None, length=1, filter=None, object_only=0):
# Get history informations for object first
cell_list = self._getCellListForID(oid, readable=True)
shuffle(cell_list)
for cell in cell_list:
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
self.local_var.history = None
try:
self._askStorage(conn, protocol.askObjectHistory(oid, 0, length))
except ConnectionClosed:
continue
if self.local_var.history == -1:
# Not found, go on with next node
continue
if self.local_var.history[0] != oid:
# Got history for wrong oid
raise NEOStorageError('inconsistency in storage: asked oid ' \
'%r, got %r' % (
oid, self.local_var.history[0]))
if not isinstance(self.local_var.history, tuple):
raise NEOStorageError('history failed')
if object_only:
# Use by getSerial
return self.local_var.history
# Now that we have object informations, get txn informations
history_list = []
for serial, size in self.local_var.history[1]:
self._getCellListForID(serial, readable=True)
shuffle(cell_list)
for cell in cell_list:
conn = self.cp.getConnForCell(cell)
if conn is None:
continue
# ask transaction information
self.local_var.txn_info = None
try:
self._askStorage(conn, protocol.askTransactionInformation(serial))
except ConnectionClosed:
continue
if self.local_var.txn_info == -1:
# TID not found
continue
if isinstance(self.local_var.txn_info, dict):
break
# create history dict
self.local_var.txn_info.pop('id')
self.local_var.txn_info.pop('oids')
self.local_var.txn_info['tid'] = serial
self.local_var.txn_info['version'] = None
self.local_var.txn_info['size'] = size
history_list.append(self.local_var.txn_info)
return history_list
def __del__(self):
"""Clear all connection."""
# Due to bug in ZODB, close is not always called when shutting
# down zope, so use __del__ to close connections
for conn in self.em.getConnectionList():
conn.lock()
try:
conn.close()
finally:
conn.release()
# Stop polling thread
self.poll_thread.stop()
close = __del__
def sync(self):
self._waitMessage()
def setNodeReady(self):
self.local_var.node_ready = True
def setNodeNotReady(self):
self.local_var.node_ready = False
def isNodeReady(self):
return self.local_var.node_ready
def setTID(self, value):
self.local_var.tid = value
def getTID(self):
return self.local_var.tid
def getConflictSerial(self):
return self.conflict_serial
def setTransactionFinished(self):
self.local_var.txn_finished = True
def isTransactionFinished(self):
return self.local_var.txn_finished
def setTransactionVoted(self):
self.local_var.txn_voted = True
def isTransactionVoted(self):
return self.local_var.txn_voted
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/config.py 0000664 0000000 0000000 00000001760 11227104631 0023323 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from ZODB.config import BaseConfig
class NeoStorage(BaseConfig):
def open(self):
from Storage import Storage
return Storage(master_nodes = self.config.master_nodes, name = self.config.name,
connector = self.config.connector)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/dispatcher.py 0000664 0000000 0000000 00000003205 11227104631 0024200 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Dispatcher:
"""Dispatcher class use to redirect request to thread."""
def __init__(self):
# This dict is used to associate conn/message id to client thread queue
# and thus redispatch answer to the original thread
self.message_table = {}
def getQueue(self, conn, packet):
key = (id(conn), packet.getId())
return self.message_table.pop(key, None)
def register(self, conn, msg_id, queue):
"""Register an expectation for a reply. Thanks to GIL, it is
safe not to use a lock here."""
key = (id(conn), msg_id)
self.message_table[key] = queue
def registered(self, conn):
"""Check if a connection is registered into message table."""
searched_id = id(conn)
for conn_id, msg_id in self.message_table.iterkeys():
if searched_id == conn_id:
return True
return False
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/exception.py 0000664 0000000 0000000 00000001655 11227104631 0024057 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from ZODB import POSException
class NEOStorageError(POSException.StorageError): pass
class NEOStorageConflictError(NEOStorageError): pass
class NEOStorageNotFoundError(NEOStorageError): pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/handlers/ 0000775 0000000 0000000 00000000000 11227104631 0023300 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/handlers/__init__.py 0000664 0000000 0000000 00000007221 11227104631 0025413 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from neo.handler import EventHandler
from neo.protocol import UnexpectedPacketError
class BaseHandler(EventHandler):
"""Base class for client-side EventHandler implementations."""
def __init__(self, app, dispatcher):
super(BaseHandler, self).__init__(app)
self.dispatcher = dispatcher
def dispatch(self, conn, packet):
# Before calling superclass's dispatch method, lock the connection.
# This covers the case where handler sends a response to received
# packet.
conn.lock()
try:
super(BaseHandler, self).dispatch(conn, packet)
finally:
conn.release()
def packetReceived(self, conn, packet):
"""Redirect all received packet to dispatcher thread."""
if packet.isResponse():
queue = self.dispatcher.getQueue(conn, packet)
if queue is None:
raise UnexpectedPacketError('Unexpected response packet')
queue.put((conn, packet))
else:
self.dispatch(conn, packet)
def _notifyQueues(self, conn):
"""
Put fake packets to task queues so that threads waiting for an
answer get notified of the disconnection.
"""
# XXX: not thread-safe !
queue_set = set()
conn_id = id(conn)
for key in self.dispatcher.message_table.keys():
if conn_id == key[0]:
queue = self.dispatcher.message_table.pop(key)
queue_set.add(queue)
for queue in queue_set:
queue.put((conn, None))
def connectionClosed(self, conn):
super(BaseHandler, self).connectionClosed(conn)
self._notifyQueues(conn)
def timeoutExpired(self, conn):
super(BaseHandler, self).timeoutExpired(conn)
conn.lock()
try:
conn.close()
finally:
conn.release()
self._notifyQueues(conn)
def connectionFailed(self, conn):
super(BaseHandler, self).connectionFailed(conn)
self._notifyQueues(conn)
def unexpectedInAnswerHandler(*args, **kw):
raise Exception('Unexpected event in an answer handler')
class AnswerBaseHandler(EventHandler):
connectionStarted = unexpectedInAnswerHandler
connectionCompleted = unexpectedInAnswerHandler
connectionFailed = unexpectedInAnswerHandler
connectionAccepted = unexpectedInAnswerHandler
timeoutExpired = unexpectedInAnswerHandler
connectionClosed = unexpectedInAnswerHandler
packetReceived = unexpectedInAnswerHandler
peerBroken = unexpectedInAnswerHandler
from neo.client.handlers.master import PrimaryBootstrapHandler
from neo.client.handlers.master import PrimaryNotificationsHandler
from neo.client.handlers.master import PrimaryAnswersHandler
from neo.client.handlers.storage import StorageEventHandler
from neo.client.handlers.storage import StorageBootstrapHandler
from neo.client.handlers.storage import StorageAnswersHandler
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/handlers/master.py 0000664 0000000 0000000 00000027564 11227104631 0025163 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.client.handlers import BaseHandler, AnswerBaseHandler
from neo.protocol import MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, \
INVALID_UUID, RUNNING_STATE, TEMPORARILY_DOWN_STATE
from neo.node import MasterNode, StorageNode
from neo.pt import MTPartitionTable as PartitionTable
from neo.util import dump
from neo import decorators
class PrimaryBootstrapHandler(AnswerBaseHandler):
""" Bootstrap handler used when looking for the primary master """
def handleNotReady(self, conn, packet, message):
app = self.app
app.trying_master_node = None
app.setNodeNotReady()
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port,
num_partitions, num_replicas, your_uuid):
app = self.app
node = app.nm.getNodeByServer(conn.getAddress())
# this must be a master node
if node_type != MASTER_NODE_TYPE:
conn.lock()
try:
conn.close()
finally:
conn.release()
return
if conn.getAddress() != (ip_address, port):
# The server address is different! Then why was
# the connection successful?
logging.error('%s:%d is waiting for %s:%d',
conn.getAddress()[0], conn.getAddress()[1],
ip_address, port)
app.nm.remove(node)
conn.lock()
try:
conn.close()
finally:
conn.release()
return
conn.setUUID(uuid)
node.setUUID(uuid)
if your_uuid != INVALID_UUID:
# got an uuid from the primary master
app.uuid = your_uuid
# Always create partition table
app.pt = PartitionTable(num_partitions, num_replicas)
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid,
known_master_list):
app = self.app
# Register new master nodes.
for ip_address, port, uuid in known_master_list:
addr = (ip_address, port)
n = app.nm.getNodeByServer(addr)
if n is None:
n = MasterNode(server = addr)
app.nm.add(n)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if n.getUUID() is None or n.getUUID() != uuid:
n.setUUID(uuid)
if primary_uuid != INVALID_UUID:
primary_node = app.nm.getNodeByUUID(primary_uuid)
if primary_node is None:
# I don't know such a node. Probably this information
# is old. So ignore it.
logging.warning('Unknown primary master UUID: %s. ' \
'Ignoring.' % dump(primary_uuid))
else:
app.primary_master_node = primary_node
if app.trying_master_node is not primary_node:
app.trying_master_node = None
conn.lock()
try:
conn.close()
finally:
conn.release()
else:
if app.primary_master_node is not None:
# The primary master node is not a primary master node
# any longer.
app.primary_master_node = None
app.trying_master_node = None
conn.lock()
try:
conn.close()
finally:
conn.release()
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
pass
def handleAnswerNodeInformation(self, conn, packet, node_list):
pass
class PrimaryNotificationsHandler(BaseHandler):
""" Handler that process the notifications from the primary master """
def connectionClosed(self, conn):
app = self.app
if app.master_conn is not None:
assert conn is app.master_conn
logging.critical("connection to primary master node closed")
conn.lock()
try:
app.master_conn.close()
finally:
conn.release()
app.master_conn = None
app.primary_master_node = None
super(PrimaryNotificationsHandler, self).connectionClosed(conn)
def timeoutExpired(self, conn):
app = self.app
if app.master_conn is not None:
assert conn is app.master_conn
logging.critical("connection timeout to primary master node expired")
BaseHandler.timeoutExpired(self, conn)
def peerBroken(self, conn):
app = self.app
if app.master_conn is not None:
assert conn is app.master_conn
logging.critical("primary master node is broken")
BaseHandler.peerBroken(self, conn)
def handleStopOperation(self, conn, packet):
logging.critical("master node ask to stop operation")
def handleInvalidateObjects(self, conn, packet, oid_list, tid):
app = self.app
app._cache_lock_acquire()
try:
# ZODB required a dict with oid as key, so create it
oids = {}
for oid in oid_list:
oids[oid] = tid
try:
del app.mq_cache[oid]
except KeyError:
pass
db = app.getDB()
if db is not None:
db.invalidate(tid, oids)
finally:
app._cache_lock_release()
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
app = self.app
nm = app.nm
pt = app.pt
if app.ptid >= ptid:
# Ignore this packet.
return
app.ptid = ptid
for offset, uuid, state in cell_list:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
@decorators.identification_required
def handleSendPartitionTable(self, conn, packet, ptid, row_list):
# This handler is in PrimaryBootstrapHandler, since this
# basicaly is an answer to askPrimaryMaster.
# Extract from P-NEO-Protocol.Description:
# Connection to primary master node (PMN in service state)
# CN -> PMN : askPrimaryMaster
# PMN -> CN : answerPrimaryMaster containing primary uuid and no
# known master list
# PMN -> CN : notifyNodeInformation containing list of all
# ASK_STORE_TRANSACTION# PMN -> CN : sendPartitionTable containing partition table id and
# list of rows
# notifyNodeInformation is valid as asynchrounous event, but
# sendPartitionTable is only triggered after askPrimaryMaster.
uuid = conn.getUUID()
app = self.app
nm = app.nm
pt = app.pt
node = app.nm.getNodeByUUID(uuid)
# This must be sent only by primary master node
if node.getNodeType() != MASTER_NODE_TYPE:
return
if app.ptid != ptid:
app.ptid = ptid
pt.clear()
for offset, row in row_list:
for uuid, state in row:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
nm = app.nm
for node_type, ip_address, port, uuid, state in node_list:
logging.debug("notified of %s %s %d %s %s" %(node_type, ip_address, port, dump(uuid), state))
# Register new nodes.
addr = (ip_address, port)
# Try to retrieve it from nm
n = None
if uuid != INVALID_UUID:
n = nm.getNodeByUUID(uuid)
if n is None:
n = nm.getNodeByServer(addr)
if n is not None and uuid != INVALID_UUID:
# node only exists by address, remove it
nm.remove(n)
n = None
elif n.getServer() != addr:
# same uuid but different address, remove it
nm.remove(n)
n = None
if node_type == MASTER_NODE_TYPE:
if n is None:
n = MasterNode(server = addr)
nm.add(n)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if n.getUUID() is None:
n.setUUID(uuid)
elif node_type == STORAGE_NODE_TYPE:
if uuid == INVALID_UUID:
# No interest.
continue
if n is None:
n = StorageNode(server = addr, uuid = uuid)
nm.add(n)
elif node_type == CLIENT_NODE_TYPE:
continue
n.setState(state)
# close connection to this node if no longer running
if node_type in (MASTER_NODE_TYPE, STORAGE_NODE_TYPE) and \
state != RUNNING_STATE:
can_close = False
for conn in self.app.em.getConnectionList():
if conn.getUUID() == n.getUUID():
conn.lock()
try:
conn.close()
finally:
conn.release()
can_close = True
break
if can_close and node_type == STORAGE_NODE_TYPE:
# Remove from pool connection
app.cp.removeConnection(n)
# Put fake packets to task queues.
queue_set = set()
for key in self.dispatcher.message_table.keys():
if id(conn) == key[0]:
queue = self.dispatcher.message_table.pop(key)
queue_set.add(queue)
# Storage failure is notified to the primary master when the fake
# packet if popped by a non-polling thread.
for queue in queue_set:
queue.put((conn, None))
class PrimaryAnswersHandler(AnswerBaseHandler):
""" Handle that process expected packets from the primary master """
def handleAnswerNewTID(self, conn, packet, tid):
app = self.app
app.setTID(tid)
def handleAnswerNewOIDs(self, conn, packet, oid_list):
app = self.app
app.new_oid_list = oid_list
app.new_oid_list.reverse()
def handleNotifyTransactionFinished(self, conn, packet, tid):
app = self.app
if tid == app.getTID():
app.setTransactionFinished()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/handlers/storage.py 0000664 0000000 0000000 00000012636 11227104631 0025326 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.client.handlers import BaseHandler, AnswerBaseHandler
from neo.protocol import STORAGE_NODE_TYPE
from ZODB.TimeStamp import TimeStamp
class StorageEventHandler(BaseHandler):
def _dealWithStorageFailure(self, conn, node):
app = self.app
# Remove from pool connection
app.cp.removeConnection(node)
# Put fake packets to task queues.
queue_set = set()
for key in self.dispatcher.message_table.keys():
if id(conn) == key[0]:
queue = self.dispatcher.message_table.pop(key)
queue_set.add(queue)
# Storage failure is notified to the primary master when the fake
# packet if popped by a non-polling thread.
for queue in queue_set:
queue.put((conn, None))
def connectionClosed(self, conn):
node = self.app.nm.getNodeByServer(conn.getAddress())
logging.info("connection to storage node %s closed", node.getServer())
self._dealWithStorageFailure(conn, node)
super(StorageEventHandler, self).connectionClosed(conn)
def timeoutExpired(self, conn):
node = self.app.nm.getNodeByServer(conn.getAddress())
self._dealWithStorageFailure(conn, node)
super(StorageEventHandler, self).timeoutExpired(conn)
def peerBroken(self, conn):
node = self.app.nm.getNodeByServer(conn.getAddress())
self._dealWithStorageFailure(conn, node)
super(StorageEventHandler, self).peerBroken(conn)
def connectionFailed(self, conn):
# Connection to a storage node failed
node = self.app.nm.getNodeByServer(conn.getAddress())
self._dealWithStorageFailure(conn, node)
super(StorageEventHandler, self).connectionFailed(conn)
class StorageBootstrapHandler(AnswerBaseHandler):
""" Handler used when connecting to a storage node """
def handleNotReady(self, conn, packet, message):
app = self.app
app.setNodeNotReady()
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, num_partitions, num_replicas, your_uuid):
app = self.app
node = app.nm.getNodeByServer(conn.getAddress())
# It can be eiter a master node or a storage node
if node_type != STORAGE_NODE_TYPE:
conn.close()
return
if conn.getAddress() != (ip_address, port):
# The server address is different! Then why was
# the connection successful?
logging.error('%s:%d is waiting for %s:%d',
conn.getAddress()[0], conn.getAddress()[1], ip_address, port)
app.nm.remove(node)
conn.close()
return
conn.setUUID(uuid)
node.setUUID(uuid)
class StorageAnswersHandler(AnswerBaseHandler):
""" Handle all messages related to ZODB operations """
def handleAnswerObject(self, conn, packet, oid, start_serial, end_serial,
compression, checksum, data):
app = self.app
app.local_var.asked_object = (oid, start_serial, end_serial, compression,
checksum, data)
def handleAnswerStoreObject(self, conn, packet, conflicting, oid, serial):
app = self.app
if conflicting:
app.local_var.object_stored = -1, serial
else:
app.local_var.object_stored = oid, serial
def handleAnswerStoreTransaction(self, conn, packet, tid):
app = self.app
app.setTransactionVoted()
def handleAnswerTransactionInformation(self, conn, packet, tid,
user, desc, ext, oid_list):
app = self.app
# transaction information are returned as a dict
info = {}
info['time'] = TimeStamp(tid).timeTime()
info['user_name'] = user
info['description'] = desc
info['id'] = tid
info['oids'] = oid_list
app.local_var.txn_info = info
def handleAnswerObjectHistory(self, conn, packet, oid, history_list):
app = self.app
# history_list is a list of tuple (serial, size)
app.local_var.history = oid, history_list
def handleOidNotFound(self, conn, packet, message):
app = self.app
# This can happen either when :
# - loading an object
# - asking for history
app.local_var.asked_object = -1
app.local_var.history = -1
def handleTidNotFound(self, conn, packet, message):
app = self.app
# This can happen when requiring txn informations
app.local_var.txn_info = -1
def handleAnswerTIDs(self, conn, packet, tid_list):
app = self.app
app.local_var.node_tids[conn.getUUID()] = tid_list
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/mq.py 0000664 0000000 0000000 00000020341 11227104631 0022467 0 ustar 00root root 0000000 0000000 ##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Yoshinori Okuji
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
"""
Multi-Queue Cache Algorithm.
"""
from math import log
class Element(object):
"""
This class defines an element of a FIFO buffer.
"""
pass
class FIFO(object):
"""
This class implements a FIFO buffer.
"""
def __init__(self):
self._head = None
self._tail = None
self._len = 0
def __len__(self):
return self._len
def append(self):
element = Element()
element.next = None
element.prev = self._tail
if self._tail is not None:
self._tail.next = element
self._tail = element
if self._head is None:
self._head = element
self._len += 1
return element
def head(self):
return self._head
def tail(self):
return self._tail
def shift(self):
element = self._head
if element is None:
return None
del self[element]
del element.next
del element.prev
return element
def __delitem__(self, element):
if element.next is None:
self._tail = element.prev
else:
element.next.prev = element.prev
if element.prev is None:
self._head = element.next
else:
element.prev.next = element.next
self._len -= 1
class Data(object):
"""
Data for each element in a FIFO buffer.
"""
pass
def sizeof(o):
"""This function returns the estimated size of an object."""
if isinstance(o, tuple):
return sum((len(s)+16 for s in o))
else:
return len(o)+16
class MQ(object):
"""
This class manages cached data by a variant of Multi-Queue.
This class caches various sizes of objects. Here are some considerations:
- Expired objects are not really deleted immediately. But if GC is invoked too often,
it degrades the performance significantly.
- If large objects are cached, the number of cached objects decreases. This might affect
the cache hit ratio. It might be better to tweak a buffer level according to the size of
an object.
- Stored values must be strings.
- The size calculation is not accurate.
"""
def __init__(self, life_time=10000, buffer_levels=9, max_history_size=100000, max_size=20*1024*1024):
self._history_buffer = FIFO()
self._cache_buffers = []
for level in range(buffer_levels):
self._cache_buffers.append(FIFO())
self._data = {}
self._time = 0
self._life_time = life_time
self._buffer_levels = buffer_levels
self._max_history_size = max_history_size
self._max_size = max_size
self._size = 0
def has_key(self, id):
if id in self._data:
data = self._data[id]
if data.level >= 0:
return 1
return 0
__contains__ = has_key
def fetch(self, id):
"""
Fetch a value associated with the id.
"""
data = self._data[id]
if data.level >= 0:
value = data.value
self._size -= sizeof(value) # XXX inaccurate
self.store(id, value)
return value
raise KeyError(id)
__getitem__ = fetch
def get(self, id, d=None):
try:
return self.fetch(id)
except KeyError:
return d
def _evict(self, id):
"""
Evict an element to the history buffer.
"""
data = self._data[id]
self._size -= sizeof(data.value) # XXX inaccurate
del self._cache_buffers[data.level][data.element]
element = self._history_buffer.append()
data.level = -1
data.element = element
del data.value
del data.expire_time
element.data = data
if len(self._history_buffer) > self._max_history_size:
element = self._history_buffer.shift()
del self._data[element.data.id]
def store(self, id, value):
cache_buffers = self._cache_buffers
try:
data = self._data[id]
level, element, counter = data.level, data.element, data.counter + 1
if level >= 0:
del cache_buffers[level][element]
else:
del self._history_buffer[element]
except KeyError:
counter = 1
# XXX It might be better to adjust the level according to the object size.
level = min(int(log(counter, 2)), self._buffer_levels - 1)
element = cache_buffers[level].append()
data = Data()
data.id = id
data.expire_time = self._time + self._life_time
data.level = level
data.element = element
data.value = value
data.counter = counter
element.data = data
self._data[id] = data
self._size += sizeof(value) # XXX inaccurate
del value
self._time += 1
# Expire old elements.
time = self._time
for level in xrange(self._buffer_levels):
cache_buffer = cache_buffers[level]
head = cache_buffer.head()
if head is not None and head.data.expire_time < time:
del cache_buffer[head]
data = head.data
if level > 0:
new_level = level - 1
element = cache_buffers[new_level].append()
element.data = data
data.expire_time = time + self._life_time
data.level = new_level
data.element = element
else:
self._evict(data.id)
# Limit the size.
size = self._size
max_size = self._max_size
if size > max_size:
for cache_buffer in cache_buffers:
while size > max_size:
element = cache_buffer.shift()
if element is None:
break
data = element.data
del self._data[data.id]
size -= sizeof(data.value) # XXX inaccurate
del data.value
if size <= max_size:
break
self._size = size
__setitem__ = store
def invalidate(self, id):
if id in self._data:
data = self._data[id]
if data.level >= 0:
del self._cache_buffers[data.level][data.element]
self._evict(id)
return
raise KeyError, "%s was not found in the cache" % id
__delitem__ = invalidate
# Here is a test.
if __name__ == '__main__':
import hotshot, hotshot.stats
def test():
cache = MQ(life_time=100, buffer_levels=9, max_history_size=10000,
max_size=2*1024*1024)
for i in xrange(10000):
assert cache.get(i) is None, '%d should not be present' % i
for i in xrange(10000):
cache[i] = str(i)
assert cache.get(i) == str(i), '%d does not exist' % i
for i in xrange(10000 - 100 - 1):
assert cache.get(i) is None, '%d should not be present' % i
for i in xrange(10):
cache[i] = str(i)
for j in xrange(1000):
for i in xrange(10):
assert cache.get(i) == str(i), '%d does not exist' % i
for i in xrange(10,500):
cache[i] = str(i)
for i in xrange(10):
assert cache.get(i) == str(i), '%d does not exist' % i
prof = hotshot.Profile("mq.prof")
prof.runcall(test)
prof.close()
stats = hotshot.stats.load("mq.prof")
stats.strip_dirs()
stats.sort_stats('time', 'calls')
stats.print_stats(20)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/poll.py 0000664 0000000 0000000 00000002551 11227104631 0023023 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from threading import Thread, Event
import logging
class ThreadedPoll(Thread):
"""Polling thread."""
def __init__(self, em, **kw):
Thread.__init__(self, **kw)
self.em = em
self.setDaemon(True)
self._stop = Event()
self.start()
def run(self):
while not self._stop.isSet():
# First check if we receive any new message from other node
try:
self.em.poll()
except:
logging.error('poll raised, retrying', exc_info=1)
logging.debug('Threaded poll stopped')
def stop(self):
self._stop.set()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/ 0000775 0000000 0000000 00000000000 11227104631 0022642 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/__init__.py 0000664 0000000 0000000 00000000577 11227104631 0024764 0 ustar 00root root 0000000 0000000 from neo.client.tests.testClientApp import ClientApplicationTests
from neo.client.tests.testClientHandler import ClientHandlerTests
from neo.client.tests.testConnectionPool import ConnectionPoolTests
from neo.client.tests.testDispatcher import DispatcherTests
__all__ = [
'ClientApplicationTests',
'ClientHandlerTests',
'ConnectionPoolTests',
'DispatcherTests',
]
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/testClientApp.py 0000664 0000000 0000000 00000111223 11227104631 0025773 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest
from mock import Mock, ReturnValues
from ZODB.POSException import StorageTransactionError, UndoError, ConflictError
from neo.tests.base import NeoTestBase
from neo.client.app import Application
from neo.client.exception import NEOStorageError, NEOStorageNotFoundError, \
NEOStorageConflictError
from neo import protocol
from neo.protocol import *
from neo.util import makeChecksum
import neo.connection
def _getMasterConnection(self):
if self.master_conn is None:
self.uuid = 'C' * 16
self.num_partitions = 10
self.num_replicas = 1
self.pt = Mock({
'getCellListForID': (),
})
self.master_conn = Mock()
return self.master_conn
def _getPartitionTable(self):
if self.pt is None:
self.master_conn = _getMasterConnection(self)
return self.pt
def _waitMessage(self, conn=None, msg_id=None, handler=None):
if conn is not None and handler is not None:
handler.dispatch(conn, conn.fakeReceived())
else:
raise NotImplementedError
class ClientApplicationTests(NeoTestBase):
def setUp(self):
# apply monkey patches
self._getMasterConnection = Application._getMasterConnection
self._waitMessage = Application._waitMessage
self._getPartitionTable = Application._getPartitionTable
Application._getMasterConnection = _getMasterConnection
Application._waitMessage = _waitMessage
Application._getPartitionTable = _getPartitionTable
def tearDown(self):
# restore environnement
Application._getMasterConnection = self._getMasterConnection
Application._waitMessage = self._waitMessage
Application._getPartitionTable = self._getPartitionTable
# some helpers
def getApp(self, master_nodes='127.0.0.1:10010', name='test',
connector='SocketConnector', **kw):
app = Application(master_nodes, name, connector, **kw)
return app
def makeOID(self, value=None):
from random import randint
if value is None:
value = randint(0, 255)
return '\00' * 7 + chr(value)
makeTID = makeOID
def makeTransactionObject(self, user='u', description='d', _extension='e'):
class Transaction(object): pass
txn = Transaction()
txn.user = user
txn.description = description
txn._extension = _extension
return txn
def beginTransaction(self, app, tid):
txn = self.makeTransactionObject()
app.tpc_begin(txn, tid=tid)
return txn
def storeObject(self, app, oid=None, data='DATA'):
tid = app.local_var.tid
if oid is None:
oid = self.makeOID()
obj = (oid, tid, 'DATA', '', app.local_var.txn)
packet = protocol.answerStoreObject(conflicting=0, oid=oid, serial=tid)
conn = Mock({ 'getNextId': 1, 'fakeReceived': packet, })
cell = Mock({ 'getServer': 'FakeServer', 'getState': 'FakeState', })
app.cp = Mock({ 'getConnForCell': conn})
app.pt = Mock({ 'getCellListForID': (cell, cell, ) })
return oid
def voteTransaction(self, app):
tid = app.local_var.tid
txn = app.local_var.txn
packet = protocol.answerStoreTransaction(tid=tid)
conn = Mock({ 'getNextId': 1, 'fakeReceived': packet, })
cell = Mock({ 'getServer': 'FakeServer', 'getState': 'FakeState', })
app.pt = Mock({ 'getCellListForID': (cell, cell, ) })
app.cp = Mock({ 'getConnForCell': ReturnValues(None, conn), })
app.tpc_vote(txn)
def finishTransaction(self, app):
txn = app.local_var.txn
tid = app.local_var.tid
packet = protocol.notifyTransactionFinished(tid)
app.master_conn = Mock({
'getNextId': 1,
'getAddress': ('127.0.0.1', 10010),
'fakeReceived': packet,
})
app.tpc_finish(txn)
# common checks
def checkDispatcherRegisterCalled(self, app, conn):
calls = app.dispatcher.mockGetNamedCalls('register')
self.assertEquals(len(calls), 1)
#self.assertEquals(calls[0].getParam(0), conn)
#self.assertTrue(isinstance(calls[0].getParam(2), Queue))
def test_getQueue(self):
app = self.getApp()
# Test sanity check
self.assertTrue(getattr(app, 'local_var', None) is not None)
# Test that queue is created
self.assertTrue(getattr(app.local_var, 'queue', None) is not None)
def test_registerDB(self):
app = self.getApp()
dummy_db = []
app.registerDB(dummy_db, None)
self.assertTrue(app.getDB() is dummy_db)
def test_new_oid(self):
app = self.getApp()
test_msg_id = 50
test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
response_packet = protocol.answerNewOIDs(test_oid_list[:])
app.master_conn = Mock({'getNextId': test_msg_id, '_addPacket': None,
'expectMessage': None, 'lock': None,
'unlock': None,
# Test-specific method
'fakeReceived': response_packet})
new_oid = app.new_oid()
self.assertTrue(new_oid in test_oid_list)
self.assertEqual(len(app.new_oid_list), 1)
self.assertTrue(app.new_oid_list[0] in test_oid_list)
self.assertNotEqual(app.new_oid_list[0], new_oid)
def test_getSerial(self):
app = self.getApp()
mq = app.mq_cache
oid = self.makeOID()
tid = self.makeTID()
# cache cleared -> result from ZODB
self.assertTrue(oid not in mq)
app.pt = Mock({ 'getCellListForID': (), })
app.local_var.history = (oid, [(tid, 0)])
self.assertEquals(app.getSerial(oid), tid)
self.assertEquals(len(app.pt.mockGetNamedCalls('getCellListForID')), 1)
# fill the cache -> hit
mq.store(oid, (tid, ''))
self.assertTrue(oid in mq)
app.pt = Mock({ 'getCellListForID': (), })
app.getSerial(oid)
self.assertEquals(app.getSerial(oid), tid)
self.assertEquals(len(app.pt.mockGetNamedCalls('getCellListForID')), 0)
def test_load(self):
app = self.getApp()
mq = app.mq_cache
oid = self.makeOID()
tid1 = self.makeTID(1)
tid2 = self.makeTID(2)
an_object = (1, oid, tid1, tid2, 0, makeChecksum(''), '')
# connection to SN close
self.assertTrue(oid not in mq)
packet = protocol.oidNotFound('')
cell = Mock({ 'getUUID': '\x00' * 16})
conn = Mock({'getUUID': '\x10' * 16,
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.local_var.queue = Mock({'get_nowait' : (conn, None)})
app.pt = Mock({ 'getCellListForID': (cell, ), })
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = -1
Application._waitMessage = self._waitMessage
self.assertRaises(NEOStorageNotFoundError, app.load, oid)
self.checkAskObject(conn)
Application._waitMessage = _waitMessage
# object not found in NEO -> NEOStorageNotFoundError
self.assertTrue(oid not in mq)
packet = protocol.oidNotFound('')
cell = Mock({ 'getUUID': '\x00' * 16})
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.pt = Mock({ 'getCellListForID': (cell, ), })
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = -1
self.assertRaises(NEOStorageNotFoundError, app.load, oid)
self.checkAskObject(conn)
# object found on storage nodes and put in cache
packet = protocol.answerObject(*an_object[1:])
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = an_object
result = app.load(oid)
self.assertEquals(result, ('', tid1))
self.checkAskObject(conn)
self.assertTrue(oid in mq)
# object is now cached, try to reload it
conn = Mock({
'getServer': ('127.0.0.1', 0),
})
app.cp = Mock({ 'getConnForCell' : conn})
result = app.load(oid)
self.assertEquals(result, ('', tid1))
self.checkNoPacketSent(conn)
def test_loadSerial(self):
app = self.getApp()
mq = app.mq_cache
oid = self.makeOID()
tid1 = self.makeTID(1)
tid2 = self.makeTID(2)
# object not found in NEO -> NEOStorageNotFoundError
self.assertTrue(oid not in mq)
packet = protocol.oidNotFound('')
cell = Mock({ 'getUUID': '\x00' * 16})
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.pt = Mock({ 'getCellListForID': (cell, ), })
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = -1
self.assertRaises(NEOStorageNotFoundError, app.loadSerial, oid, tid2)
self.checkAskObject(conn)
# object should not have been cached
self.assertFalse(oid in mq)
# now a cached version ewxists but should not be hit
mq.store(oid, (tid1, 'WRONG'))
self.assertTrue(oid in mq)
another_object = (1, oid, tid2, INVALID_SERIAL, 0, makeChecksum('RIGHT'), 'RIGHT')
packet = protocol.answerObject(*another_object[1:])
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = another_object
result = app.loadSerial(oid, tid1)
self.assertEquals(result, 'RIGHT')
self.checkAskObject(conn)
self.assertTrue(oid in mq)
def test_loadBefore(self):
app = self.getApp()
mq = app.mq_cache
oid = self.makeOID()
tid1 = self.makeTID(1)
tid2 = self.makeTID(2)
# object not found in NEO -> NEOStorageNotFoundError
self.assertTrue(oid not in mq)
packet = protocol.oidNotFound('')
cell = Mock({ 'getUUID': '\x00' * 16})
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.pt = Mock({ 'getCellListForID': (cell, ), })
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = -1
self.assertRaises(NEOStorageNotFoundError, app.loadBefore, oid, tid2)
self.checkAskObject(conn)
# no previous versions -> return None
an_object = (1, oid, tid2, INVALID_SERIAL, 0, makeChecksum(''), '')
packet = protocol.answerObject(*an_object[1:])
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = an_object
result = app.loadBefore(oid, tid1)
self.assertEquals(result, None)
# object should not have been cached
self.assertFalse(oid in mq)
# as for loadSerial, the object is cached but should be loaded from db
mq.store(oid, (tid1, 'WRONG'))
self.assertTrue(oid in mq)
another_object = (1, oid, tid1, tid2, 0, makeChecksum('RIGHT'), 'RIGHT')
packet = protocol.answerObject(*another_object[1:])
conn = Mock({
'getServer': ('127.0.0.1', 0),
'fakeReceived': packet,
})
app.cp = Mock({ 'getConnForCell' : conn})
app.local_var.asked_object = another_object
result = app.loadBefore(oid, tid1)
self.assertEquals(result, ('RIGHT', tid1, tid2))
self.checkAskObject(conn)
self.assertTrue(oid in mq)
def test_tpc_begin(self):
app = self.getApp()
tid = self.makeTID()
txn = Mock()
# first, tid is supplied
self.assertNotEquals(getattr(app, 'tid', None), tid)
self.assertNotEquals(getattr(app, 'txn', None), txn)
app.tpc_begin(transaction=txn, tid=tid)
self.assertTrue(app.local_var.txn is txn)
self.assertEquals(app.local_var.tid, tid)
# next, the transaction already begin -> do nothing
app.tpc_begin(transaction=txn, tid=None)
self.assertTrue(app.local_var.txn is txn)
self.assertEquals(app.local_var.tid, tid)
# cancel and start a transaction without tid
app.local_var.txn = None
app.local_var.tid = None
# no connection -> NEOStorageError (wait until connected to primary)
#self.assertRaises(NEOStorageError, app.tpc_begin, transaction=txn, tid=None)
# ask a tid to pmn
packet = protocol.answerNewTID(tid=tid)
app.master_conn = Mock({
'getNextId': 1,
'expectMessage': None,
'lock': None,
'unlock': None,
'fakeReceived': packet,
})
app.dispatcher = Mock({
})
app.tpc_begin(transaction=txn, tid=None)
self.checkAskNewTid(app.master_conn)
self.checkDispatcherRegisterCalled(app, app.master_conn)
# check attributes
self.assertTrue(app.local_var.txn is txn)
self.assertEquals(app.local_var.tid, tid)
def test_store1(self):
app = self.getApp()
oid = self.makeOID(11)
tid = self.makeTID()
txn = self.makeTransactionObject()
# invalid transaction > StorageTransactionError
app.local_var.txn = old_txn = object()
self.assertTrue(app.local_var.txn is not txn)
self.assertRaises(StorageTransactionError, app.store, oid, tid, '', None, txn)
self.assertEquals(app.local_var.txn, old_txn)
# check partition_id and an empty cell list -> NEOStorageError
app.local_var.txn = txn
app.local_var.tid = tid
app.pt = Mock({ 'getCellListForID': (), })
app.num_partitions = 2
self.assertRaises(NEOStorageError, app.store, oid, tid, '', None, txn)
calls = app.pt.mockGetNamedCalls('getCellListForID')
self.assertEquals(len(calls), 1)
self.assertEquals(calls[0].getParam(0), oid) # oid=11
def test_store2(self):
app = self.getApp()
oid = self.makeOID(11)
tid = self.makeTID()
txn = self.makeTransactionObject()
# build conflicting state
app.local_var.txn = txn
app.local_var.tid = tid
packet = protocol.answerStoreObject(conflicting=1, oid=oid, serial=tid)
conn = Mock({
'getNextId': 1,
'fakeReceived': packet,
})
cell = Mock({
'getServer': 'FakeServer',
'getState': 'FakeState',
})
app.pt = Mock({ 'getCellListForID': (cell, cell, )})
app.cp = Mock({ 'getConnForCell': ReturnValues(None, conn)})
app.dispatcher = Mock({})
app.local_var.object_stored = (oid, tid)
app.local_var.data_dict[oid] = 'BEFORE'
self.assertRaises(NEOStorageConflictError, app.store, oid, tid, '', None, txn)
self.assertTrue(oid not in app.local_var.data_dict)
self.assertEquals(app.conflict_serial, tid)
self.assertEquals(app.local_var.object_stored, (-1, tid))
self.checkAskStoreObject(conn)
self.checkDispatcherRegisterCalled(app, conn)
def test_store3(self):
app = self.getApp()
oid = self.makeOID(11)
tid = self.makeTID()
txn = self.makeTransactionObject()
# case with no conflict
app.local_var.txn = txn
app.local_var.tid = tid
packet = protocol.answerStoreObject(conflicting=0, oid=oid, serial=tid)
conn = Mock({
'getNextId': 1,
'fakeReceived': packet,
})
app.cp = Mock({ 'getConnForCell': ReturnValues(None, conn, ) })
cell = Mock({
'getServer': 'FakeServer',
'getState': 'FakeState',
})
app.pt = Mock({ 'getCellListForID': (cell, cell, ) })
app.dispatcher = Mock({})
app.conflict_serial = None # reset by hand
app.local_var.object_stored = ()
app.store(oid, tid, 'DATA', None, txn)
self.assertEquals(app.local_var.object_stored, (oid, tid))
self.assertEquals(app.local_var.data_dict.get(oid, None), 'DATA')
self.assertNotEquals(app.conflict_serial, tid)
self.checkAskStoreObject(conn)
self.checkDispatcherRegisterCalled(app, conn)
def test_tpc_vote1(self):
app = self.getApp()
oid = self.makeOID(11)
txn = self.makeTransactionObject()
# invalid transaction > StorageTransactionError
app.local_var.txn = old_txn = object()
self.assertTrue(app.local_var.txn is not txn)
self.assertRaises(StorageTransactionError, app.tpc_vote, txn)
self.assertEquals(app.local_var.txn, old_txn)
def test_tpc_vote2(self):
# fake transaction object
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn = txn
app.local_var.tid = tid
# wrong answer -> failure
packet = protocol.answerNewOIDs(())
conn = Mock({
'getNextId': 1,
'fakeReceived': packet,
'getAddress': ('127.0.0.1', 0),
})
cell = Mock({
'getServer': 'FakeServer',
'getState': 'FakeState',
})
app.pt = Mock({ 'getCellListForID': (cell, cell, ) })
app.cp = Mock({ 'getConnForCell': ReturnValues(None, conn), })
app.dispatcher = Mock()
app.tpc_begin(txn, tid)
self.assertRaises(NEOStorageError, app.tpc_vote, txn)
self.assertEquals(len(conn.mockGetNamedCalls('abort')), 1)
calls = conn.mockGetNamedCalls('ask')
self.assertEquals(len(calls), 1)
packet = calls[0].getParam(0)
self.assertTrue(isinstance(packet, Packet))
self.assertEquals(packet._type, ASK_STORE_TRANSACTION)
def test_tpc_vote3(self):
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn = txn
app.local_var.tid = tid
# response -> OK
packet = protocol.answerStoreTransaction(tid=tid)
conn = Mock({
'getNextId': 1,
'fakeReceived': packet,
})
cell = Mock({
'getServer': 'FakeServer',
'getState': 'FakeState',
})
app.pt = Mock({ 'getCellListForID': (cell, cell, ) })
app.cp = Mock({ 'getConnForCell': ReturnValues(None, conn), })
app.dispatcher = Mock()
app.tpc_begin(txn, tid)
app.tpc_vote(txn)
self.checkAskStoreTransaction(conn)
self.checkDispatcherRegisterCalled(app, conn)
def test_tpc_abort1(self):
# ignore mismatch transaction
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn = old_txn = object()
app.master_conn = Mock()
app.local_var.tid = tid
self.assertFalse(app.local_var.txn is txn)
conn = Mock()
cell = Mock()
app.pt = Mock({'getCellListForID': (cell, cell)})
app.cp = Mock({'getConnForCell': ReturnValues(None, cell)})
app.tpc_abort(txn)
# no packet sent
self.checkNoPacketSent(conn)
self.checkNoPacketSent(app.master_conn)
self.assertEquals(app.local_var.txn, old_txn)
self.assertEquals(app.local_var.tid, tid)
def test_tpc_abort2(self):
# 2 nodes : 1 transaction in the first, 2 objects in the second
# connections to each node should received only one packet to abort
# and transaction must also be aborted on the master node
# for simplicity, just one cell per partition
oid1, oid2 = self.makeOID(2), self.makeOID(4) # on partition 0
app, tid = self.getApp(), self.makeTID(1) # on partition 1
txn = self.makeTransactionObject()
app.local_var.txn, app.local_var.tid = txn, tid
app.master_conn = Mock({'__hash__': 0})
app.num_partitions = 2
cell1 = Mock({ 'getNode': 'NODE1', '__hash__': 1 })
cell2 = Mock({ 'getNode': 'NODE2', '__hash__': 2 })
conn1, conn2 = Mock({ 'getNextId': 1, }), Mock({ 'getNextId': 2, })
app.pt = Mock({ 'getCellListForID': ReturnValues((cell1, ), (cell1, ), (cell1, cell2)), })
app.cp = Mock({ 'getConnForCell': ReturnValues(conn1, conn2), })
# fake data
app.local_var.data_dict = {oid1: '', oid2: ''}
app.tpc_abort(txn)
# will check if there was just one call/packet :
self.checkNotifyPacket(conn1, ABORT_TRANSACTION)
self.checkNotifyPacket(conn2, ABORT_TRANSACTION)
self.checkNotifyPacket(app.master_conn, ABORT_TRANSACTION)
self.assertEquals(app.local_var.tid, None)
self.assertEquals(app.local_var.txn, None)
self.assertEquals(app.local_var.data_dict, {})
self.assertEquals(app.local_var.txn_voted, False)
self.assertEquals(app.local_var.txn_finished, False)
def test_tpc_finish1(self):
# ignore mismatch transaction
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn = old_txn = object()
app.master_conn = Mock()
self.assertFalse(app.local_var.txn is txn)
conn = Mock()
cell = Mock()
app.pt = Mock({'getCellListForID': (cell, cell)})
app.cp = Mock({'getConnForCell': ReturnValues(None, cell)})
app.tpc_finish(txn)
# no packet sent
self.checkNoPacketSent(conn)
self.checkNoPacketSent(app.master_conn)
self.assertEquals(app.local_var.txn, old_txn)
def test_tpc_finish2(self):
# bad answer -> NEOStorageError
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn, app.local_var.tid = txn, tid
# test callable passed to tpc_finish
self.f_called = False
self.f_called_with_tid = None
def hook(tid):
self.f_called = True
self.f_called_with_tid = tid
packet = protocol.answerNewTID(INVALID_TID)
app.master_conn = Mock({
'getNextId': 1,
'getAddress': ('127.0.0.1', 10000),
'fakeReceived': packet,
})
app.dispatcher = Mock({})
app.local_var.txn_finished = False
self.assertRaises(NEOStorageError, app.tpc_finish, txn, hook)
self.assertTrue(self.f_called)
self.assertEquals(self.f_called_with_tid, tid)
self.checkFinishTransaction(app.master_conn)
self.checkDispatcherRegisterCalled(app, app.master_conn)
def test_tpc_finish3(self):
# transaction is finished
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
app.local_var.txn, app.local_var.tid = txn, tid
self.f_called = False
self.f_called_with_tid = None
def hook(tid):
self.f_called = True
self.f_called_with_tid = tid
packet = protocol.notifyTransactionFinished(tid)
app.master_conn = Mock({
'getNextId': 1,
'getAddress': ('127.0.0.1', 10010),
'fakeReceived': packet,
})
app.dispatcher = Mock({})
app.local_var.txn_finished = True
app.tpc_finish(txn, hook)
self.assertTrue(self.f_called)
self.assertEquals(self.f_called_with_tid, tid)
self.checkFinishTransaction(app.master_conn)
self.checkDispatcherRegisterCalled(app, app.master_conn)
self.assertEquals(app.local_var.tid, None)
self.assertEquals(app.local_var.txn, None)
self.assertEquals(app.local_var.data_dict, {})
self.assertEquals(app.local_var.txn_voted, False)
self.assertEquals(app.local_var.txn_finished, False)
def test_undo1(self):
# invalid transaction
app = self.getApp()
tid = self.makeTID()
txn = self.makeTransactionObject()
wrapper = Mock()
app.local_var.txn = old_txn = object()
app.master_conn = Mock()
self.assertFalse(app.local_var.txn is txn)
conn = Mock()
cell = Mock()
self.assertRaises(StorageTransactionError, app.undo, tid, txn, wrapper)
# no packet sent
self.checkNoPacketSent(conn)
self.checkNoPacketSent(app.master_conn)
# nothing done
self.assertEquals(len(wrapper.mockGetNamedCalls('tryToResolveConflict')), 0)
self.assertEquals(app.local_var.txn, old_txn)
def test_undo2(self):
# Four tests here :
# undo txn1 where obj1 was created -> fail
# undo txn2 where obj2 was modified in tid3 -> fail
# undo txn3 where there is a conflict on obj2
# undo txn3 where obj2 was altered from tid2 -> ok
# txn4 is the transaction where the undo occurs
app = self.getApp()
app.num_partitions = 2
oid1, oid2 = self.makeOID(1), self.makeOID(2)
tid1, tid2 = self.makeTID(1), self.makeTID(2)
tid3, tid4 = self.makeTID(3), self.makeTID(4)
# commit version 1 of object 1
txn1 = self.beginTransaction(app, tid=tid1)
self.storeObject(app, oid=oid1, data='O1V1')
self.voteTransaction(app)
self.finishTransaction(app)
# commit version 1 of object 2
txn2 = self.beginTransaction(app, tid=tid2)
self.storeObject(app, oid=oid2, data='O1V2')
self.voteTransaction(app)
self.finishTransaction(app)
# commit version 2 of object 2
txn3 = self.beginTransaction(app, tid=tid3)
self.storeObject(app, oid=oid2, data='O2V2')
self.voteTransaction(app)
self.finishTransaction(app)
# undo 1 -> no previous revision
u1p1 = protocol.answerTransactionInformation(tid1, '', '', '', (oid1, ))
u1p2 = protocol.oidNotFound('oid not found')
# undo 2 -> not end tid
u2p1 = protocol.answerTransactionInformation(tid2, '', '', '', (oid2, ))
u2p2 = protocol.answerObject(oid2, tid2, tid3, 0, makeChecksum('O2V1'), 'O2V1')
# undo 3 -> conflict
u3p1 = protocol.answerTransactionInformation(tid3, '', '', '', (oid2, ))
u3p2 = protocol.answerObject(oid2, tid3, tid3, 0, makeChecksum('O2V2'), 'O2V2')
u3p3 = protocol.answerStoreObject(conflicting=1, oid=oid2, serial=tid2)
# undo 4 -> ok
u4p1 = protocol.answerTransactionInformation(tid3, '', '', '', (oid2, ))
u4p2 = protocol.answerObject(oid2, tid3, tid3, 0, makeChecksum('O2V2'), 'O2V2')
u4p3 = protocol.answerStoreObject(conflicting=0, oid=oid2, serial=tid2)
# test logic
packets = (u1p1, u1p2, u2p1, u2p2, u3p1, u3p2, u3p3, u3p1, u4p2, u4p3)
conn = Mock({
'getNextId': 1,
'fakeReceived': ReturnValues(*packets),
'getAddress': ('127.0.0.1', 10010),
})
cell = Mock({ 'getServer': 'FakeServer', 'getState': 'FakeState', })
app.pt = Mock({ 'getCellListForID': (cell, ) })
app.cp = Mock({ 'getConnForCell': conn})
wrapper = Mock({'tryToResolveConflict': None})
txn4 = self.beginTransaction(app, tid=tid4)
# all start here
self.assertRaises(UndoError, app.undo, tid1, txn4, wrapper)
self.assertRaises(UndoError, app.undo, tid2, txn4, wrapper)
self.assertRaises(ConflictError, app.undo, tid3, txn4, wrapper)
self.assertEquals(len(wrapper.mockGetNamedCalls('tryToResolveConflict')), 1)
self.assertEquals(app.undo(tid3, txn4, wrapper), (tid4, [oid2, ]))
self.finishTransaction(app)
def test_undoLog(self):
app = self.getApp()
app.num_partitions = 2
uuid1, uuid2 = '\x00' * 15 + '\x01', '\x00' * 15 + '\x02'
# two nodes, two partition, two transaction, two objects :
node1, node2 = Mock({}), Mock({})
cell1, cell2 = Mock({}), Mock({})
tid1, tid2 = self.makeTID(1), self.makeTID(2)
oid1, oid2 = self.makeOID(1), self.makeOID(2)
# TIDs packets supplied by _waitMessage hook
# TXN info packets
p3 = protocol.answerTransactionInformation(tid1, '', '', '', (oid1, ))
p4 = protocol.answerTransactionInformation(tid2, '', '', '', (oid2, ))
conn = Mock({
'getNextId': 1,
'getUUID': ReturnValues(uuid1, uuid2),
'fakeGetApp': app,
'fakeReceived': ReturnValues(p3, p4),
'getAddress': ('127.0.0.1', 10010),
})
app.pt = Mock({
'getNodeList': (node1, node2, ),
'getCellListForID': ReturnValues([cell1], [cell2]),
})
app.cp = Mock({ 'getConnForCell': conn})
def _waitMessage(self, conn=None, msg_id=None, handler=None):
self.local_var.node_tids = {uuid1: (tid1, ), uuid2: (tid2, )}
Application._waitMessage = _waitMessage_old
_waitMessage_old = Application._waitMessage
Application._waitMessage = _waitMessage
def txn_filter(info):
return info['id'] > '\x00' * 8
result = app.undoLog(0, 4, filter=txn_filter)
self.assertEquals(result[0]['id'], tid1)
self.assertEquals(result[1]['id'], tid2)
def test_history(self):
app = self.getApp()
oid = self.makeOID(1)
tid1, tid2 = self.makeTID(1), self.makeTID(2)
object_history = ( (tid1, 42), (tid2, 42),)
# object history, first is a wrong oid, second is valid
p2 = protocol.answerObjectHistory(oid, object_history)
# transaction history
p3 = protocol.answerTransactionInformation(tid1, 'u', 'd', 'e', (oid, ))
p4 = protocol.answerTransactionInformation(tid2, 'u', 'd', 'e', (oid, ))
# faked environnement
conn = Mock({
'getNextId': 1,
'fakeGetApp': app,
'fakeReceived': ReturnValues(p2, p3, p4),
'getAddress': ('127.0.0.1', 10010),
})
object_cells = [ Mock({}), ]
history_cells = [ Mock({}), Mock({}) ]
app.pt = Mock({
'getCellListForID': ReturnValues(object_cells, history_cells,
history_cells),
})
app.cp = Mock({ 'getConnForCell': conn})
# start test here
result = app.history(oid)
self.assertEquals(len(result), 2)
self.assertEquals(result[0]['tid'], tid1)
self.assertEquals(result[1]['tid'], tid2)
self.assertEquals(result[0]['size'], 42)
self.assertEquals(result[1]['size'], 42)
def test_connectToPrimaryMasterNode(self):
# here we have three master nodes :
# the connection to the first will fail
# the second will have changed
# the third will not be ready
# after the third, the partition table will be operational
# (as if it was connected to the primary master node)
from neo.master.tests.connector import DoNothingConnector
# will raise IndexError at the third iteration
app = self.getApp('127.0.0.1:10010 127.0.0.1:10011')
# TODO: test more connection failure cases
# Seventh packet : askNodeInformation succeeded
all_passed = []
def _waitMessage8(self, conn=None, msg_id=None, handler=None):
print '_waitMessage8'
all_passed.append(1)
# Sixth packet : askPartitionTable succeeded
def _waitMessage7(self, conn=None, msg_id=None, handler=None):
print '_waitMessage7'
app.pt = Mock({'operational': True})
Application._waitMessage = _waitMessage8
# fifth packet : request node identification succeeded
def _waitMessage6(self, conn=None, msg_id=None, handler=None):
print '_waitMessage6'
conn.setUUID('D' * 16)
app.uuid = 'C' * 16
Application._waitMessage = _waitMessage7
# fourth iteration : connection to primary master succeeded
def _waitMessage5(self, conn=None, msg_id=None, handler=None):
print '_waitMessage5'
app.trying_master_node = app.primary_master_node = Mock({
'getServer': ('192.168.1.1', 10000),
'__str__': 'Fake master node',
})
Application._waitMessage = _waitMessage6
# third iteration : node not ready
def _waitMessage4(app, conn=None, msg_id=None, handler=None):
print '_waitMessage4'
app.setNodeNotReady()
app.trying_master_node = None
Application._waitMessage = _waitMessage5
# second iteration : master node changed
def _waitMessage3(app, conn=None, msg_id=None, handler=None):
print '_waitMessage3'
app.primary_master_node = Mock({
'getServer': ('192.168.1.1', 10000),
'__str__': 'Fake master node',
})
Application._waitMessage = _waitMessage4
# first iteration : connection failed
def _waitMessage2(app, conn=None, msg_id=None, handler=None):
print '_waitMessage2'
app.trying_master_node = None
Application._waitMessage = _waitMessage3
# do nothing for the first call
def _waitMessage1(app, conn=None, msg_id=None, handler=None):
print '_waitMessage1'
Application._waitMessage = _waitMessage2
_waitMessage_old = Application._waitMessage
Application._waitMessage = _waitMessage1
# faked environnement
app.connector_handler = DoNothingConnector
app.em = Mock({})
app.pt = Mock({ 'operational': False})
try:
app.master_conn = app._connectToPrimaryMasterNode()
self.assertEqual(len(all_passed), 1)
self.assertTrue(app.master_conn is not None)
self.assertTrue(app.pt.operational())
finally:
Application._waitMessage = _waitMessage_old
def test_askStorage(self):
""" _askStorage is private but test it anyway """
app = self.getApp('')
app.dispatcher = Mock()
conn = Mock()
self.test_ok = False
def _waitMessage_hook(app, conn=None, msg_id=None, handler=None):
self.test_ok = True
_waitMessage_old = Application._waitMessage
packet = protocol.askNewTID()
Application._waitMessage = _waitMessage_hook
try:
app._askStorage(conn, packet)
finally:
Application._waitMessage = _waitMessage_old
# check packet sent, connection unlocked and dispatcher updated
self.checkAskNewTid(conn)
self.assertEquals(len(conn.mockGetNamedCalls('unlock')), 1)
self.assertEquals(len(app.dispatcher.mockGetNamedCalls('register')), 1)
# and _waitMessage called
self.assertTrue(self.test_ok)
def test_askPrimary(self):
""" _askPrimary is private but test it anyway """
app = self.getApp('')
app.dispatcher = Mock()
conn = Mock()
app.master_conn = conn
app.primary_handler = Mock()
self.test_ok = False
def _waitMessage_hook(app, conn=None, msg_id=None, handler=None):
self.assertTrue(handler is app.primary_handler)
self.test_ok = True
_waitMessage_old = Application._waitMessage
Application._waitMessage = _waitMessage_hook
packet = protocol.askNewTID()
try:
app._askPrimary(packet)
finally:
Application._waitMessage = _waitMessage_old
# check packet sent, connection locked during process and dispatcher updated
self.checkAskNewTid(conn)
self.assertEquals(len(conn.mockGetNamedCalls('lock')), 1)
self.assertEquals(len(conn.mockGetNamedCalls('unlock')), 1)
self.assertEquals(len(app.dispatcher.mockGetNamedCalls('register')), 1)
# and _waitMessage called
self.assertTrue(self.test_ok)
# check NEOStorageError is raised when the primary connection is lost
app.master_conn = None
# check disabled since we reonnect to pmn
#self.assertRaises(NEOStorageError, app._askPrimary, packet)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/testClientHandler.py 0000664 0000000 0000000 00000132655 11227104631 0026644 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest
import logging
import threading
from mock import Mock, ReturnValues
from neo.tests.base import NeoTestBase
from neo import protocol
from neo.protocol import UnexpectedPacketError, INVALID_UUID
from neo.protocol import \
INVALID_PTID, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, \
UP_TO_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.client.handlers import BaseHandler, PrimaryBootstrapHandler
from neo.client.handlers import PrimaryNotificationsHandler, PrimaryAnswersHandler
from neo.client.handlers import StorageBootstrapHandler, StorageAnswersHandler
MARKER = []
class ClientHandlerTests(NeoTestBase):
def setUp(self):
# Silence all log messages
logging.basicConfig(level=logging.CRITICAL + 1)
def getConnection(self, uuid=None, port=10010, next_id=None, ip='127.0.0.1'):
if uuid is None:
uuid = self.getNewUUID()
return Mock({'_addPacket': None,
'getUUID': uuid,
'getAddress': (ip, port),
'getNextId': next_id,
'lock': None,
'unlock': None})
def getDispatcher(self, queue=None):
return Mock({'getQueue': queue, 'connectToPrimaryMasterNode': None})
def buildHandler(self, handler_class, app, dispatcher):
# some handlers do not accept the second argument
try:
return handler_class(app, dispatcher)
except TypeError:
return handler_class(app)
def test_ping(self):
"""
Simplest test: check that a PING packet is answered by a PONG
packet.
"""
dispatcher = self.getDispatcher()
client_handler = BaseHandler(None, dispatcher)
conn = self.getConnection()
client_handler.packetReceived(conn, protocol.ping())
self.checkAnswerPacket(conn, protocol.PONG)
def _testInitialMasterWithMethod(self, method):
class App:
primary_master_node = None
trying_master_node = 1
app = App()
method(self.getDispatcher(), app, PrimaryBootstrapHandler)
self.assertEqual(app.primary_master_node, None)
def _testMasterWithMethod(self, method, handler_class):
uuid = self.getNewUUID()
app = Mock({'connectToPrimaryMasterNode': None})
app.primary_master_node = Mock({'getUUID': uuid})
app.master_conn = Mock({'close': None, 'getUUID': uuid, 'getAddress': ('127.0.0.1', 10000)})
dispatcher = self.getDispatcher()
method(dispatcher, app, handler_class, uuid=uuid, conn=app.master_conn)
# XXX: should connection closure be tested ? It's not implemented in all cases
#self.assertEquals(len(App.master_conn.mockGetNamedCalls('close')), 1)
#self.assertEquals(app.master_conn, None)
#self.assertEquals(app.primary_master_node, None)
def _testStorageWithMethod(self, method, handler_class, state=TEMPORARILY_DOWN_STATE):
storage_ip = '127.0.0.1'
storage_port = 10011
fake_storage_node_uuid = self.getNewUUID()
fake_storage_node = Mock({'getUUID': fake_storage_node_uuid, 'getServer': (storage_ip, storage_port), 'getNodeType': STORAGE_NODE_TYPE})
master_node_next_packet_id = 1
class App:
primary_master_node = Mock({'getUUID': self.getNewUUID()})
nm = Mock({'getNodeByServer': fake_storage_node})
cp = Mock({'removeConnection': None})
master_conn = Mock({
'_addPacket': None,
'getUUID': self.getNewUUID(),
'getAddress': ('127.0.0.1', 10010),
'getNextId': master_node_next_packet_id,
'lock': None,
'unlock': None
})
app = App()
conn = self.getConnection(port=storage_port, ip=storage_ip)
key_1 = (id(conn), 0)
queue_1 = Mock({'put': None, '__hash__': 1})
# Fake another Storage connection by adding 1 to id(conn)
key_2 = (id(conn) + 1, 0)
queue_2 = Mock({'put': None, '__hash__': 2})
class Dispatcher:
message_table = {key_1: queue_1,
key_2: queue_2}
dispatcher = Dispatcher()
method(dispatcher, app, handler_class, conn=conn)
# The master should be notified, but this is done in app.py
# Check that failed connection got removed from connection pool
removeConnection_call_list = app.cp.mockGetNamedCalls('removeConnection')
# Test sanity check
self.assertEqual(len(removeConnection_call_list), 1)
self.assertTrue(removeConnection_call_list[0].getParam(0) is fake_storage_node)
# Check that fake packet was put into queue_1, and none in queue_2.
queue_1_put_call_list = queue_1.mockGetNamedCalls('put')
self.assertEqual(len(queue_1_put_call_list), 1)
self.assertEqual(queue_1_put_call_list[0].getParam(0), (conn, None))
self.assertEqual(len(queue_2.mockGetNamedCalls('put')), 0)
def _testConnectionFailed(self, dispatcher, app, handler_class, uuid=None, conn=None):
client_handler = handler_class(app)
if conn is None:
conn = self.getConnection(uuid=uuid)
client_handler.connectionFailed(conn)
def test_initialMasterConnectionFailed(self):
self._testInitialMasterWithMethod(self._testConnectionFailed)
def test_storageConnectionFailed(self):
self._testStorageWithMethod(self._testConnectionFailed,
StorageBootstrapHandler)
def _testConnectionClosed(self, dispatcher, app, handler_class, uuid=None, conn=None):
client_handler = self.buildHandler(handler_class, app, dispatcher)
if conn is None:
conn = self.getConnection(uuid=uuid)
client_handler.connectionClosed(conn)
def test_initialMasterConnectionClosed(self):
self._testInitialMasterWithMethod(self._testConnectionClosed)
def test_masterConnectionClosed(self):
self._testMasterWithMethod(self._testConnectionClosed,
PrimaryNotificationsHandler)
def test_storageConnectionClosed(self):
self._testStorageWithMethod(self._testConnectionClosed,
StorageBootstrapHandler)
self._testStorageWithMethod(self._testConnectionClosed,
StorageAnswersHandler)
def _testTimeoutExpired(self, dispatcher, app, handler_class, uuid=None, conn=None):
client_handler = self.buildHandler(handler_class, app, dispatcher)
if conn is None:
conn = self.getConnection(uuid=uuid)
client_handler.timeoutExpired(conn)
def test_initialMasterTimeoutExpired(self):
self._testInitialMasterWithMethod(self._testTimeoutExpired)
def test_masterTimeoutExpired(self):
self._testMasterWithMethod(self._testTimeoutExpired, PrimaryNotificationsHandler)
def test_storageTimeoutExpired(self):
self._testStorageWithMethod(self._testTimeoutExpired,
StorageAnswersHandler)
self._testStorageWithMethod(self._testTimeoutExpired,
StorageBootstrapHandler)
def _testPeerBroken(self, dispatcher, app, handler_class, uuid=None, conn=None):
client_handler = self.buildHandler(handler_class, app, dispatcher)
if conn is None:
conn = self.getConnection(uuid=uuid)
client_handler.peerBroken(conn)
def test_initialMasterPeerBroken(self):
self._testInitialMasterWithMethod(self._testPeerBroken)
def test_masterPeerBroken(self):
self._testMasterWithMethod(self._testPeerBroken, PrimaryNotificationsHandler)
def test_storagePeerBroken(self):
self._testStorageWithMethod(self._testPeerBroken,
StorageBootstrapHandler, state=BROKEN_STATE)
self._testStorageWithMethod(self._testPeerBroken,
StorageAnswersHandler, state=BROKEN_STATE)
def test_notReady(self):
app = Mock({'setNodeNotReady': None})
dispatcher = self.getDispatcher()
conn = self.getConnection()
client_handler = StorageBootstrapHandler(app)
client_handler.handleNotReady(conn, None, None)
self.assertEquals(len(app.mockGetNamedCalls('setNodeNotReady')), 1)
def test_clientAcceptNodeIdentification(self):
class App:
nm = Mock({'getNodeByServer': None})
storage_node = None
pt = None
app = App()
dispatcher = self.getDispatcher()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
uuid = self.getNewUUID()
app.uuid = 'C' * 16
client_handler.handleAcceptNodeIdentification(conn, None, CLIENT_NODE_TYPE,
uuid, '127.0.0.1', 10010,
0, 0, INVALID_UUID)
self.checkClosed(conn)
self.assertEquals(app.storage_node, None)
self.assertEquals(app.pt, None)
self.assertEquals(app.uuid, 'C' * 16)
def test_masterAcceptNodeIdentification(self):
node = Mock({'setUUID': None})
class FakeLocal:
from Queue import Queue
queue = Queue()
class App:
nm = Mock({'getNodeByServer': node})
storage_node = None
pt = None
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
uuid = self.getNewUUID()
your_uuid = 'C' * 16
app.uuid = INVALID_UUID
client_handler.handleAcceptNodeIdentification(conn, None, MASTER_NODE_TYPE,
uuid, '127.0.0.1', 10010,
10, 2, your_uuid)
self.checkNotClosed(conn)
self.checkUUIDSet(conn, uuid)
self.assertEquals(app.storage_node, None)
self.assertTrue(app.pt is not None)
self.assertEquals(app.uuid, your_uuid)
def test_storageAcceptNodeIdentification(self):
node = Mock({'setUUID': None})
class App:
nm = Mock({'getNodeByServer': node})
storage_node = None
pt = None
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageBootstrapHandler(app)
conn = self.getConnection()
uuid = self.getNewUUID()
app.uuid = 'C' * 16
client_handler.handleAcceptNodeIdentification(conn, None, STORAGE_NODE_TYPE,
uuid, '127.0.0.1', 10010,
0, 0, INVALID_UUID)
self.checkNotClosed(conn)
self.checkUUIDSet(conn, uuid)
self.assertEquals(app.pt, None)
self.assertEquals(app.uuid, 'C' * 16)
def _testHandleUnexpectedPacketCalledWithMedhod(self, method, args=(), kw=()):
self.assertRaises(UnexpectedPacketError, method, *args, **dict(kw))
# Master node handler
def test_nonMasterAnswerPrimaryMaster(self):
for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
node = Mock({'getNodeType': node_type})
class App:
nm = Mock({'getNodeByUUID': node, 'getNodeByServer': None, 'add': None})
trying_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
client_handler.handleAnswerPrimaryMaster(conn, None, 0, [])
# Check that nothing happened
self.assertEqual(len(app.nm.mockGetNamedCalls('getNodeByServer')), 0)
self.assertEqual(len(app.nm.mockGetNamedCalls('add')), 0)
def test_unknownNodeAnswerPrimaryMaster(self):
node = Mock({'getNodeType': MASTER_NODE_TYPE})
class App:
nm = Mock({'getNodeByServer': None, 'add': None})
primary_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
test_master_list = [('127.0.0.1', 10010, self.getNewUUID())]
client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
# Check that yet-unknown master node got added
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
add_call_list = app.nm.mockGetNamedCalls('add')
self.assertEqual(len(getNodeByServer_call_list), 1)
self.assertEqual(len(add_call_list), 1)
address, port, test_uuid = test_master_list[0]
getNodeByServer_call = getNodeByServer_call_list[0]
add_call = add_call_list[0]
self.assertEquals((address, port), getNodeByServer_call.getParam(0))
node_instance = add_call.getParam(0)
self.assertEquals(test_uuid, node_instance.getUUID())
# Check that primary master was not updated (it is not known yet,
# hence INVALID_UUID in call).
self.assertEquals(app.primary_master_node, None)
def test_knownNodeUnknownUUIDNodeAnswerPrimaryMaster(self):
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': None, 'setUUID': None})
class App:
nm = Mock({'getNodeByServer': node, 'add': None})
primary_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
test_node_uuid = self.getNewUUID()
test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
# Test sanity checks
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
self.assertEqual(len(getNodeByServer_call_list), 1)
self.assertEqual(getNodeByServer_call_list[0].getParam(0), test_master_list[0][:2])
# Check that known master node did not get added
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
add_call_list = app.nm.mockGetNamedCalls('add')
self.assertEqual(len(getNodeByServer_call_list), 1)
self.assertEqual(len(add_call_list), 0)
# Check that node UUID got updated
self.checkUUIDSet(node, test_node_uuid)
# Check that primary master was not updated (it is not known yet,
# hence INVALID_UUID in call).
self.assertEquals(app.primary_master_node, None)
def test_knownNodeKnownUUIDNodeAnswerPrimaryMaster(self):
test_node_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
class App:
nm = Mock({'getNodeByServer': node, 'add': None})
primary_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
# Test sanity checks
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
self.assertEqual(len(getNodeByServer_call_list), 1)
self.assertEqual(getNodeByServer_call_list[0].getParam(0), test_master_list[0][:2])
# Check that known master node did not get added
add_call_list = app.nm.mockGetNamedCalls('add')
self.assertEqual(len(add_call_list), 0)
# Check that node UUID was untouched
# XXX: should we just check that there was either no call or a call
# with same uuid, or enforce no call ? Here we enforce no call just
# because it's what implementation does.
self.checkNoUUIDSet(node)
# Check that primary master was not updated (it is not known yet,
# hence INVALID_UUID in call).
self.assertEquals(app.primary_master_node, None)
# TODO: test known node, known but different uuid (not detected in code,
# desired behaviour unknown)
def test_alreadyDifferentPrimaryAnswerPrimaryMaster(self):
test_node_uuid = self.getNewUUID()
test_primary_node_uuid = test_node_uuid
while test_primary_node_uuid == test_node_uuid:
test_primary_node_uuid = self.getNewUUID()
test_primary_master_node = Mock({'getUUID': test_primary_node_uuid})
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
class App:
nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
primary_master_node = test_primary_master_node
trying_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
# If primary master is already set *and* is not given primary master
# handle call raises.
# Check that the call doesn't raise
client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, [])
# Check that the primary master changed
self.assertTrue(app.primary_master_node is node)
# Test sanity checks
getNodeByUUID_call_list = app.nm.mockGetNamedCalls('getNodeByUUID')
self.assertEqual(len(getNodeByUUID_call_list), 1)
self.assertEqual(getNodeByUUID_call_list[0].getParam(0), test_node_uuid)
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
self.assertEqual(len(getNodeByServer_call_list), 0)
def test_alreadySamePrimaryAnswerPrimaryMaster(self):
test_node_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
class App:
nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
primary_master_node = node
trying_master_node = node
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, [])
# Check that primary node is (still) node.
self.assertTrue(app.primary_master_node is node)
def test_unknownNewPrimaryAnswerPrimaryMaster(self):
test_node_uuid = self.getNewUUID()
test_primary_node_uuid = test_node_uuid
while test_primary_node_uuid == test_node_uuid:
test_primary_node_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
class App:
nm = Mock({'getNodeByUUID': None, 'getNodeByServer': node, 'add': None})
primary_master_node = None
trying_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
client_handler.handleAnswerPrimaryMaster(conn, None, test_primary_node_uuid, [])
# Test sanity checks
getNodeByUUID_call_list = app.nm.mockGetNamedCalls('getNodeByUUID')
self.assertEqual(len(getNodeByUUID_call_list), 1)
self.assertEqual(getNodeByUUID_call_list[0].getParam(0), test_primary_node_uuid)
# Check that primary node was not updated.
self.assertTrue(app.primary_master_node is None)
def test_AnswerPrimaryMaster(self):
test_node_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
class App:
nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
primary_master_node = None
trying_master_node = None
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, test_master_list)
# Test sanity checks
getNodeByUUID_call_list = app.nm.mockGetNamedCalls('getNodeByUUID')
self.assertEqual(len(getNodeByUUID_call_list), 1)
self.assertEqual(getNodeByUUID_call_list[0].getParam(0), test_node_uuid)
getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
self.assertEqual(len(getNodeByServer_call_list), 1)
self.assertEqual(getNodeByServer_call_list[0].getParam(0), test_master_list[0][:2])
# Check that primary master was updated to known node
self.assertTrue(app.primary_master_node is node)
def test_initialSendPartitionTable(self):
client_handler = PrimaryBootstrapHandler(None)
conn = Mock({'getUUID': None})
self._testHandleUnexpectedPacketCalledWithMedhod(
client_handler.handleSendPartitionTable,
args=(conn, None, None, None))
def test_nonMasterSendPartitionTable(self):
for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
node = Mock({'getNodeType': node_type})
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock()
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
client_handler.handleSendPartitionTable(conn, None, 0, [])
# Check that nothing happened
self.assertEquals(len(app.pt.mockGetNamedCalls('setCell')), 0)
self.assertEquals(len(app.pt.mockGetNamedCalls('removeCell')), 0)
def test_newSendPartitionTable(self):
node = Mock({'getNodeType': MASTER_NODE_TYPE})
test_ptid = 0
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock({'clear': None})
ptid = test_ptid
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
client_handler.handleSendPartitionTable(conn, None, test_ptid + 1, [])
# Check that partition table got cleared and ptid got updated
self.assertEquals(app.ptid, test_ptid + 1)
self.assertEquals(len(app.pt.mockGetNamedCalls('clear')), 1)
def test_unknownNodeSendPartitionTable(self):
test_node = Mock({'getNodeType': MASTER_NODE_TYPE})
test_ptid = 0
class App:
nm = Mock({'getNodeByUUID': ReturnValues(test_node, None), 'add': None})
pt = Mock({'setCell': None})
ptid = test_ptid
test_storage_uuid = self.getNewUUID()
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
# TODO: use realistic values
test_row_list = [(0, [(test_storage_uuid, 0)])]
client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list)
# Check that node got created
add_call_list = app.nm.mockGetNamedCalls('add')
self.assertEquals(len(add_call_list), 1)
created_node = add_call_list[0].getParam(0)
self.assertEqual(created_node.getUUID(), test_storage_uuid)
# Check that partition table cell got added
setCell_call_list = app.pt.mockGetNamedCalls('setCell')
self.assertEquals(len(setCell_call_list), 1)
setCell_call_list[0].checkArgs(test_row_list[0][0], created_node,
test_row_list[0][1][0][1])
def test_knownNodeSendPartitionTable(self):
test_node = Mock({'getNodeType': MASTER_NODE_TYPE})
test_ptid = 0
class App:
nm = Mock({'getNodeByUUID': test_node, 'add': None})
pt = Mock({'setCell': None})
ptid = test_ptid
test_storage_uuid = self.getNewUUID()
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection()
# TODO: use realistic values
test_row_list = [(0, [(test_storage_uuid, 0)])]
client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list)
# Check that node did not get created
self.assertEquals(len(app.nm.mockGetNamedCalls('add')), 0)
# Check that partition table cell got added
setCell_call_list = app.pt.mockGetNamedCalls('setCell')
self.assertEquals(len(setCell_call_list), 1)
setCell_call_list[0].checkArgs(test_row_list[0][0], test_node,
test_row_list[0][1][0][1])
def test_nonMasterNotifyNodeInformation(self):
for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
test_master_uuid = self.getNewUUID()
node = Mock({'getNodeType': node_type})
class App:
nm = Mock({'getNodeByUUID': node})
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_master_uuid)
client_handler.handleNotifyNodeInformation(conn, None, ())
def test_nonIterableParameterRaisesNotifyNodeInformation(self):
# XXX: this test is here for sanity self-check: it verifies the
# assumption described in test_nonMasterNotifyNodeInformation
# by making a valid call with a non-iterable parameter given as
# node_list value.
test_master_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE})
class App:
nm = Mock({'getNodeByUUID': node})
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_master_uuid)
self.assertRaises(TypeError, client_handler.handleNotifyNodeInformation,
conn, None, None)
def _testNotifyNodeInformation(self, test_node, getNodeByServer=None, getNodeByUUID=MARKER):
invalid_uid_test_node = (test_node[0], test_node[1], test_node[2] + 1,
INVALID_UUID, test_node[4])
test_node_list = [test_node, invalid_uid_test_node]
test_master_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE})
if getNodeByUUID is not MARKER:
getNodeByUUID = ReturnValues(node, getNodeByUUID)
class App:
nm = Mock({'getNodeByUUID': getNodeByUUID,
'getNodeByServer': getNodeByServer,
'add': None,
'remove': None})
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = self.getConnection(uuid=test_master_uuid)
client_handler.handleNotifyNodeInformation(conn, None, test_node_list)
# Return nm so caller can check handler actions.
return app.nm
def test_unknownMasterNotifyNodeInformation(self):
# first notify unknown master nodes
uuid = self.getNewUUID()
test_node = (MASTER_NODE_TYPE, '127.0.0.1', 10010, uuid,
RUNNING_STATE)
nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=None)
# Check that two nodes got added (second is with INVALID_UUID)
add_call_list = nm.mockGetNamedCalls('add')
self.assertEqual(len(add_call_list), 2)
added_node = add_call_list[0].getParam(0)
self.assertEquals(added_node.getUUID(), uuid)
added_node = add_call_list[1].getParam(0)
self.assertEquals(added_node.getUUID(), None)
def test_knownMasterNotifyNodeInformation(self):
node = Mock({})
uuid = self.getNewUUID()
test_node = (MASTER_NODE_TYPE, '127.0.0.1', 10010, uuid,
RUNNING_STATE)
nm = self._testNotifyNodeInformation(test_node, getNodeByServer=node,
getNodeByUUID=node)
# Check that node got replaced
add_call_list = nm.mockGetNamedCalls('add')
self.assertEquals(len(add_call_list), 1)
remove_call_list = nm.mockGetNamedCalls('remove')
self.assertEquals(len(remove_call_list), 1)
# Check node state has been updated
setState_call_list = node.mockGetNamedCalls('setState')
self.assertEqual(len(setState_call_list), 1)
self.assertEqual(setState_call_list[0].getParam(0), test_node[4])
def test_unknownStorageNotifyNodeInformation(self):
test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getNewUUID(),
RUNNING_STATE)
nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=None)
# Check that node got added
add_call_list = nm.mockGetNamedCalls('add')
self.assertEqual(len(add_call_list), 1)
added_node = add_call_list[0].getParam(0)
# XXX: this test does not check that node state got updated.
# This is because there would be no way to tell the difference between
# an updated state and default state if they are the same value (we
# don't control node class/instance here)
# Likewise for server address and node uuid.
def test_knownStorageNotifyNodeInformation(self):
node = Mock({'setState': None, 'setServer': None})
test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getNewUUID(),
RUNNING_STATE)
nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=node)
# Check that node got replaced
add_call_list = nm.mockGetNamedCalls('add')
self.assertEquals(len(add_call_list), 1)
remove_call_list = nm.mockGetNamedCalls('remove')
self.assertEquals(len(remove_call_list), 1)
# Check node state has been updated
node = add_call_list[0].getParam(0)
self.assertEquals(node.getState(), test_node[4])
def test_initialNotifyPartitionChanges(self):
class App:
nm = None
pt = None
ptid = INVALID_PTID
app = App()
client_handler = PrimaryBootstrapHandler(app)
conn = Mock({'getUUID': None})
self._testHandleUnexpectedPacketCalledWithMedhod(
client_handler.handleNotifyPartitionChanges,
args=(conn, None, None, None))
def test_nonMasterNotifyPartitionChanges(self):
for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
test_master_uuid = self.getNewUUID()
node = Mock({'getNodeType': node_type, 'getUUID': test_master_uuid})
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock()
ptid = INVALID_PTID
primary_master_node = node
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_master_uuid)
client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
# Check that nothing happened
self.assertEquals(len(app.pt.mockGetNamedCalls('setCell')), 0)
self.assertEquals(len(app.pt.mockGetNamedCalls('removeCell')), 0)
def test_noPrimaryMasterNotifyPartitionChanges(self):
node = Mock({'getNodeType': MASTER_NODE_TYPE})
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock()
ptid = INVALID_PTID
primary_master_node = None
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection()
client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
# Check that nothing happened
self.assertEquals(len(app.pt.mockGetNamedCalls('setCell')), 0)
self.assertEquals(len(app.pt.mockGetNamedCalls('removeCell')), 0)
def test_nonPrimaryMasterNotifyPartitionChanges(self):
test_master_uuid = self.getNewUUID()
test_sender_uuid = test_master_uuid
while test_sender_uuid == test_master_uuid:
test_sender_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE})
test_master_node = Mock({'getUUID': test_master_uuid})
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock()
ptid = INVALID_PTID
primary_master_node = test_master_node
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_sender_uuid)
client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
# Check that nothing happened
self.assertEquals(len(app.pt.mockGetNamedCalls('setCell')), 0)
self.assertEquals(len(app.pt.mockGetNamedCalls('removeCell')), 0)
def test_ignoreOutdatedPTIDNotifyPartitionChanges(self):
test_master_uuid = self.getNewUUID()
node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid})
test_ptid = 1
class App:
nm = Mock({'getNodeByUUID': node})
pt = Mock()
primary_master_node = node
ptid = test_ptid
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_master_uuid)
client_handler.handleNotifyPartitionChanges(conn, None, test_ptid, [])
# Check that nothing happened
self.assertEquals(len(app.pt.mockGetNamedCalls('setCell')), 0)
self.assertEquals(len(app.pt.mockGetNamedCalls('removeCell')), 0)
self.assertEquals(app.ptid, test_ptid)
def test_unknownNodeNotifyPartitionChanges(self):
test_master_uuid = self.getNewUUID()
test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid})
test_ptid = 1
class App:
nm = Mock({'getNodeByUUID': ReturnValues(None)})
pt = Mock({'setCell': None})
primary_master_node = test_node
ptid = test_ptid
uuid = None # XXX: Is it really needed ?
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=test_master_uuid)
test_storage_uuid = self.getNewUUID()
# TODO: use realistic values
test_cell_list = [(0, test_storage_uuid, UP_TO_DATE_STATE)]
client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list)
# Check that a new node got added
add_call_list = app.nm.mockGetNamedCalls('add')
self.assertEqual(len(add_call_list), 1)
added_node = add_call_list[0].getParam(0)
# Check that partition got updated
self.assertEqual(app.ptid, test_ptid + 1)
setCell_call_list = app.pt.mockGetNamedCalls('setCell')
setCell_call_list[0].checkArgs(test_cell_list[0][0], added_node,
test_cell_list[0][2])
# TODO: confirm condition under which an unknown node should be added with a TEMPORARILY_DOWN_STATE (implementation is unclear)
def test_knownNodeNotifyPartitionChanges(self):
test_ptid = 1
uuid1, uuid2 = self.getNewUUID(), self.getNewUUID()
uuid3, uuid4 = self.getNewUUID(), self.getNewUUID()
test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': uuid1})
class App:
nm = Mock({'getNodeByUUID': ReturnValues(test_node, None, None, None), 'add': None})
pt = Mock({'setCell': None})
primary_master_node = test_node
ptid = test_ptid
uuid = uuid4
app = App()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection(uuid=uuid1)
test_cell_list = [
(0, uuid1, UP_TO_DATE_STATE),
(0, uuid2, DISCARDED_STATE),
(0, uuid3, FEEDING_STATE),
(0, uuid4, UP_TO_DATE_STATE),
]
client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list)
# Check that the three last node got added
calls = app.nm.mockGetNamedCalls('add')
self.assertEquals(len(calls), 3)
self.assertEquals(calls[0].getParam(0).getUUID(), uuid2)
self.assertEquals(calls[1].getParam(0).getUUID(), uuid3)
self.assertEquals(calls[2].getParam(0).getUUID(), uuid4)
self.assertEquals(calls[0].getParam(0).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(calls[1].getParam(0).getState(), TEMPORARILY_DOWN_STATE)
# and the others are updated
self.assertEqual(app.ptid, test_ptid + 1)
calls = app.pt.mockGetNamedCalls('setCell')
self.assertEqual(len(calls), 4)
self.assertEquals(calls[0].getParam(1).getUUID(), uuid1)
self.assertEquals(calls[1].getParam(1).getUUID(), uuid2)
self.assertEquals(calls[2].getParam(1).getUUID(), uuid3)
self.assertEquals(calls[3].getParam(1).getUUID(), uuid4)
def test_AnswerNewTID(self):
app = Mock({'setTID': None})
dispatcher = self.getDispatcher()
client_handler = PrimaryAnswersHandler(app)
conn = self.getConnection()
test_tid = 1
client_handler.handleAnswerNewTID(conn, None, test_tid)
setTID_call_list = app.mockGetNamedCalls('setTID')
self.assertEquals(len(setTID_call_list), 1)
self.assertEquals(setTID_call_list[0].getParam(0), test_tid)
def test_NotifyTransactionFinished(self):
test_tid = 1
app = Mock({'getTID': test_tid, 'setTransactionFinished': None})
dispatcher = self.getDispatcher()
client_handler = PrimaryAnswersHandler(app)
conn = self.getConnection()
client_handler.handleNotifyTransactionFinished(conn, None, test_tid)
self.assertEquals(len(app.mockGetNamedCalls('setTransactionFinished')), 1)
# TODO: decide what to do when non-current transaction is notified as finished, and test that behaviour
def test_InvalidateObjects(self):
class App:
def _cache_lock_acquire(self):
pass
def _cache_lock_release(self):
pass
def registerDB(self, db, limit):
self.db = db
def getDB(self):
return self.db
mq_cache = Mock({'__delitem__': None})
app = App()
dispatcher = self.getDispatcher()
client_handler = PrimaryNotificationsHandler(app, self.getDispatcher())
conn = self.getConnection()
test_tid = 1
test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
test_db = Mock({'invalidate': None})
app.registerDB(test_db, None)
client_handler.handleInvalidateObjects(conn, None, test_oid_list[:], test_tid)
# 'invalidate' is called just once
db = app.getDB()
self.assertTrue(db is test_db)
invalidate_call_list = db.mockGetNamedCalls('invalidate')
self.assertEquals(len(invalidate_call_list), 1)
invalidate_call = invalidate_call_list[0]
invalidate_tid = invalidate_call.getParam(0)
self.assertEquals(invalidate_tid, test_tid)
invalidate_oid_dict = invalidate_call.getParam(1)
self.assertEquals(len(invalidate_oid_dict), len(test_oid_list))
self.assertEquals(set(invalidate_oid_dict), set(test_oid_list))
self.assertEquals(set(invalidate_oid_dict.itervalues()), set([test_tid]))
# '__delitem__' is called once per invalidated object
delitem_call_list = app.mq_cache.mockGetNamedCalls('__delitem__')
self.assertEquals(len(delitem_call_list), len(test_oid_list))
oid_list = [x.getParam(0) for x in delitem_call_list]
self.assertEquals(set(oid_list), set(test_oid_list))
def test_AnswerNewOIDs(self):
class App:
new_oid_list = []
app = App()
dispatcher = self.getDispatcher()
client_handler = PrimaryAnswersHandler(app)
conn = self.getConnection()
test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
client_handler.handleAnswerNewOIDs(conn, None, test_oid_list[:])
self.assertEquals(set(app.new_oid_list), set(test_oid_list))
def test_StopOperation(self):
raise NotImplementedError
# Storage node handler
def test_AnswerObject(self):
class FakeLocal:
asked_object = ()
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
# TODO: use realistic values
test_object_data = ('\x00\x00\x00\x00\x00\x00\x00\x01', 0, 0, 0, 0, 'test')
client_handler.handleAnswerObject(conn, None, *test_object_data)
self.assertEquals(app.local_var.asked_object, test_object_data)
def _testAnswerStoreObject(self, app, conflicting, oid, serial):
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
client_handler.handleAnswerStoreObject(conn, None, conflicting, oid, serial)
def test_conflictingAnswerStoreObject(self):
class App:
local_var = threading.local()
app = App()
app.local_var.object_stored = (0, 0)
test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
test_serial = 1
self._testAnswerStoreObject(app, 1, test_oid, test_serial)
self.assertEqual(app.local_var.object_stored, (-1, test_serial))
def test_AnswerStoreObject(self):
class App:
local_var = threading.local()
app = App()
app.local_var.object_stored = (0, 0)
test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
test_serial = 1
self._testAnswerStoreObject(app, 0, test_oid, test_serial)
self.assertEqual(app.local_var.object_stored, (test_oid, test_serial))
def test_AnswerStoreTransaction(self):
test_tid = 10
app = Mock({'getTID': test_tid, 'setTransactionVoted': None})
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
client_handler.handleAnswerStoreTransaction(conn, None, test_tid)
self.assertEquals(len(app.mockGetNamedCalls('setTransactionVoted')), 1)
# TODO: test handleAnswerObject with test_tid not matching app.tid (not handled in program)
def test_AnswerTransactionInformation(self):
class FakeLocal:
txn_info = {}
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
tid = '\x00\x00\x00\x00\x00\x00\x00\x01' # TODO: use a more realistic tid
user = 'bar'
desc = 'foo'
ext = 0 # XXX: unused in implementation
oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
client_handler.handleAnswerTransactionInformation(conn, None, tid, user, desc, ext, oid_list[:])
stored_dict = app.local_var.txn_info
# TODO: test 'time' value ?
self.assertEquals(stored_dict['user_name'], user)
self.assertEquals(stored_dict['description'], desc)
self.assertEquals(stored_dict['id'], tid)
self.assertEquals(stored_dict['oids'], oid_list)
def test_AnswerObjectHistory(self):
class FakeLocal:
history = (0, [])
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
# TODO: use realistic values
test_history_list = [(1, 2), (3, 4)]
client_handler.handleAnswerObjectHistory(conn, None, test_oid, test_history_list[:])
oid, history = app.local_var.history
self.assertEquals(oid, test_oid)
self.assertEquals(len(history), len(test_history_list))
self.assertEquals(set(history), set(test_history_list))
def test_OidNotFound(self):
class FakeLocal:
asked_object = 0
history = 0
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
client_handler.handleOidNotFound(conn, None, None)
self.assertEquals(app.local_var.asked_object, -1)
self.assertEquals(app.local_var.history, -1)
def test_TidNotFound(self):
class FakeLocal:
txn_info = 0
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
client_handler.handleTidNotFound(conn, None, None)
self.assertEquals(app.local_var.txn_info, -1)
def test_AnswerTIDs(self):
class FakeLocal:
node_tids = {}
class App:
local_var = FakeLocal()
app = App()
dispatcher = self.getDispatcher()
client_handler = StorageAnswersHandler(app)
conn = self.getConnection()
test_tid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
client_handler.handleAnswerTIDs(conn, None, test_tid_list[:])
stored_tid_list = []
for tid_list in app.local_var.node_tids.itervalues():
stored_tid_list.extend(tid_list)
self.assertEquals(len(stored_tid_list), len(test_tid_list))
self.assertEquals(set(stored_tid_list), set(test_tid_list))
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/testConnectionPool.py 0000664 0000000 0000000 00000004026 11227104631 0027047 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest
from mock import Mock
from neo.tests.base import NeoTestBase
from neo.client.app import ConnectionPool
class ConnectionPoolTests(NeoTestBase):
def test_removeConnection(self):
app = None
pool = ConnectionPool(app)
test_node_uuid = self.getNewUUID()
other_node_uuid = test_node_uuid
while other_node_uuid == test_node_uuid:
other_node_uuid = self.getNewUUID()
test_node = Mock({'getUUID': test_node_uuid})
other_node = Mock({'getUUID': other_node_uuid})
# Test sanity check
self.assertEqual(getattr(pool, 'connection_dict', None), {})
# Call must not raise if node is not known
self.assertEqual(len(pool.connection_dict), 0)
pool.removeConnection(test_node)
# Test that removal with another uuid doesn't affect entry
pool.connection_dict[test_node_uuid] = None
self.assertEqual(len(pool.connection_dict), 1)
pool.removeConnection(other_node)
self.assertEqual(len(pool.connection_dict), 1)
# Test that removeConnection works
pool.removeConnection(test_node)
self.assertEqual(len(pool.connection_dict), 0)
# TODO: test getConnForNode (requires splitting complex functionalities)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/testDispatcher.py 0000664 0000000 0000000 00000004555 11227104631 0026213 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest
from neo.client.dispatcher import Dispatcher
class DispatcherTests(unittest.TestCase):
def test_register(self):
dispatcher = Dispatcher()
conn = []
other_conn = []
queue = []
test_message_id = 1
class Packet:
def __init__(self, msg_id):
self.msg_id = msg_id
def getId(self):
return self.msg_id
packet = Packet(test_message_id)
other_packet = Packet(test_message_id + 1)
# Check that unregistered message is detected as unregistered
self.assertEqual(dispatcher.getQueue(conn, packet), None)
self.assertFalse(dispatcher.registered(conn))
# Register (must not raise)
dispatcher.register(conn, test_message_id, queue)
# Check that connection is detected as registered
self.assertTrue(dispatcher.registered(conn))
# Check that variations don't get detected as registered
self.assertFalse(dispatcher.registered(other_conn))
self.assertEqual(dispatcher.getQueue(other_conn, packet), None)
self.assertEqual(dispatcher.getQueue(conn, other_packet), None)
self.assertEqual(dispatcher.getQueue(other_conn, other_packet), None)
# Check that queue is detected as registered.
# This unregisters the message...
self.assertTrue(dispatcher.getQueue(conn, packet) is queue)
# so check again that unregistered message is detected as unregistered
self.assertEqual(dispatcher.getQueue(conn, packet), None)
self.assertFalse(dispatcher.registered(conn))
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/client/tests/testZODB.py 0000664 0000000 0000000 00000076000 11227104631 0024655 0 ustar 00root root 0000000 0000000 ##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import unittest
import ZODB
import ZODB.FileStorage
from ZODB.POSException import ReadConflictError, ConflictError
from ZODB.POSException import TransactionFailedError
from ZODB.tests.warnhook import WarningsHook
from persistent import Persistent
from persistent.mapping import PersistentMapping
import transaction
from neo.client.Storage import Storage
import os
import sys
import signal
import MySQLdb
import logging
import tempfile
import traceback
class P(Persistent):
pass
class Independent(Persistent):
def _p_independent(self):
return 1
class DecoyIndependent(Persistent):
def _p_independent(self):
return 0
# No need to protect this list with a lock, NEOProcess instanciations and
# killallNeo calls are done from a single thread.
neo_process_list = []
class NEOProcess:
pid = 0
def __init__(self, command, *args):
self.pid = os.fork()
if self.pid == 0:
# Child
try:
os.execlp(command, command, *args)
except:
print traceback.format_exc()
# If we reach this line, exec call failed (is it possible to reach
# it without going through above "except" branch ?).
print 'Error executing %r.' % (command + ' '.join(args), )
# KeyboardInterrupt is not intercepted by test runner (it is still
# above us in the stack), and we do want to exit.
# To avoid polluting test foreground output with induced
# traceback, replace stdout & stderr.
sys.stdout = sys.stderr = open('/dev/null', 'w')
raise KeyboardInterrupt
else:
neo_process_list.append(self)
def kill(self, sig=signal.SIGTERM):
if self.pid:
try:
os.kill(self.pid, sig)
except OSError:
traceback.print_last()
def __del__(self):
# If we get killed, kill subprocesses aswell.
try:
self.kill(signal.SIGKILL)
except:
# We can ignore all exceptions at this point, since there is no
# garanteed way to handle them (other objects we would depend on
# might already have been deleted).
pass
def wait(self, options=0):
assert self.pid
return os.WEXITSTATUS(os.waitpid(self.pid, options)[1])
def killallNeo():
while len(neo_process_list):
process = neo_process_list.pop()
process.kill()
process.wait()
NEO_MASTER = 'neomaster'
NEO_STORAGE = 'neostorage'
NEO_PORT_BASE = 10010
NEO_CLUSTER_NAME = 'test'
NEO_MASTER_PORT_1 = NEO_PORT_BASE
NEO_MASTER_PORT_2 = NEO_MASTER_PORT_1 + 1
NEO_MASTER_PORT_3 = NEO_MASTER_PORT_2 + 1
NEO_STORAGE_PORT_1 = NEO_MASTER_PORT_3 + 1
NEO_STORAGE_PORT_2 = NEO_STORAGE_PORT_1 + 1
NEO_STORAGE_PORT_3 = NEO_STORAGE_PORT_2 + 1
NEO_STORAGE_PORT_4 = NEO_STORAGE_PORT_3 + 1
NEO_MASTER_NODES = '127.0.0.1:%(port_1)s 127.0.0.1:%(port_2)s 127.0.0.1:%(port_3)s' % {
'port_1': NEO_MASTER_PORT_1,
'port_2': NEO_MASTER_PORT_2,
'port_3': NEO_MASTER_PORT_3
}
NEO_SQL_USER = 'test'
NEO_SQL_PASSWORD = ''
NEO_SQL_DATABASE_1 = 'test_neo1'
NEO_SQL_DATABASE_2 = 'test_neo2'
NEO_SQL_DATABASE_3 = 'test_neo3'
NEO_SQL_DATABASE_4 = 'test_neo4'
# Used to create & drop above databases and grant test users privileges.
SQL_ADMIN_USER = 'root'
SQL_ADMIN_PASSWORD = None
NEO_CONFIG = '''
# Default parameters.
[DEFAULT]
# The list of master nodes.
master_nodes: %(master_nodes)s
# The number of replicas.
replicas: 2
# The number of partitions.
partitions: 1009
# The name of this cluster.
name: %(name)s
# The user name for the database.
user: %(user)s
# The password for the database.
password: %(password)s
# The connector class used
connector: SocketConnector
# The first master.
[master1]
server: 127.0.0.1:%(master1_port)s
# The second master.
[master2]
server: 127.0.0.1:%(master2_port)s
# The third master.
[master3]
server: 127.0.0.1:%(master3_port)s
# The first storage.
[storage1]
database: %(storage1_db)s
server: 127.0.0.1:%(storage1_port)s
# The first storage.
[storage2]
database: %(storage2_db)s
server: 127.0.0.1:%(storage2_port)s
# The third storage.
[storage3]
database: %(storage3_db)s
server: 127.0.0.1:%(storage3_port)s
# The fourth storage.
[storage4]
database: %(storage4_db)s
server: 127.0.0.1:%(storage4_port)s
''' % {
'master_nodes': NEO_MASTER_NODES,
'name': NEO_CLUSTER_NAME,
'user': NEO_SQL_USER,
'password': NEO_SQL_PASSWORD,
'master1_port': NEO_MASTER_PORT_1,
'master2_port': NEO_MASTER_PORT_2,
'master3_port': NEO_MASTER_PORT_3,
'storage1_port': NEO_STORAGE_PORT_1,
'storage1_db': NEO_SQL_DATABASE_1,
'storage2_port': NEO_STORAGE_PORT_2,
'storage2_db': NEO_SQL_DATABASE_2,
'storage3_port': NEO_STORAGE_PORT_3,
'storage3_db': NEO_SQL_DATABASE_3,
'storage4_port': NEO_STORAGE_PORT_4,
'storage4_db': NEO_SQL_DATABASE_4
}
temp_dir = tempfile.mkdtemp(prefix='neo_')
print 'Using temp directory %r.' % (temp_dir, )
config_file_path = os.path.join(temp_dir, 'neo.conf')
config_file = open(config_file_path, 'w')
config_file.write(NEO_CONFIG)
config_file.close()
m1_log = os.path.join(temp_dir, 'm1.log')
m2_log = os.path.join(temp_dir, 'm2.log')
m3_log = os.path.join(temp_dir, 'm3.log')
s1_log = os.path.join(temp_dir, 's1.log')
s2_log = os.path.join(temp_dir, 's2.log')
s3_log = os.path.join(temp_dir, 's3.log')
s4_log = os.path.join(temp_dir, 's4.log')
# override logging default handler
from neo import buildFormatString
client_log = os.path.join(temp_dir, 'c.log')
format = buildFormatString('CLIENT')
logging.basicConfig(filename=client_log, level=logging.DEBUG, format=format)
class ZODBTests(unittest.TestCase):
def setUp(self):
# Stop NEO cluster (if running)
killallNeo()
# Cleanup or bootstrap databases
connect_arg_dict = {'user': SQL_ADMIN_USER}
if SQL_ADMIN_PASSWORD is not None:
connect_arg_dict['passwd'] = SQL_ADMIN_PASSWORD
sql_connection = MySQLdb.Connect(**connect_arg_dict)
cursor = sql_connection.cursor()
for database in (NEO_SQL_DATABASE_1, NEO_SQL_DATABASE_2, NEO_SQL_DATABASE_3, NEO_SQL_DATABASE_4):
cursor.execute('DROP DATABASE IF EXISTS %s' % (database, ))
cursor.execute('CREATE DATABASE %s' % (database, ))
cursor.execute('GRANT ALL ON %s.* TO "%s"@"localhost" IDENTIFIED BY "%s"' % (database, NEO_SQL_USER, NEO_SQL_PASSWORD))
cursor.close()
sql_connection.close()
# Start NEO cluster
NEOProcess(NEO_MASTER, '-vc', config_file_path, '-s', 'master1', '-l', m1_log)
NEOProcess(NEO_MASTER, '-vc', config_file_path, '-s', 'master2', '-l', m2_log)
NEOProcess(NEO_MASTER, '-vc', config_file_path, '-s', 'master3', '-l', m3_log)
NEOProcess(NEO_STORAGE, '-vRc', config_file_path, '-s', 'storage1', '-l', s1_log)
NEOProcess(NEO_STORAGE, '-vRc', config_file_path, '-s', 'storage2', '-l', s2_log)
NEOProcess(NEO_STORAGE, '-vRc', config_file_path, '-s', 'storage3', '-l', s3_log)
NEOProcess(NEO_STORAGE, '-vRc', config_file_path, '-s', 'storage4', '-l', s4_log)
# Send Storage output to a logfile
self._storage = Storage(
master_nodes=NEO_MASTER_NODES,
name=NEO_CLUSTER_NAME,
connector='SocketConnector')
self._db = ZODB.DB(self._storage)
def populate(self):
transaction.begin()
conn = self._db.open()
root = conn.root()
root['test'] = pm = PersistentMapping()
for n in range(100):
pm[n] = PersistentMapping({0: 100 - n})
transaction.get().note('created test data')
transaction.commit()
conn.close()
def tearDown(self):
self._db.close()
self._storage.cleanup()
killallNeo()
def checkExportImport(self, abort_it=False):
self.populate()
conn = self._db.open()
try:
self.duplicate(conn, abort_it)
finally:
conn.close()
conn = self._db.open()
try:
self.verify(conn, abort_it)
finally:
conn.close()
def duplicate(self, conn, abort_it):
transaction.begin()
transaction.get().note('duplication')
root = conn.root()
ob = root['test']
assert len(ob) > 10, 'Insufficient test data'
try:
import tempfile
f = tempfile.TemporaryFile()
ob._p_jar.exportFile(ob._p_oid, f)
assert f.tell() > 0, 'Did not export correctly'
f.seek(0)
new_ob = ob._p_jar.importFile(f)
self.assertEqual(new_ob, ob)
root['dup'] = new_ob
f.close()
if abort_it:
transaction.abort()
else:
transaction.commit()
except:
transaction.abort()
raise
def verify(self, conn, abort_it):
transaction.begin()
root = conn.root()
ob = root['test']
try:
ob2 = root['dup']
except KeyError:
if abort_it:
# Passed the test.
return
else:
raise
else:
self.failUnless(not abort_it, 'Did not abort duplication')
l1 = list(ob.items())
l1.sort()
l2 = list(ob2.items())
l2.sort()
l1 = map(lambda (k, v): (k, v[0]), l1)
l2 = map(lambda (k, v): (k, v[0]), l2)
self.assertEqual(l1, l2)
self.assert_(ob._p_oid != ob2._p_oid)
self.assertEqual(ob._p_jar, ob2._p_jar)
oids = {}
for v in ob.values():
oids[v._p_oid] = 1
for v in ob2.values():
assert not oids.has_key(v._p_oid), (
'Did not fully separate duplicate from original')
transaction.commit()
def checkExportImportAborted(self):
self.checkExportImport(abort_it=True)
def checkVersionOnly(self):
# Make sure the changes to make empty transactions a no-op
# still allow things like abortVersion(). This should work
# because abortVersion() calls tpc_begin() itself.
conn = self._db.open("version")
try:
r = conn.root()
r[1] = 1
transaction.commit()
finally:
conn.close()
self._db.abortVersion("version")
transaction.commit()
def checkResetCache(self):
# The cache size after a reset should be 0. Note that
# _resetCache is not a public API, but the resetCaches()
# function is, and resetCaches() causes _resetCache() to be
# called.
self.populate()
conn = self._db.open()
conn.root()
self.assert_(len(conn._cache) > 0) # Precondition
conn._resetCache()
self.assertEqual(len(conn._cache), 0)
def checkResetCachesAPI(self):
# Checks the resetCaches() API.
# (resetCaches used to be called updateCodeTimestamp.)
self.populate()
conn = self._db.open()
conn.root()
self.assert_(len(conn._cache) > 0) # Precondition
ZODB.Connection.resetCaches()
conn.close()
self.assert_(len(conn._cache) > 0) # Still not flushed
conn._setDB(self._db) # simulate the connection being reopened
self.assertEqual(len(conn._cache), 0)
def checkExplicitTransactionManager(self):
# Test of transactions that apply to only the connection,
# not the thread.
tm1 = transaction.TransactionManager()
conn1 = self._db.open(transaction_manager=tm1)
tm2 = transaction.TransactionManager()
conn2 = self._db.open(transaction_manager=tm2)
try:
r1 = conn1.root()
r2 = conn2.root()
if r1.has_key('item'):
del r1['item']
tm1.get().commit()
r1.get('item')
r2.get('item')
r1['item'] = 1
tm1.get().commit()
self.assertEqual(r1['item'], 1)
# r2 has not seen a transaction boundary,
# so it should be unchanged.
self.assertEqual(r2.get('item'), None)
conn2.sync()
# Now r2 is updated.
self.assertEqual(r2['item'], 1)
# Now, for good measure, send an update in the other direction.
r2['item'] = 2
tm2.get().commit()
self.assertEqual(r1['item'], 1)
self.assertEqual(r2['item'], 2)
conn1.sync()
conn2.sync()
self.assertEqual(r1['item'], 2)
self.assertEqual(r2['item'], 2)
finally:
conn1.close()
conn2.close()
def checkLocalTransactions(self):
# Test of transactions that apply to only the connection,
# not the thread.
conn1 = self._db.open()
conn2 = self._db.open()
hook = WarningsHook()
hook.install()
try:
conn1.setLocalTransaction()
conn2.setLocalTransaction()
r1 = conn1.root()
r2 = conn2.root()
if r1.has_key('item'):
del r1['item']
conn1.getTransaction().commit()
r1.get('item')
r2.get('item')
r1['item'] = 1
conn1.getTransaction().commit()
self.assertEqual(r1['item'], 1)
# r2 has not seen a transaction boundary,
# so it should be unchanged.
self.assertEqual(r2.get('item'), None)
conn2.sync()
# Now r2 is updated.
self.assertEqual(r2['item'], 1)
# Now, for good measure, send an update in the other direction.
r2['item'] = 2
conn2.getTransaction().commit()
self.assertEqual(r1['item'], 1)
self.assertEqual(r2['item'], 2)
conn1.sync()
conn2.sync()
self.assertEqual(r1['item'], 2)
self.assertEqual(r2['item'], 2)
for msg, obj, filename, lineno in hook.warnings:
self.assert_(msg in [
"This will be removed in ZODB 3.6:\n"
"setLocalTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead.",
"This will be removed in ZODB 3.6:\n"
"getTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead, or access "
".transaction_manager directly on the Connection."])
finally:
conn1.close()
conn2.close()
hook.uninstall()
def checkReadConflict(self):
self.obj = P()
self.readConflict()
def readConflict(self, shouldFail=True):
# Two transactions run concurrently. Each reads some object,
# then one commits and the other tries to read an object
# modified by the first. This read should fail with a conflict
# error because the object state read is not necessarily
# consistent with the objects read earlier in the transaction.
tm1 = transaction.TransactionManager()
conn = self._db.open(mvcc=False, transaction_manager=tm1)
r1 = conn.root()
r1["p"] = self.obj
self.obj.child1 = P()
tm1.get().commit()
# start a new transaction with a new connection
tm2 = transaction.TransactionManager()
cn2 = self._db.open(mvcc=False, transaction_manager=tm2)
# start a new transaction with the other connection
r2 = cn2.root()
self.assertEqual(r1._p_serial, r2._p_serial)
self.obj.child2 = P()
tm1.get().commit()
# resume the transaction using cn2
obj = r2["p"]
# An attempt to access obj should fail, because r2 was read
# earlier in the transaction and obj was modified by the othe
# transaction.
if shouldFail:
self.assertRaises(ReadConflictError, lambda: obj.child1)
# And since ReadConflictError was raised, attempting to commit
# the transaction should re-raise it. checkNotIndependent()
# failed this part of the test for a long time.
self.assertRaises(ReadConflictError, tm2.get().commit)
# And since that commit failed, trying to commit again should
# fail again.
self.assertRaises(TransactionFailedError, tm2.get().commit)
# And again.
self.assertRaises(TransactionFailedError, tm2.get().commit)
# Etc.
self.assertRaises(TransactionFailedError, tm2.get().commit)
else:
# make sure that accessing the object succeeds
obj.child1
tm2.get().abort()
def checkReadConflictIgnored(self):
# Test that an application that catches a read conflict and
# continues can not commit the transaction later.
root = self._db.open(mvcc=False).root()
root["real_data"] = real_data = PersistentMapping()
root["index"] = index = PersistentMapping()
real_data["a"] = PersistentMapping({"indexed_value": 0})
real_data["b"] = PersistentMapping({"indexed_value": 1})
index[1] = PersistentMapping({"b": 1})
index[0] = PersistentMapping({"a": 1})
transaction.commit()
# load some objects from one connection
tm = transaction.TransactionManager()
cn2 = self._db.open(mvcc=False, transaction_manager=tm)
r2 = cn2.root()
real_data2 = r2["real_data"]
index2 = r2["index"]
real_data["b"]["indexed_value"] = 0
del index[1]["b"]
index[0]["b"] = 1
transaction.commit()
del real_data2["a"]
try:
del index2[0]["a"]
except ReadConflictError:
# This is the crux of the text. Ignore the error.
pass
else:
self.fail("No conflict occurred")
# real_data2 still ready to commit
self.assert_(real_data2._p_changed)
# index2 values not ready to commit
self.assert_(not index2._p_changed)
self.assert_(not index2[0]._p_changed)
self.assert_(not index2[1]._p_changed)
self.assertRaises(ReadConflictError, tm.get().commit)
self.assertRaises(TransactionFailedError, tm.get().commit)
tm.get().abort()
def checkIndependent(self):
self.obj = Independent()
self.readConflict(shouldFail=False)
def checkNotIndependent(self):
self.obj = DecoyIndependent()
self.readConflict()
def checkSubtxnCommitDoesntGetInvalidations(self):
# Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed
# invalidations even for a subtxn commit. This could make
# inconsistent state visible after a subtxn commit. There was a
# suspicion that POSKeyError was possible as a result, but I wasn't
# able to construct a case where that happened.
# Set up the database, to hold
# root --> "p" -> value = 1
# --> "q" -> value = 2
tm1 = transaction.TransactionManager()
conn = self._db.open(transaction_manager=tm1)
r1 = conn.root()
p = P()
p.value = 1
r1["p"] = p
q = P()
q.value = 2
r1["q"] = q
tm1.commit()
# Now txn T1 changes p.value to 3 locally (subtxn commit).
p.value = 3
tm1.commit(True)
# Start new txn T2 with a new connection.
tm2 = transaction.TransactionManager()
cn2 = self._db.open(transaction_manager=tm2)
r2 = cn2.root()
p2 = r2["p"]
self.assertEqual(p._p_oid, p2._p_oid)
# T2 shouldn't see T1's change of p.value to 3, because T1 didn't
# commit yet.
self.assertEqual(p2.value, 1)
# Change p.value to 4, and q.value to 5. Neither should be visible
# to T1, because T1 is still in progress.
p2.value = 4
q2 = r2["q"]
self.assertEqual(q._p_oid, q2._p_oid)
self.assertEqual(q2.value, 2)
q2.value = 5
tm2.commit()
# Back to T1. p and q still have the expected values.
rt = conn.root()
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
# Now do another subtxn commit in T1. This shouldn't change what
# T1 sees for p and q.
rt["r"] = P()
tm1.commit(True)
# Doing that subtxn commit in T1 should not process invalidations
# from T2's commit. p.value should still be 3 here (because that's
# what T1 subtxn-committed earlier), and q.value should still be 2.
# Prior to ZODB 3.2.9 and 3.4, q.value was 5 here.
rt = conn.root()
try:
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
finally:
tm1.abort()
def checkReadConflictErrorClearedDuringAbort(self):
# When a transaction is aborted, the "memory" of which
# objects were the cause of a ReadConflictError during
# that transaction should be cleared.
root = self._db.open(mvcc=False).root()
data = PersistentMapping({'d': 1})
root["data"] = data
transaction.commit()
# Provoke a ReadConflictError.
tm2 = transaction.TransactionManager()
cn2 = self._db.open(mvcc=False, transaction_manager=tm2)
r2 = cn2.root()
data2 = r2["data"]
data['d'] = 2
transaction.commit()
try:
data2['d'] = 3
except ReadConflictError:
pass
else:
self.fail("No conflict occurred")
# Explicitly abort cn2's transaction.
tm2.get().abort()
# cn2 should retain no memory of the read conflict after an abort(),
# but 3.2.3 had a bug wherein it did.
data_conflicts = data._p_jar._conflicts
data2_conflicts = data2._p_jar._conflicts
self.failIf(data_conflicts)
self.failIf(data2_conflicts) # this used to fail
# And because of that, we still couldn't commit a change to data2['d']
# in the new transaction.
cn2.sync() # process the invalidation for data2['d']
data2['d'] = 3
tm2.get().commit() # 3.2.3 used to raise ReadConflictError
cn2.close()
def checkTxnBeginImpliesAbort(self):
# begin() should do an abort() first, if needed.
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
transaction.begin() # should abort adding 'a' to the root
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# A longstanding bug: this didn't work if changes were only in
# subtransactions.
transaction.begin()
rt = cn.root()
rt['a'] = 2
transaction.commit(1)
transaction.begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# One more time, mixing "top level" and subtransaction changes.
transaction.begin()
rt = cn.root()
rt['a'] = 3
transaction.commit(1)
rt['b'] = 4
transaction.begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
self.assertRaises(KeyError, rt.__getitem__, 'b')
# That used methods of the default transaction *manager*. Alas,
# that's not necessarily the same as using methods of the current
# transaction, and, in fact, when this test was written,
# Transaction.begin() didn't do anything (everything from here
# down failed).
# Oh, bleech. Since Transaction.begin is also deprecated, we have
# to goof around suppressing the deprecation warning.
import warnings
# First verify that Transaction.begin *is* deprecated, by turning
# the warning into an error.
warnings.filterwarnings("error", category=DeprecationWarning)
self.assertRaises(DeprecationWarning, transaction.get().begin)
del warnings.filters[0]
# Now ignore DeprecationWarnings for the duration. Use a
# try/finally block to ensure we reenable DeprecationWarnings
# no matter what.
warnings.filterwarnings("ignore", category=DeprecationWarning)
try:
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
transaction.get().begin() # should abort adding 'a' to the root
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# A longstanding bug: this didn't work if changes were only in
# subtransactions.
transaction.get().begin()
rt = cn.root()
rt['a'] = 2
transaction.get().commit(1)
transaction.get().begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# One more time, mixing "top level" and subtransaction changes.
transaction.get().begin()
rt = cn.root()
rt['a'] = 3
transaction.get().commit(1)
rt['b'] = 4
transaction.get().begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
self.assertRaises(KeyError, rt.__getitem__, 'b')
cn.close()
finally:
del warnings.filters[0]
def checkFailingCommitSticks(self):
# See also checkFailingSubtransactionCommitSticks.
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
# Arrange for commit to fail during tpc_vote.
poisoned = PoisonedObject(PoisonedJar(break_tpc_vote=True))
transaction.get().register(poisoned)
self.assertRaises(PoisonedError, transaction.get().commit)
# Trying to commit again fails too.
self.assertRaises(TransactionFailedError, transaction.get().commit)
self.assertRaises(TransactionFailedError, transaction.get().commit)
self.assertRaises(TransactionFailedError, transaction.get().commit)
# The change to rt['a'] is lost.
self.assertRaises(KeyError, rt.__getitem__, 'a')
# Trying to modify an object also fails, because Transaction.join()
# also raises TransactionFailedError.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via abort(), and try again.
transaction.get().abort()
rt['a'] = 1
transaction.get().commit()
self.assertEqual(rt['a'], 1)
# Cleaning up via begin() should also work.
rt['a'] = 2
transaction.get().register(poisoned)
self.assertRaises(PoisonedError, transaction.get().commit)
self.assertRaises(TransactionFailedError, transaction.get().commit)
# The change to rt['a'] is lost.
self.assertEqual(rt['a'], 1)
# Trying to modify an object also fails.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via begin(), and try again.
transaction.begin()
rt['a'] = 2
transaction.get().commit()
self.assertEqual(rt['a'], 2)
cn.close()
def checkFailingSubtransactionCommitSticks(self):
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
transaction.get().commit(True)
self.assertEqual(rt['a'], 1)
rt['b'] = 2
# Subtransactions don't do tpc_vote, so we poison tpc_begin.
poisoned = PoisonedJar()
transaction.get().join(poisoned)
poisoned.break_savepoint = True
self.assertRaises(PoisonedError, transaction.get().commit, True)
# Trying to subtxn-commit again fails too.
self.assertRaises(TransactionFailedError,
transaction.get().commit, True)
self.assertRaises(TransactionFailedError,
transaction.get().commit, True)
# Top-level commit also fails.
self.assertRaises(TransactionFailedError, transaction.get().commit)
# The changes to rt['a'] and rt['b'] are lost.
self.assertRaises(KeyError, rt.__getitem__, 'a')
self.assertRaises(KeyError, rt.__getitem__, 'b')
# Trying to modify an object also fails, because Transaction.join()
# also raises TransactionFailedError.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via abort(), and try again.
transaction.get().abort()
rt['a'] = 1
transaction.get().commit()
self.assertEqual(rt['a'], 1)
# Cleaning up via begin() should also work.
rt['a'] = 2
poisoned = PoisonedJar()
transaction.get().join(poisoned)
poisoned.break_savepoint = True
self.assertRaises(PoisonedError, transaction.get().commit, True)
self.assertRaises(TransactionFailedError,
transaction.get().commit, True)
# The change to rt['a'] is lost.
self.assertEqual(rt['a'], 1)
# Trying to modify an object also fails.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via begin(), and try again.
transaction.begin()
rt['a'] = 2
transaction.get().commit(True)
self.assertEqual(rt['a'], 2)
transaction.get().commit()
cn2 = self._db.open()
rt = cn.root()
self.assertEqual(rt['a'], 2)
cn.close()
cn2.close()
class PoisonedError(Exception):
pass
# PoisonedJar arranges to raise exceptions from interesting places.
# For whatever reason, subtransaction commits don't call tpc_vote.
class PoisonedJar:
def __init__(self, break_tpc_begin=False, break_tpc_vote=False,
break_savepoint=False):
self.break_tpc_begin = break_tpc_begin
self.break_tpc_vote = break_tpc_vote
self.break_savepoint = break_savepoint
def sortKey(self):
return str(id(self))
# A way to poison a subtransaction commit.
def tpc_begin(self, *args):
if self.break_tpc_begin:
raise PoisonedError("tpc_begin fails")
# A way to poison a top-level commit.
def tpc_vote(self, *args):
if self.break_tpc_vote:
raise PoisonedError("tpc_vote fails")
def savepoint(self):
if self.break_savepoint:
raise PoisonedError("savepoint fails")
def commit(*args):
pass
def abort(*self):
pass
class PoisonedObject:
def __init__(self, poisonedjar):
self._p_jar = poisonedjar
def test_suite():
return unittest.makeSuite(ZODBTests, 'check')
if __name__ == "__main__":
unittest.main(defaultTest="test_suite")
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/component.xml 0000664 0000000 0000000 00000001253 11227104631 0022747 0 ustar 00root root 0000000 0000000
A scalable storage for Zope
Give the list of the master node like ip:port ip:port...
Give the name of the cluster
Give the name of the connector used at low-level
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/config.py 0000664 0000000 0000000 00000006042 11227104631 0022043 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from ConfigParser import SafeConfigParser
import logging
class ConfigurationManager:
"""This class provides support for parsing a configuration file."""
# The default values for a config file.
default_config_dict = {'database' : 'test',
'user' : 'test',
'password' : None,
'server' : '127.0.0.1',
'connector' : 'SocketConnector',
'master_nodes' : '',
'replicas' : '0',
'partitions' : '1009',
'name' : 'main'}
# The default port to which master nodes listen when none is specified.
default_master_port = 10100
def __init__(self, file, section):
parser = SafeConfigParser(self.default_config_dict)
logging.debug('reading a configuration from %s', file)
parser.read(file)
self.parser = parser
self.section = section
def __getitem__(self, key):
return self.parser.get(self.section, key)
def getDatabase(self):
return self['database']
def getUser(self):
return self['user']
def getPassword(self):
return self['password']
def getServer(self):
server = self['server']
if ':' in server:
ip_address, port = server.split(':')
port = int(port)
else:
ip_address = server
port = self.default_master_port
return ip_address, port
def getReplicas(self):
return int(self['replicas'])
def getPartitions(self):
return int(self['partitions'])
def getConnector(self):
return str(self['connector'])
def getName(self):
return self['name']
def getMasterNodeList(self):
master_nodes = self['master_nodes']
master_node_list = []
# A list of master nodes separated by whitespace.
for node in master_nodes.split():
if not node:
continue
if ':' in node:
ip_address, port = node.split(':')
port = int(port)
else:
ip_address = node
port = self.default_master_port
master_node_list.append((ip_address, port))
return master_node_list
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/connection.py 0000664 0000000 0000000 00000045214 11227104631 0022741 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.locking import RLock
from neo import protocol
from neo.protocol import PacketMalformedError
from neo.event import IdleEvent
from neo.connector import ConnectorException, ConnectorTryAgainException, \
ConnectorInProgressException, ConnectorConnectionRefusedException, \
ConnectorConnectionClosedException
from neo.util import dump
def not_closed(func):
def decorator(self, *args, **kw):
if self.connector is None:
raise ConnectorConnectionClosedException
return func(self, *args, **kw)
return decorator
def lockCheckWrapper(func):
"""
This function is to be used as a wrapper around
MT(Client|Server)Connection class methods.
It uses a "_" method on RLock class, so it might stop working without
notice (sadly, RLock does not offer any "acquired" method, but that one
will do as it checks that current thread holds this lock).
It requires moniroted class to have an RLock instance in self._lock
property.
"""
def wrapper(self, *args, **kw):
if not self._lock._is_owned():
import traceback
logging.warning('%s called on %s instance without being locked. Stack:\n%s', func.func_code.co_name, self.__class__.__name__, ''.join(traceback.format_stack()))
# Call anyway
return func(self, *args, **kw)
return wrapper
class BaseConnection(object):
"""A base connection."""
def __init__(self, event_manager, handler, connector = None,
addr = None, connector_handler = None):
self.em = event_manager
self.connector = connector
self.addr = addr
self.handler = handler
if connector is not None:
self.connector_handler = connector.__class__
event_manager.register(self)
else:
self.connector_handler = connector_handler
def lock(self):
return 1
def unlock(self):
return None
def getConnector(self):
return self.connector
def getDescriptor(self):
return self.connector.getDescriptor()
def setConnector(self, connector):
if self.connector is not None:
raise RuntimeError, 'cannot overwrite a connector in a connection'
if connector is not None:
self.connector = connector
self.em.register(self)
def getAddress(self):
return self.addr
def readable(self):
raise NotImplementedError
def writable(self):
raise NotImplementedError
def close(self):
"""Close the connection."""
em = self.em
if self.connector is not None:
em.removeReader(self)
em.removeWriter(self)
em.unregister(self)
self.connector.shutdown()
self.connector.close()
self.connector = None
__del__ = close
def getHandler(self):
return self.handler
def setHandler(self, handler):
self.handler = handler
def getEventManager(self):
return self.em
def getUUID(self):
return None
def isListeningConnection(self):
raise NotImplementedError
def hasPendingMessages(self):
return False
class ListeningConnection(BaseConnection):
"""A listen connection."""
def __init__(self, event_manager, handler, addr = None,
connector_handler = None, **kw):
logging.debug('listening to %s:%d', *addr)
BaseConnection.__init__(self, event_manager, handler,
addr = addr,
connector_handler = connector_handler)
connector = self.connector_handler()
connector.makeListeningConnection(addr)
self.setConnector(connector)
self.em.addReader(self)
def readable(self):
try:
new_s, addr = self.connector.getNewConnection()
logging.debug('accepted a connection from %s:%d', *addr)
self.handler.connectionAccepted(self, new_s, addr)
except ConnectorTryAgainException:
pass
def writable(self):
return False
def isListeningConnection(self):
return True
class Connection(BaseConnection):
"""A connection."""
def __init__(self, event_manager, handler,
connector = None, addr = None,
connector_handler = None):
self.read_buf = ""
self.write_buf = ""
self.cur_id = 0
self.event_dict = {}
self.aborted = False
self.uuid = None
self._queue = []
BaseConnection.__init__(self, event_manager, handler,
connector = connector, addr = addr,
connector_handler = connector_handler)
if connector is not None:
event_manager.addReader(self)
def getUUID(self):
return self.uuid
def setUUID(self, uuid):
self.uuid = uuid
def _getNextId(self):
next_id = self.cur_id
# Deal with an overflow.
if self.cur_id == 0xffffffff:
self.cur_id = 0
else:
self.cur_id += 1
return next_id
def close(self):
logging.debug('closing a connector for %s (%s:%d)',
dump(self.uuid), *(self.addr))
BaseConnection.close(self)
for event in self.event_dict.itervalues():
self.em.removeIdleEvent(event)
self.event_dict.clear()
self.write_buf = ""
self.read_buf = ""
def abort(self):
"""Abort dealing with this connection."""
logging.debug('aborting a connector for %s (%s:%d)',
dump(self.uuid), *(self.addr))
self.aborted = True
def writable(self):
"""Called when self is writable."""
self._send()
if not self.pending():
if self.aborted:
self.close()
else:
self.em.removeWriter(self)
def readable(self):
"""Called when self is readable."""
self._recv()
self.analyse()
if self.aborted:
self.em.removeReader(self)
def analyse(self):
"""Analyse received data."""
while 1:
packet = None
try:
packet = protocol.parse(self.read_buf)
except PacketMalformedError, msg:
self.handler.packetMalformed(self, packet, msg)
return
if packet is None:
break
# Remove idle events, if appropriate packets were received.
for msg_id in (None, packet.getId()):
try:
event = self.event_dict[msg_id]
del self.event_dict[msg_id]
self.em.removeIdleEvent(event)
except KeyError:
pass
try:
self._queue.append(packet)
finally:
self.read_buf = self.read_buf[len(packet):]
def hasPendingMessages(self):
"""
Returns True if there are messages queued and awaiting processing.
"""
return len(self._queue) != 0
def _enqueue(self, packet):
"""
Enqueue a parsed packet for future processing.
"""
self._queue.append(packet)
def _dequeue(self):
"""
Dequeue a packet for processing.
"""
return self._queue.pop(0)
def process(self):
"""
Process a pending packet.
"""
packet = self._dequeue()
logging.debug('#0x%04x %-30s from %s (%s:%d)', packet.getId(),
packet.getType(), dump(self.uuid), *self.getAddress())
self.handler.packetReceived(self, packet)
def pending(self):
return self.connector is not None and self.write_buf
def _recv(self):
"""Receive data from a connector."""
try:
data = self.connector.receive()
if not data:
logging.debug('Connection %r closed in recv', self.connector)
self.close()
self.handler.connectionClosed(self)
return
self.read_buf += data
except ConnectorTryAgainException:
pass
except ConnectorConnectionRefusedException:
# should only occur while connecting
self.close()
self.handler.connectionFailed(self)
except ConnectorConnectionClosedException:
# connection resetted by peer, according to the man, this error
# should not occurs but it seems it's false
logging.debug('Connection reset by peer: %r', self.connector)
self.close()
self.handler.connectionClosed(self)
except ConnectorException:
logging.debug('Unknown connection error: %r', self.connector)
self.close()
self.handler.connectionClosed(self)
# unhandled connector exception
raise
def _send(self):
"""Send data to a connector."""
if not self.write_buf:
return
try:
n = self.connector.send(self.write_buf)
if not n:
logging.debug('Connection %r closed in send', self.connector)
self.handler.connectionClosed(self)
self.close()
return
self.write_buf = self.write_buf[n:]
except ConnectorTryAgainException:
pass
except ConnectorConnectionClosedException:
# connection resetted by peer
logging.debug('Connection reset by peer: %r', self.connector)
self.close()
self.handler.connectionClosed(self)
except ConnectorException:
logging.debug('Unknown connection error: %r', self.connector)
# unhandled connector exception
self.close()
self.handler.connectionClosed(self)
raise
def _addPacket(self, packet):
"""Add a packet into the write buffer."""
if self.connector is None:
return
logging.debug('#0x%04x %-30s to %s (%s:%d)', packet.getId(),
packet.getType(), dump(self.uuid), *self.getAddress())
try:
self.write_buf += packet.encode()
except PacketMalformedError, m:
logging.critical('trying to send a too big message')
# XXX: we should assert that the internalError packet has a size
# lower than MAX_PACKET_SIZE
return self.notify(protocol.internalError(m))
# If this is the first time, enable polling for writing.
if self.write_buf:
self.em.addWriter(self)
def expectMessage(self, msg_id = None, timeout = 5, additional_timeout = 30):
"""Expect a message for a reply to a given message ID or any message.
The purpose of this method is to define how much amount of time is
acceptable to wait for a message, thus to detect a down or broken
peer. This is important, because one error may halt a whole cluster
otherwise. Although TCP defines a keep-alive feature, the timeout
is too long generally, and it does not detect a certain type of reply,
thus it is better to probe problems at the application level.
The message ID specifies what ID is expected. Usually, this should
be identical with an ID for a request message. If it is None, any
message is acceptable, so it can be used to check idle time.
The timeout is the amount of time to wait until keep-alive messages start.
Once the timeout is expired, the connection starts to ping the peer.
The additional timeout defines the amount of time after the timeout
to invoke a timeoutExpired callback. If it is zero, no ping is sent, and
the callback is executed immediately."""
if self.connector is None:
return
event = IdleEvent(self, msg_id, timeout, additional_timeout)
self.event_dict[msg_id] = event
self.em.addIdleEvent(event)
@not_closed
def notify(self, packet, msg_id=None):
""" Then a packet with a new ID """
if msg_id is None:
msg_id = self._getNextId()
packet.setId(msg_id)
self._addPacket(packet)
return msg_id
@not_closed
def ask(self, packet, timeout=5, additional_timeout=30):
""" Send a packet with a new ID and register the expectation of an answer """
msg_id = self._getNextId()
packet.setId(msg_id)
self.expectMessage(msg_id)
self._addPacket(packet)
return msg_id
@not_closed
def answer(self, packet, answered_packet):
""" Answer to a packet by re-using its ID for the packet answer """
msg_id = answered_packet.getId()
packet.setId(msg_id)
self._addPacket(packet)
def isServerConnection(self):
raise NotImplementedError
def isListeningConnection(self):
return False
class ClientConnection(Connection):
"""A connection from this node to a remote node."""
def __init__(self, event_manager, handler, addr = None,
connector_handler = None, **kw):
self.connecting = True
Connection.__init__(self, event_manager, handler, addr = addr,
connector_handler = connector_handler)
handler.connectionStarted(self)
try:
connector = self.connector_handler()
self.setConnector(connector)
try:
connector.makeClientConnection(addr)
except ConnectorInProgressException:
event_manager.addWriter(self)
else:
self.connecting = False
self.handler.connectionCompleted(self)
event_manager.addReader(self)
except ConnectorConnectionRefusedException:
handler.connectionFailed(self)
self.close()
except ConnectorException:
# unhandled connector exception
handler.connectionFailed(self)
self.close()
raise
def writable(self):
"""Called when self is writable."""
if self.connecting:
err = self.connector.getError()
if err:
self.handler.connectionFailed(self)
self.close()
return
else:
self.connecting = False
self.handler.connectionCompleted(self)
self.em.addReader(self)
else:
Connection.writable(self)
def isServerConnection(self):
return False
class ServerConnection(Connection):
"""A connection from a remote node to this node."""
def isServerConnection(self):
return True
class MTClientConnection(ClientConnection):
"""A Multithread-safe version of ClientConnection."""
def __init__(self, local_var, *args, **kwargs):
# _lock is only here for lock debugging purposes. Do not use.
self._lock = lock = RLock()
self.acquire = lock.acquire
self.release = lock.release
self.dispatcher = kwargs.pop('dispatcher')
self.local_var = local_var
self.lock()
try:
super(MTClientConnection, self).__init__(*args, **kwargs)
finally:
self.unlock()
def lock(self, blocking = 1):
return self.acquire(blocking = blocking)
def unlock(self):
self.release()
@lockCheckWrapper
def writable(self, *args, **kw):
return super(MTClientConnection, self).writable(*args, **kw)
@lockCheckWrapper
def readable(self, *args, **kw):
return super(MTClientConnection, self).readable(*args, **kw)
@lockCheckWrapper
def analyse(self, *args, **kw):
return super(MTClientConnection, self).analyse(*args, **kw)
@lockCheckWrapper
def expectMessage(self, *args, **kw):
return super(MTClientConnection, self).expectMessage(*args, **kw)
@lockCheckWrapper
def notify(self, *args, **kw):
return super(MTClientConnection, self).notify(*args, **kw)
@lockCheckWrapper
def ask(self, packet, timeout=5, additional_timeout=30):
msg_id = self._getNextId()
packet.setId(msg_id)
self.dispatcher.register(self, msg_id, self.local_var.queue)
self.expectMessage(msg_id)
self._addPacket(packet)
return msg_id
@lockCheckWrapper
def answer(self, *args, **kw):
return super(MTClientConnection, self).answer(*args, **kw)
@lockCheckWrapper
def close(self, *args, **kw):
return super(MTClientConnection, self).close(*args, **kw)
class MTServerConnection(ServerConnection):
"""A Multithread-safe version of ServerConnection."""
def __init__(self, *args, **kwargs):
# _lock is only here for lock debugging purposes. Do not use.
self._lock = lock = RLock()
self.acquire = lock.acquire
self.release = lock.release
self.lock()
try:
super(MTServerConnection, self).__init__(*args, **kwargs)
finally:
self.unlock()
def lock(self, blocking = 1):
return self.acquire(blocking = blocking)
def unlock(self):
self.release()
@lockCheckWrapper
def writable(self, *args, **kw):
return super(MTServerConnection, self).writable(*args, **kw)
@lockCheckWrapper
def readable(self, *args, **kw):
return super(MTServerConnection, self).readable(*args, **kw)
@lockCheckWrapper
def analyse(self, *args, **kw):
return super(MTServerConnection, self).analyse(*args, **kw)
@lockCheckWrapper
def expectMessage(self, *args, **kw):
return super(MTServerConnection, self).expectMessage(*args, **kw)
@lockCheckWrapper
def notify(self, *args, **kw):
return super(MTClientConnection, self).notify(*args, **kw)
@lockCheckWrapper
def ask(self, *args, **kw):
return super(MTClientConnection, self).ask(*args, **kw)
@lockCheckWrapper
def answer(self, *args, **kw):
return super(MTClientConnection, self).answer(*args, **kw)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/connector.py 0000664 0000000 0000000 00000012336 11227104631 0022573 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import socket
import errno
import logging
# Global connector registry.
# Fill by calling registerConnectorHandler.
# Read by calling getConnectorHandler.
connector_registry = {}
def registerConnectorHandler(connector_handler):
connector_registry[connector_handler.__name__] = connector_handler
def getConnectorHandler(connector):
if isinstance(connector, basestring):
connector_handler = connector_registry.get(connector)
else:
# Allow to directly provide a handler class without requiring to register
# it first.
connector_handler = connector
return connector_handler
class SocketConnector:
""" This class is a wrapper for a socket """
is_listening = False
remote_addr = None
is_closed = None
def __init__(self, s=None, accepted_from=None):
self.accepted_from = accepted_from
if accepted_from is not None:
self.remote_addr = accepted_from
self.is_listening = False
self.is_closed = False
if s is None:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
else:
self.socket = s
def makeClientConnection(self, addr):
self.is_closed = False
self.remote_addr = addr
try:
try:
self.socket.setblocking(0)
self.socket.connect(addr)
except socket.error, (err, errmsg):
if err == errno.EINPROGRESS:
raise ConnectorInProgressException
if err == errno.ECONNREFUSED:
raise ConnectorConnectionRefusedException
raise ConnectorException, 'makeClientConnection failed: %s:%s' % (err, errmsg)
finally:
logging.debug('%r connecting to %r', self.socket.getsockname(), addr)
def makeListeningConnection(self, addr):
self.is_closed = False
self.is_listening = True
try:
self.socket.setblocking(0)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(addr)
self.socket.listen(5)
except socket.error, (err, errmsg):
self.socket.close()
raise ConnectorException, 'makeListeningConnection failed: %s:%s' % (err, errmsg)
def getError(self):
return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
def getDescriptor(self):
return self.socket.fileno()
def getNewConnection(self):
try:
new_s, addr = self.socket.accept()
new_s = SocketConnector(new_s, accepted_from=addr)
return new_s, addr
except socket.error, (err, errmsg):
if err == errno.EAGAIN:
raise ConnectorTryAgainException
raise ConnectorException, 'getNewConnection failed: %s:%s' % (err, errmsg)
def shutdown(self):
# This may fail if the socket is not connected.
try:
self.socket.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
def receive(self):
try:
return self.socket.recv(4096)
except socket.error, (err, errmsg):
if err == errno.EAGAIN:
raise ConnectorTryAgainException
if err == errno.ECONNREFUSED:
raise ConnectorConnectionRefusedException
if err == errno.ECONNRESET:
raise ConnectorConnectionClosedException
raise ConnectorException, 'receive failed: %s:%s' % (err, errmsg)
def send(self, msg):
try:
return self.socket.send(msg)
except socket.error, (err, errmsg):
if err == errno.EAGAIN:
raise ConnectorTryAgainException
if err == errno.ECONNRESET:
raise ConnectorConnectionClosedException
raise ConnectorException, 'send failed: %s:%s' % (err, errmsg)
def close(self):
self.is_closed = True
return self.socket.close()
def __repr__(self):
try:
fileno = str(self.socket.fileno())
except socket.error, (err, errmsg):
fileno = '?'
result = '<%s at 0x%x fileno %s %s>' % (self.__class__.__name__, id(self),
fileno, self.socket.getsockname())
if self.is_closed is None:
result += 'never opened'
else:
if self.is_closed:
result += 'closed '
else:
result += 'opened '
if self.is_listening:
result += 'listening'
else:
if self.accepted_from is None:
result += 'to'
else:
result += 'from'
result += ' %s' % (self.remote_addr, )
return result + '>'
registerConnectorHandler(SocketConnector)
class ConnectorException(Exception): pass
class ConnectorTryAgainException(ConnectorException): pass
class ConnectorInProgressException(ConnectorException): pass
class ConnectorConnectionClosedException(ConnectorException): pass
class ConnectorConnectionRefusedException(ConnectorException): pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/decorators.py 0000664 0000000 0000000 00000005544 11227104631 0022751 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from neo import protocol
# Some decorators useful to avoid duplication of patterns in handlers
def identification_required(handler):
""" Raise UnexpectedPacketError if the identification has not succeed """
def wrapper(self, conn, packet, *args, **kwargs):
# check if node identification succeed
if conn.getUUID() is None:
raise protocol.UnexpectedPacketError
# identified, call the handler
handler(self, conn, packet, *args, **kwargs)
return wrapper
def restrict_node_types(*node_types):
""" Raise UnexpectedPacketError if the node type is node in the supplied
list, if the uuid is None or if the node is not known. This decorator
should be applied after identification_required """
def inner(handler):
def wrapper(self, conn, packet, *args, **kwargs):
# check if node type is allowed
uuid = conn.getUUID()
if uuid is None:
raise protocol.UnexpectedPacketError
node = self.app.nm.getNodeByUUID(uuid)
if node is None:
raise protocol.UnexpectedPacketError
if node.getNodeType() not in node_types:
raise protocol.UnexpectedPacketError
# all is ok, call the handler
handler(self, conn, packet, *args, **kwargs)
return wrapper
return inner
def client_connection_required(handler):
""" Raise UnexpectedPacketError if the packet comes from a client connection """
def wrapper(self, conn, packet, *args, **kwargs):
if conn.isServerConnection():
raise protocol.UnexpectedPacketError
# it's a client connection, call the handler
handler(self, conn, packet, *args, **kwargs)
return wrapper
def server_connection_required(handler):
""" Raise UnexpectedPacketError if the packet comes from a server connection """
def wrapper(self, conn, packet, *args, **kwargs):
if not conn.isServerConnection():
raise protocol.UnexpectedPacketError
# it's a server connection, call the handler
handler(self, conn, packet, *args, **kwargs)
return wrapper
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/epoll.py 0000664 0000000 0000000 00000010004 11227104631 0021702 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
r"""This is an epoll(4) interface available in Linux 2.6. This requires
ctypes ."""
from ctypes import cdll, Union, Structure, \
c_void_p, c_int, byref
try:
from ctypes import c_uint32, c_uint64
except ImportError:
from ctypes import c_uint, c_ulonglong
c_uint32 = c_uint
c_uint64 = c_ulonglong
from os import close
from errno import EINTR, EAGAIN
libc = cdll.LoadLibrary('libc.so.6')
epoll_create = libc.epoll_create
epoll_wait = libc.epoll_wait
epoll_ctl = libc.epoll_ctl
errno = c_int.in_dll(libc, 'errno')
EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLOUT = 0x004
EPOLLRDNORM = 0x040
EPOLLRDBAND = 0x080
EPOLLWRNORM = 0x100
EPOLLWRBAND = 0x200
EPOLLMSG = 0x400
EPOLLERR = 0x008
EPOLLHUP = 0x010
EPOLLONESHOT = (1 << 30)
EPOLLET = (1 << 31)
EPOLL_CTL_ADD = 1
EPOLL_CTL_DEL = 2
EPOLL_CTL_MOD = 3
class epoll_data(Union):
_fields_ = [("ptr", c_void_p),
("fd", c_int),
("u32", c_uint32),
("u64", c_uint64)]
class epoll_event(Structure):
_fields_ = [("events", c_uint32),
("data", epoll_data)]
class Epoll(object):
efd = -1
def __init__(self):
self.efd = epoll_create(10)
if self.efd == -1:
raise OSError(errno.value, 'epoll_create failed')
self.maxevents = 1024 # XXX arbitrary
epoll_event_array = epoll_event * self.maxevents
self.events = epoll_event_array()
def poll(self, timeout = 1):
if timeout is None:
timeout = -1
else:
timeout *= 1000
timeout = int(timeout)
while 1:
n = epoll_wait(self.efd, byref(self.events), self.maxevents,
timeout)
if n == -1:
e = errno.value
if e in (EINTR, EAGAIN):
continue
else:
raise OSError(e, 'epoll_wait failed')
else:
readable_fd_list = []
writable_fd_list = []
for i in xrange(n):
ev = self.events[i]
if ev.events & (EPOLLIN | EPOLLERR | EPOLLHUP):
readable_fd_list.append(int(ev.data.fd))
elif ev.events & (EPOLLOUT | EPOLLERR | EPOLLHUP):
writable_fd_list.append(int(ev.data.fd))
return readable_fd_list, writable_fd_list
def register(self, fd):
ev = epoll_event()
ev.data.fd = fd
ret = epoll_ctl(self.efd, EPOLL_CTL_ADD, fd, byref(ev))
if ret == -1:
raise OSError(errno.value, 'epoll_ctl failed')
def modify(self, fd, readable, writable):
ev = epoll_event()
ev.data.fd = fd
events = 0
if readable:
events |= EPOLLIN
if writable:
events |= EPOLLOUT
ev.events = events
ret = epoll_ctl(self.efd, EPOLL_CTL_MOD, fd, byref(ev))
if ret == -1:
raise OSError(errno.value, 'epoll_ctl failed')
def unregister(self, fd):
ev = epoll_event()
ret = epoll_ctl(self.efd, EPOLL_CTL_DEL, fd, byref(ev))
if ret == -1:
raise OSError(errno.value, 'epoll_ctl failed')
def __del__(self):
if self.efd >= 0:
close(self.efd)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/event.py 0000664 0000000 0000000 00000025206 11227104631 0021722 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from select import select
from time import time
from neo import protocol
from neo.epoll import Epoll
class IdleEvent(object):
"""This class represents an event called when a connection is waiting for
a message too long."""
def __init__(self, conn, msg_id, timeout, additional_timeout):
self._conn = conn
self._id = msg_id
t = time()
self._time = t + timeout
self._critical_time = t + timeout + additional_timeout
self._additional_timeout = additional_timeout
def getId(self):
return self._id
def getTime(self):
return self._time
def getCriticalTime(self):
return self._critical_time
def __call__(self, t):
conn = self._conn
if t > self._critical_time:
conn.lock()
try:
logging.info('timeout for %r with %s:%d',
self._id, *(conn.getAddress()))
conn.getHandler().timeoutExpired(conn)
conn.close()
return True
finally:
conn.unlock()
elif t > self._time:
conn.lock()
try:
if self._additional_timeout > 5:
self._additional_timeout -= 5
conn.expectMessage(self._id, 5, self._additional_timeout)
# Start a keep-alive packet.
conn.ask(protocol.ping(), 5, 0)
else:
conn.expectMessage(self._id, self._additional_timeout, 0)
return True
finally:
conn.unlock()
return False
class SelectEventManager(object):
"""This class manages connections and events based on select(2)."""
def __init__(self):
self.connection_dict = {}
self.reader_set = set([])
self.writer_set = set([])
self.exc_list = []
self.event_list = []
self.prev_time = time()
self._pending_processing = []
def getConnectionList(self, with_admin_nodes=False):
return self.connection_dict.values()
def register(self, conn):
self.connection_dict[conn.getConnector()] = conn
def unregister(self, conn):
del self.connection_dict[conn.getConnector()]
def _getPendingConnection(self):
if len(self._pending_processing):
result = self._pending_processing.pop(0)
else:
result = None
return result
def _addPendingConnection(self, conn):
self._pending_processing.append(conn)
def poll(self, timeout = 1):
to_process = self._getPendingConnection()
if to_process is None:
# Fetch messages from polled file descriptors
self._poll(timeout=timeout)
# See if there is anything to process
to_process = self._getPendingConnection()
if to_process is not None:
# Process
to_process.process()
# ...and requeue if there are pending messages
if to_process.hasPendingMessages():
self._addPendingConnection(to_process)
def _poll(self, timeout = 1):
rlist, wlist, xlist = select(self.reader_set, self.writer_set, self.exc_list,
timeout)
for s in rlist:
conn = self.connection_dict[s]
conn.lock()
try:
conn.readable()
finally:
conn.unlock()
if conn.hasPendingMessages():
self._addPendingConnection(conn)
for s in wlist:
# This can fail, if a connection is closed in readable().
try:
conn = self.connection_dict[s]
except KeyError:
pass
else:
conn.lock()
try:
conn.writable()
finally:
conn.unlock()
# Check idle events. Do not check them out too often, because this
# is somehow heavy.
event_list = self.event_list
if event_list:
t = time()
if t - self.prev_time >= 1:
self.prev_time = t
event_list.sort(key = lambda event: event.getTime(),
reverse = True)
while event_list:
event = event_list.pop()
if event(t):
try:
event_list.remove(event)
except ValueError:
pass
else:
break
def addIdleEvent(self, event):
self.event_list.append(event)
def removeIdleEvent(self, event):
try:
self.event_list.remove(event)
except ValueError:
pass
def addReader(self, conn):
self.reader_set.add(conn.getConnector())
def removeReader(self, conn):
self.reader_set.discard(conn.getConnector())
def addWriter(self, conn):
self.writer_set.add(conn.getConnector())
def removeWriter(self, conn):
self.writer_set.discard(conn.getConnector())
class EpollEventManager(object):
"""This class manages connections and events based on epoll(5)."""
def __init__(self):
self.connection_dict = {}
self.reader_set = set([])
self.writer_set = set([])
self.event_list = []
self.prev_time = time()
self.epoll = Epoll()
self._pending_processing = []
def getConnectionList(self):
return self.connection_dict.values()
def getConnectionByUUID(self, uuid):
""" Return the connection associated to the UUID, None if the UUID is
None, invalid or not found"""
# FIXME: We may optimize this by using use a dict on UUIDs
if uuid in (None, protocol.INVALID_UUID):
return None
for conn in self.connection_dict.values():
if conn.getUUID() == uuid:
return conn
return None
def register(self, conn):
fd = conn.getDescriptor()
self.connection_dict[fd] = conn
self.epoll.register(fd)
def unregister(self, conn):
fd = conn.getDescriptor()
self.epoll.unregister(fd)
del self.connection_dict[fd]
def _getPendingConnection(self):
if len(self._pending_processing):
result = self._pending_processing.pop(0)
else:
result = None
return result
def _addPendingConnection(self, conn):
self._pending_processing.append(conn)
def poll(self, timeout = 1):
to_process = self._getPendingConnection()
if to_process is None:
# Fetch messages from polled file descriptors
self._poll(timeout=timeout)
# See if there is anything to process
to_process = self._getPendingConnection()
if to_process is not None:
# Process
to_process.process()
# ...and requeue if there are pending messages
if to_process.hasPendingMessages():
self._addPendingConnection(to_process)
def _poll(self, timeout = 1):
rlist, wlist = self.epoll.poll(timeout)
for fd in rlist:
try:
conn = self.connection_dict[fd]
except KeyError:
pass
else:
conn.lock()
try:
conn.readable()
finally:
conn.unlock()
if conn.hasPendingMessages():
self._addPendingConnection(conn)
for fd in wlist:
# This can fail, if a connection is closed in readable().
try:
conn = self.connection_dict[fd]
except KeyError:
pass
else:
conn.lock()
try:
conn.writable()
finally:
conn.unlock()
# Check idle events. Do not check them out too often, because this
# is somehow heavy.
event_list = self.event_list
if event_list:
t = time()
if t - self.prev_time >= 1:
self.prev_time = t
event_list.sort(key = lambda event: event.getTime())
while event_list:
event = event_list[0]
if event(t):
try:
event_list.remove(event)
except ValueError:
pass
else:
break
def addIdleEvent(self, event):
self.event_list.append(event)
def removeIdleEvent(self, event):
try:
self.event_list.remove(event)
except ValueError:
pass
def addReader(self, conn):
try:
fd = conn.getDescriptor()
if fd not in self.reader_set:
self.reader_set.add(fd)
self.epoll.modify(fd, 1, fd in self.writer_set)
except AttributeError:
pass
def removeReader(self, conn):
try:
fd = conn.getDescriptor()
if fd in self.reader_set:
self.reader_set.remove(fd)
self.epoll.modify(fd, 0, fd in self.writer_set)
except AttributeError:
pass
def addWriter(self, conn):
try:
fd = conn.getDescriptor()
if fd not in self.writer_set:
self.writer_set.add(fd)
self.epoll.modify(fd, fd in self.reader_set, 1)
except AttributeError:
pass
def removeWriter(self, conn):
try:
fd = conn.getDescriptor()
if fd in self.writer_set:
self.writer_set.remove(fd)
self.epoll.modify(fd, fd in self.reader_set, 0)
except AttributeError:
pass
# Default to EpollEventManager.
EventManager = EpollEventManager
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/exception.py 0000664 0000000 0000000 00000001750 11227104631 0022575 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class NeoException(Exception): pass
class ElectionFailure(NeoException): pass
class PrimaryFailure(NeoException): pass
class VerificationFailure(NeoException): pass
class OperationFailure(NeoException): pass
class DatabaseFailure(NeoException): pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/handler.py 0000664 0000000 0000000 00000046411 11227104631 0022217 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import PacketMalformedError, UnexpectedPacketError, \
BrokenNodeDisallowedError, NotReadyError, ProtocolError
from neo.connection import ServerConnection
from protocol import ERROR, REQUEST_NODE_IDENTIFICATION, ACCEPT_NODE_IDENTIFICATION, \
PING, PONG, ASK_PRIMARY_MASTER, ANSWER_PRIMARY_MASTER, ANNOUNCE_PRIMARY_MASTER, \
REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, START_OPERATION, \
STOP_OPERATION, ASK_LAST_IDS, ANSWER_LAST_IDS, ASK_PARTITION_TABLE, \
ANSWER_PARTITION_TABLE, SEND_PARTITION_TABLE, NOTIFY_PARTITION_CHANGES, \
ASK_UNFINISHED_TRANSACTIONS, ANSWER_UNFINISHED_TRANSACTIONS, \
ASK_OBJECT_PRESENT, ANSWER_OBJECT_PRESENT, \
DELETE_TRANSACTION, COMMIT_TRANSACTION, ASK_NEW_TID, ANSWER_NEW_TID, \
FINISH_TRANSACTION, NOTIFY_TRANSACTION_FINISHED, LOCK_INFORMATION, \
NOTIFY_INFORMATION_LOCKED, INVALIDATE_OBJECTS, UNLOCK_INFORMATION, \
ASK_NEW_OIDS, ANSWER_NEW_OIDS, ASK_STORE_OBJECT, ANSWER_STORE_OBJECT, \
ABORT_TRANSACTION, ASK_STORE_TRANSACTION, ANSWER_STORE_TRANSACTION, \
ASK_OBJECT, ANSWER_OBJECT, ASK_TIDS, ANSWER_TIDS, ASK_TRANSACTION_INFORMATION, \
ANSWER_TRANSACTION_INFORMATION, ASK_OBJECT_HISTORY, ANSWER_OBJECT_HISTORY, \
ASK_OIDS, ANSWER_OIDS, ADD_PENDING_NODES, ANSWER_NEW_NODES, \
NOT_READY_CODE, OID_NOT_FOUND_CODE, SERIAL_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \
PROTOCOL_ERROR_CODE, TIMEOUT_ERROR_CODE, BROKEN_NODE_DISALLOWED_CODE, \
INTERNAL_ERROR_CODE, ASK_PARTITION_LIST, ANSWER_PARTITION_LIST, ASK_NODE_LIST, \
ANSWER_NODE_LIST, SET_NODE_STATE, ANSWER_NODE_STATE, SET_CLUSTER_STATE, \
ASK_NODE_INFORMATION, ANSWER_NODE_INFORMATION, NO_ERROR_CODE, \
ASK_CLUSTER_STATE, ANSWER_CLUSTER_STATE, NOTIFY_CLUSTER_INFORMATION
class EventHandler(object):
"""This class handles events."""
def __init__(self, app):
self.app = app
self.initPacketDispatchTable()
self.initErrorDispatchTable()
def connectionStarted(self, conn):
"""Called when a connection is started."""
logging.debug('connection started for %s:%d', *(conn.getAddress()))
def connectionCompleted(self, conn):
"""Called when a connection is completed."""
logging.debug('connection completed for %s:%d', *(conn.getAddress()))
def connectionFailed(self, conn):
"""Called when a connection failed."""
logging.debug('connection failed for %s:%d', *(conn.getAddress()))
def connectionAccepted(self, conn, connector, addr):
"""Called when a connection is accepted."""
logging.debug('connection accepted from %s:%d', *addr)
new_conn = ServerConnection(conn.getEventManager(), conn.getHandler(),
connector = connector, addr = addr)
# A request for a node identification should arrive.
new_conn.expectMessage(timeout = 10, additional_timeout = 0)
def timeoutExpired(self, conn):
"""Called when a timeout event occurs."""
logging.debug('timeout expired for %s:%d', *(conn.getAddress()))
def connectionClosed(self, conn):
"""Called when a connection is closed by the peer."""
logging.debug('connection closed for %s:%d', *(conn.getAddress()))
def packetReceived(self, conn, packet):
"""Called when a packet is received."""
self.dispatch(conn, packet)
# XXX: what's the purpose of this method ?
def peerBroken(self, conn):
"""Called when a peer is broken."""
logging.error('%s:%d is broken', *(conn.getAddress()))
def packetMalformed(self, conn, packet, message='', *args):
"""Called when a packet is malformed."""
args = (conn.getAddress()[0], conn.getAddress()[1], message)
if packet is None:
# if decoding fail, there's no packet instance
logging.error('malformed packet from %s:%d: %s', *args)
else:
logging.error('malformed packet %s from %s:%d: %s', packet.getType(), *args)
response = protocol.protocolError(message)
if packet is not None:
conn.answer(response, packet)
else:
conn.notify(response)
conn.abort()
self.peerBroken(conn)
def unexpectedPacket(self, conn, packet, message=None):
"""Handle an unexpected packet."""
if message is None:
message = 'unexpected packet type %s in %s' % (packet.getType(),
self.__class__.__name__)
else:
message = 'unexpected packet: %s in %s' % (message,
self.__class__.__name__)
logging.error('%s', message)
conn.answer(protocol.protocolError(message), packet)
conn.abort()
self.peerBroken(conn)
def brokenNodeDisallowedError(self, conn, packet, *args):
""" Called when a broken node send packets """
conn.answer(protocol.brokenNodeDisallowedError('go away'), packet)
conn.abort()
def notReadyError(self, conn, packet, *args):
""" Called when the node is not ready """
conn.answer(protocol.notReady('retry later'), packet)
conn.abort()
def protocolError(self, conn, packet, message='', *args):
""" Called for any other protocol error """
conn.answer(protocol.protocolError(message), packet)
conn.abort()
def dispatch(self, conn, packet):
"""This is a helper method to handle various packet types."""
t = packet.getType()
try:
try:
method = self.packet_dispatch_table[t]
except KeyError:
raise UnexpectedPacketError('no handler found')
args = packet.decode() or ()
method(conn, packet, *args)
except UnexpectedPacketError, e:
self.unexpectedPacket(conn, packet, *e.args)
except PacketMalformedError, e:
self.packetMalformed(conn, packet, *e.args)
except BrokenNodeDisallowedError, e:
self.brokenNodeDisallowedError(conn, packet, *e.args)
except NotReadyError, e:
self.notReadyError(conn, packet, *e.args)
except ProtocolError, e:
self.protocolError(conn, packet, *e.args)
def checkClusterName(self, name):
# raise an exception if the fiven name mismatch the current cluster name
if self.app.name != name:
logging.error('reject an alien cluster')
raise protocol.ProtocolError('invalid cluster name')
# Packet handlers.
def handleError(self, conn, packet, code, message):
try:
method = self.error_dispatch_table[code]
method(conn, packet, message)
except ValueError:
raise UnexpectedPacketError(message)
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
raise UnexpectedPacketError
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port,
num_partitions, num_replicas, your_uuid):
raise UnexpectedPacketError
def handlePing(self, conn, packet):
logging.debug('got a ping packet; am I overloaded?')
conn.answer(protocol.pong(), packet)
def handlePong(self, conn, packet):
pass
def handleAskPrimaryMaster(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid,
known_master_list):
raise UnexpectedPacketError
def handleAnnouncePrimaryMaster(self, conn, packet):
raise UnexpectedPacketError
def handleReelectPrimaryMaster(self, conn, packet):
raise UnexpectedPacketError
def handleNotifyNodeInformation(self, conn, packet, node_list):
raise UnexpectedPacketError
def handleAskLastIDs(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
raise UnexpectedPacketError
def handleAskPartitionTable(self, conn, packet, offset_list):
raise UnexpectedPacketError
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
raise UnexpectedPacketError
def handleSendPartitionTable(self, conn, packet, ptid, row_list):
raise UnexpectedPacketError
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
raise UnexpectedPacketError
def handleStartOperation(self, conn, packet):
raise UnexpectedPacketError
def handleStopOperation(self, conn, packet):
raise UnexpectedPacketError
def handleAskUnfinishedTransactions(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
raise UnexpectedPacketError
def handleAskObjectPresent(self, conn, packet, oid, tid):
raise UnexpectedPacketError
def handleAnswerObjectPresent(self, conn, packet, oid, tid):
raise UnexpectedPacketError
def handleDeleteTransaction(self, conn, packet, tid):
raise UnexpectedPacketError
def handleCommitTransaction(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAskNewTID(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerNewTID(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAskNewOIDs(self, conn, packet, num_oids):
raise UnexpectedPacketError
def handleAnswerNewOIDs(self, conn, packet, num_oids):
raise UnexpectedPacketError
def handleFinishTransaction(self, conn, packet, oid_list, tid):
raise UnexpectedPacketError
def handleNotifyTransactionFinished(self, conn, packet, tid):
raise UnexpectedPacketError
def handleLockInformation(self, conn, packet, tid):
raise UnexpectedPacketError
def handleNotifyInformationLocked(self, conn, packet, tid):
raise UnexpectedPacketError
def handleInvalidateObjects(self, conn, packet, oid_list, tid):
raise UnexpectedPacketError
def handleUnlockInformation(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAskStoreObject(self, conn, packet, oid, serial,
compression, checksum, data, tid):
raise UnexpectedPacketError
def handleAnswerStoreObject(self, conn, packet, status, oid):
raise UnexpectedPacketError
def handleAbortTransaction(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAskStoreTransaction(self, conn, packet, tid, user, desc,
ext, oid_list):
raise UnexpectedPacketError
def handleAnswerStoreTransaction(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAskObject(self, conn, packet, oid, serial, tid):
raise UnexpectedPacketError
def handleAnswerObject(self, conn, packet, oid, serial_start,
serial_end, compression, checksum, data):
raise UnexpectedPacketError
def handleAskTIDs(self, conn, packet, first, last, partition):
raise UnexpectedPacketError
def handleAnswerTIDs(self, conn, packet, tid_list):
raise UnexpectedPacketError
def handleAskTransactionInformation(self, conn, packet, tid):
raise UnexpectedPacketError
def handleAnswerTransactionInformation(self, conn, packet, tid,
user, desc, ext, oid_list):
raise UnexpectedPacketError
def handleAskObjectHistory(self, conn, packet, oid, first, last):
raise UnexpectedPacketError
def handleAnswerObjectHistory(self, conn, packet, oid, history_list):
raise UnexpectedPacketError
def handleAskOIDs(self, conn, packet, first, last, partition):
raise UnexpectedPacketError
def handleAnswerOIDs(self, conn, packet, oid_list):
raise UnexpectedPacketError
def handleAskPartitionList(self, conn, packet, offset_list):
raise UnexpectedPacketError
def handleAnswerPartitionList(self, conn, packet, ptid, row_list):
raise UnexpectedPacketError
def handleAskNodeList(self, conn, packet, offset_list):
raise UnexpectedPacketError
def handleAnswerNodeList(self, conn, packet, ptid, row_list):
raise UnexpectedPacketError
def handleSetNodeState(self, conn, packet, uuid, state, modify_partition_table):
raise UnexpectedPacketError
def handleAnswerNodeState(self, conn, packet, uuid, state):
raise UnexpectedPacketError
def handleAddPendingNodes(self, conn, packet, uuid_list):
raise UnexpectedPacketError
def handleAnswerNewNodes(self, conn, packet, uuid_list):
raise UnexpectedPacketError
def handleAskNodeInformation(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerNodeInformation(self, conn, packet, node_list):
raise UnexpectedPacketError
def handleAskClusterState(self, conn, packet):
raise UnexpectedPacketError
def handleAnswerClusterState(self, conn, packet, state):
raise UnexpectedPacketError
def handleSetClusterState(self, conn, packet, name, state):
raise UnexpectedPacketError
def handleNotifyClusterInformation(self, conn, packet, state):
raise UnexpectedPacketError
# Error packet handlers.
# XXX: why answer a protocolError to another protocolError ?
handleNotReady = unexpectedPacket
handleOidNotFound = unexpectedPacket
handleSerialNotFound = unexpectedPacket
handleTidNotFound = unexpectedPacket
def handleProtocolError(self, conn, packet, message):
raise RuntimeError, 'protocol error: %s' % (message,)
def handleTimeoutError(self, conn, packet, message):
raise RuntimeError, 'timeout error: %s' % (message,)
def handleBrokenNodeDisallowedError(self, conn, packet, message):
raise RuntimeError, 'broken node disallowed error: %s' % (message,)
def handleInternalError(self, conn, packet, message):
self.peerBroken(conn)
conn.close()
def handleNoError(self, conn, packet, message):
logging.debug("no error message : %s" %(message))
def initPacketDispatchTable(self):
d = {}
d[ERROR] = self.handleError
d[REQUEST_NODE_IDENTIFICATION] = self.handleRequestNodeIdentification
d[ACCEPT_NODE_IDENTIFICATION] = self.handleAcceptNodeIdentification
d[PING] = self.handlePing
d[PONG] = self.handlePong
d[ASK_PRIMARY_MASTER] = self.handleAskPrimaryMaster
d[ANSWER_PRIMARY_MASTER] = self.handleAnswerPrimaryMaster
d[ANNOUNCE_PRIMARY_MASTER] = self.handleAnnouncePrimaryMaster
d[REELECT_PRIMARY_MASTER] = self.handleReelectPrimaryMaster
d[NOTIFY_NODE_INFORMATION] = self.handleNotifyNodeInformation
d[ASK_LAST_IDS] = self.handleAskLastIDs
d[ANSWER_LAST_IDS] = self.handleAnswerLastIDs
d[ASK_PARTITION_TABLE] = self.handleAskPartitionTable
d[ANSWER_PARTITION_TABLE] = self.handleAnswerPartitionTable
d[SEND_PARTITION_TABLE] = self.handleSendPartitionTable
d[NOTIFY_PARTITION_CHANGES] = self.handleNotifyPartitionChanges
d[START_OPERATION] = self.handleStartOperation
d[STOP_OPERATION] = self.handleStopOperation
d[ASK_UNFINISHED_TRANSACTIONS] = self.handleAskUnfinishedTransactions
d[ANSWER_UNFINISHED_TRANSACTIONS] = self.handleAnswerUnfinishedTransactions
d[ASK_OBJECT_PRESENT] = self.handleAskObjectPresent
d[ANSWER_OBJECT_PRESENT] = self.handleAnswerObjectPresent
d[DELETE_TRANSACTION] = self.handleDeleteTransaction
d[COMMIT_TRANSACTION] = self.handleCommitTransaction
d[ASK_NEW_TID] = self.handleAskNewTID
d[ANSWER_NEW_TID] = self.handleAnswerNewTID
d[FINISH_TRANSACTION] = self.handleFinishTransaction
d[NOTIFY_TRANSACTION_FINISHED] = self.handleNotifyTransactionFinished
d[LOCK_INFORMATION] = self.handleLockInformation
d[NOTIFY_INFORMATION_LOCKED] = self.handleNotifyInformationLocked
d[INVALIDATE_OBJECTS] = self.handleInvalidateObjects
d[UNLOCK_INFORMATION] = self.handleUnlockInformation
d[ASK_NEW_OIDS] = self.handleAskNewOIDs
d[ANSWER_NEW_OIDS] = self.handleAnswerNewOIDs
d[ASK_STORE_OBJECT] = self.handleAskStoreObject
d[ANSWER_STORE_OBJECT] = self.handleAnswerStoreObject
d[ABORT_TRANSACTION] = self.handleAbortTransaction
d[ASK_STORE_TRANSACTION] = self.handleAskStoreTransaction
d[ANSWER_STORE_TRANSACTION] = self.handleAnswerStoreTransaction
d[ASK_OBJECT] = self.handleAskObject
d[ANSWER_OBJECT] = self.handleAnswerObject
d[ASK_TIDS] = self.handleAskTIDs
d[ANSWER_TIDS] = self.handleAnswerTIDs
d[ASK_TRANSACTION_INFORMATION] = self.handleAskTransactionInformation
d[ANSWER_TRANSACTION_INFORMATION] = self.handleAnswerTransactionInformation
d[ASK_OBJECT_HISTORY] = self.handleAskObjectHistory
d[ANSWER_OBJECT_HISTORY] = self.handleAnswerObjectHistory
d[ASK_OIDS] = self.handleAskOIDs
d[ANSWER_OIDS] = self.handleAnswerOIDs
d[ASK_PARTITION_LIST] = self.handleAskPartitionList
d[ANSWER_PARTITION_LIST] = self.handleAnswerPartitionList
d[ASK_NODE_LIST] = self.handleAskNodeList
d[ANSWER_NODE_LIST] = self.handleAnswerNodeList
d[SET_NODE_STATE] = self.handleSetNodeState
d[ANSWER_NODE_STATE] = self.handleAnswerNodeState
d[SET_CLUSTER_STATE] = self.handleSetClusterState
d[ADD_PENDING_NODES] = self.handleAddPendingNodes
d[ANSWER_NEW_NODES] = self.handleAnswerNewNodes
d[ASK_NODE_INFORMATION] = self.handleAskNodeInformation
d[ANSWER_NODE_INFORMATION] = self.handleAnswerNodeInformation
d[ASK_CLUSTER_STATE] = self.handleAskClusterState
d[ANSWER_CLUSTER_STATE] = self.handleAnswerClusterState
d[NOTIFY_CLUSTER_INFORMATION] = self.handleNotifyClusterInformation
self.packet_dispatch_table = d
def initErrorDispatchTable(self):
d = {}
d[NO_ERROR_CODE] = self.handleNoError
d[NOT_READY_CODE] = self.handleNotReady
d[OID_NOT_FOUND_CODE] = self.handleOidNotFound
d[SERIAL_NOT_FOUND_CODE] = self.handleSerialNotFound
d[TID_NOT_FOUND_CODE] = self.handleTidNotFound
d[PROTOCOL_ERROR_CODE] = self.handleProtocolError
d[TIMEOUT_ERROR_CODE] = self.handleTimeoutError
d[BROKEN_NODE_DISALLOWED_CODE] = self.handleBrokenNodeDisallowedError
d[INTERNAL_ERROR_CODE] = self.handleInternalError
self.error_dispatch_table = d
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/locking.py 0000664 0000000 0000000 00000007734 11227104631 0022235 0 ustar 00root root 0000000 0000000 from threading import Lock as threading_Lock
from threading import RLock as threading_RLock
from threading import currentThread
"""
Verbose locking classes.
Python threading module contains a simple logging mechanism, but:
- It's limitted to RLock class
- It's enabled instance by instance
- Choice to log or not is done at instanciation
- It does not emit any log before trying to acquire lock
This file defines a VerboseLock class implementing basic lock API and
logging in appropriate places with extensive details.
It can be globaly toggled by changing VERBOSE_LOCKING value.
There is no overhead at all when disabled (passthrough to threading
classes).
"""
__all__ = ['Lock', 'RLock']
VERBOSE_LOCKING = False
if VERBOSE_LOCKING:
class LockUser(object):
def __init__(self, level=0):
self.ident = currentThread().getName()
# This class is instanciated from a place desiring to known what
# called it.
# limit=1 would return execution position in this method
# limit=2 would return execution position in caller
# limit=3 returns execution position in caller's caller
# Additionnal level value (should be positive only) can be used when
# more intermediate calls are involved
path, line_number, func_name, line = traceback.extract_stack(limit=3 + level)[0]
# Simplify path. Only keep 3 last path elements. It is enough for
# current Neo directory structure.
path = os.path.join('...', *path.split(os.path.sep)[-3:])
self.caller = (path, line_number, func_name, line)
def __eq__(self, other):
return isinstance(other, self.__class__) and self.ident == other.ident
def __repr__(self):
return '%s@%s:%s %s' % (self.ident, self.caller[0], self.caller[1], self.caller[3])
class VerboseLock(object):
def __init__(self):
self.owner = None
self.waiting = []
self._note('%s@%X created by %r', self.__class__.__name__, id(self), LockUser(1))
def _note(self, format, *args):
sys.stderr.write(format % args + '\n')
sys.stderr.flush()
def _getOwner(self):
if self._locked():
owner = self.owner
else:
owner = None
return owner
def acquire(self, blocking=1):
me = LockUser()
owner = self._getOwner()
self._note('[%r]%s.acquire(%s) Waiting for lock. Owned by:%r Waiting:%r', me, self, blocking, owner, self.waiting)
if blocking and me == owner:
self._note('[%r]%s.acquire(%s): Deadlock detected: I already own this lock:%r', me, self, blocking, owner)
self.waiting.append(me)
try:
return self.lock.acquire(blocking)
finally:
self.owner = me
self.waiting.remove(me)
self._note('[%r]%s.acquire(%s) Lock granted. Waiting: %r', me, self, blocking, self.waiting)
def release(self):
me = LockUser()
self._note('[%r]%s.release() Waiting: %r', me, self, self.waiting)
return self.lock.release()
def _locked(self):
raise NotImplementedError
def __repr__(self):
return '<%s@%X>' % (self.__class__.__name__, id(self))
class RLock(VerboseLock):
def __init__(self, verbose=None):
VerboseLock.__init__(self)
self.lock = threading_RLock()
def _locked(self):
return self.lock.__block.locked()
def _is_owned(self):
return self.lock._is_owned()
class Lock(VerboseLock):
def __init__(self, verbose=None):
VerboseLock.__init__(self)
self.lock = threading_Lock()
def locked(self):
return self.lock.locked()
_locked = locked
else:
Lock = threading_Lock
RLock = threading_RLock
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/ 0000775 0000000 0000000 00000000000 11227104631 0021515 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/__init__.py 0000664 0000000 0000000 00000000000 11227104631 0023614 0 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/app.py 0000664 0000000 0000000 00000104734 11227104631 0022660 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
import os, sys
from time import time, gmtime
from struct import pack, unpack
from neo.config import ConfigurationManager
from neo import protocol
from neo.protocol import \
RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
INVALID_UUID, INVALID_OID, INVALID_TID, INVALID_PTID, \
CLIENT_NODE_TYPE, MASTER_NODE_TYPE, STORAGE_NODE_TYPE, \
UUID_NAMESPACES, ADMIN_NODE_TYPE, BOOTING
from neo.node import NodeManager, MasterNode, StorageNode, ClientNode, AdminNode
from neo.event import EventManager
from neo.connection import ListeningConnection, ClientConnection, ServerConnection
from neo.exception import ElectionFailure, PrimaryFailure, VerificationFailure, \
OperationFailure
from neo.master import handlers
from neo.master.pt import PartitionTable
from neo.util import dump
from neo.connector import getConnectorHandler
class Application(object):
"""The master node application."""
def __init__(self, file, section):
config = ConfigurationManager(file, section)
self.connector_handler = getConnectorHandler(config.getConnector())
self.name = config.getName()
if len(self.name) == 0:
raise RuntimeError, 'cluster name must be non-empty'
self.server = config.getServer()
logging.debug('IP address is %s, port is %d', *(self.server))
# Exclude itself from the list.
self.master_node_list = [n for n in config.getMasterNodeList() if n != self.server]
logging.debug('master nodes are %s', self.master_node_list)
# Internal attributes.
self.em = EventManager()
self.nm = NodeManager()
# Partition table
replicas, partitions = config.getReplicas(), config.getPartitions()
if replicas < 0:
raise RuntimeError, 'replicas must be a positive integer'
if partitions <= 0:
raise RuntimeError, 'partitions must be more than zero'
self.pt = PartitionTable(partitions, replicas)
logging.debug('the number of replicas is %d, the number of partitions is %d, the name is %s',
replicas, partitions, self.name)
self.primary = None
self.primary_master_node = None
# Generate an UUID for self
self.uuid = self.getNewUUID(MASTER_NODE_TYPE)
# The last OID.
self.loid = INVALID_OID
# The last TID.
self.ltid = INVALID_TID
# The target node's uuid to request next.
self.target_uuid = None
def run(self):
"""Make sure that the status is sane and start a loop."""
for server in self.master_node_list:
self.nm.add(MasterNode(server = server))
# Make a listening port.
self.listening_conn = ListeningConnection(self.em, None,
addr = self.server, connector_handler = self.connector_handler)
self.cluster_state = BOOTING
# Start the election of a primary master node.
self.electPrimary()
# Start a normal operation.
while 1:
try:
if self.primary:
self.playPrimaryRole()
else:
self.playSecondaryRole()
raise RuntimeError, 'should not reach here'
except (ElectionFailure, PrimaryFailure):
# Forget all connections.
for conn in self.em.getConnectionList():
if not conn.isListeningConnection():
conn.close()
# Reelect a new primary master.
self.electPrimary(bootstrap = False)
def electPrimary(self, bootstrap = True):
"""Elect a primary master node.
The difficulty is that a master node must accept connections from
others while attempting to connect to other master nodes at the
same time. Note that storage nodes and client nodes may connect
to self as well as master nodes."""
logging.info('begin the election of a primary master')
self.unconnected_master_node_set = set()
self.negotiating_master_node_set = set()
self.listening_conn.setHandler(handlers.ServerElectionHandler(self))
client_handler = handlers.ClientElectionHandler(self)
em = self.em
nm = self.nm
while 1:
t = 0
self.primary = None
self.primary_master_node = None
for node in nm.getMasterNodeList():
self.unconnected_master_node_set.add(node.getServer())
# For now, believe that every node should be available,
# since down or broken nodes may be already repaired.
node.setState(RUNNING_STATE)
self.negotiating_master_node_set.clear()
try:
while 1:
em.poll(1)
current_time = time()
if current_time >= t + 1:
t = current_time
# Expire temporarily down nodes. For now, assume that a node
# which is down for 60 seconds is really down, if this is a
# bootstrap. 60 seconds may sound too long, but this is reasonable
# when rebooting many cluster machines. Otherwise, wait for only
# 10 seconds, because stopping the whole cluster for a long time
# is a bad idea.
if bootstrap:
expiration = 60
else:
expiration = 10
for node in nm.getMasterNodeList():
if node.getState() == TEMPORARILY_DOWN_STATE \
and node.getLastStateChange() + expiration < current_time:
logging.info('%s is down' % (node, ))
node.setState(DOWN_STATE)
self.unconnected_master_node_set.discard(node.getServer())
# Try to connect to master nodes.
if self.unconnected_master_node_set:
for addr in list(self.unconnected_master_node_set):
ClientConnection(em, client_handler, addr = addr,
connector_handler = self.connector_handler)
if (len(self.unconnected_master_node_set) == 0 \
and len(self.negotiating_master_node_set) == 0) \
or self.primary is not None:
break
# Now there are three situations:
# - I am the primary master
# - I am secondary but don't know who is primary
# - I am secondary and know who is primary
if self.primary is None:
# I am the primary.
self.primary = True
logging.debug('I am the primary, so sending an announcement')
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection):
conn.notify(protocol.announcePrimaryMaster())
conn.abort()
closed = False
t = time()
while not closed:
em.poll(1)
closed = True
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection):
closed = False
break
if t + 10 < time():
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection):
conn.close()
closed = True
else:
# Wait for an announcement. If this is too long, probably
# the primary master is down.
t = time()
while self.primary_master_node is None:
em.poll(1)
if t + 10 < time():
raise ElectionFailure, 'no primary master elected'
# Now I need only a connection to the primary master node.
primary = self.primary_master_node
addr = primary.getServer()
for conn in em.getConnectionList():
if isinstance(conn, ServerConnection) \
or isinstance(conn, ClientConnection) \
and addr != conn.getAddress():
conn.close()
# But if there is no such connection, something wrong happened.
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection) \
and addr == conn.getAddress():
break
else:
raise ElectionFailure, 'no connection remains to the primary'
return
except ElectionFailure, m:
logging.error('election failed; %s' % m)
# Ask all connected nodes to reelect a single primary master.
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection):
conn.notify(protocol.reelectPrimaryMaster())
conn.abort()
# Wait until the connections are closed.
self.primary = None
self.primary_master_node = None
closed = False
t = time()
while not closed:
try:
em.poll(1)
except ElectionFailure:
pass
closed = True
for conn in em.getConnectionList():
if isinstance(conn, ClientConnection):
# Still not closed.
closed = False
break
if time() > t + 10:
# If too long, do not wait.
break
# Close all connections.
for conn in em.getConnectionList():
if not conn.isListeningConnection():
conn.close()
bootstrap = False
def broadcastNodeInformation(self, node):
"""Broadcast a Notify Node Information packet."""
logging.debug('broadcasting node information')
node_type = node.getNodeType()
state = node.getState()
uuid = node.getUUID()
# The server address may be None.
addr = node.getServer()
if addr is None:
ip_address, port = None, None
else:
ip_address, port = addr
if ip_address is None:
ip_address = '0.0.0.0'
if port is None:
port = 0
if node_type == CLIENT_NODE_TYPE:
# Only to master nodes and storage nodes.
for c in self.em.getConnectionList():
if c.getUUID() is not None:
n = self.nm.getNodeByUUID(c.getUUID())
if n.getNodeType() in (MASTER_NODE_TYPE, STORAGE_NODE_TYPE, ADMIN_NODE_TYPE):
node_list = [(node_type, ip_address, port, uuid, state)]
c.notify(protocol.notifyNodeInformation(node_list))
elif node.getNodeType() in (MASTER_NODE_TYPE, STORAGE_NODE_TYPE):
for c in self.em.getConnectionList():
if c.getUUID() is not None:
node_list = [(node_type, ip_address, port, uuid, state)]
c.notify(protocol.notifyNodeInformation(node_list))
elif node.getNodeType() != ADMIN_NODE_TYPE:
raise RuntimeError('unknown node type')
def broadcastPartitionChanges(self, ptid, cell_list):
"""Broadcast a Notify Partition Changes packet."""
logging.debug('broadcastPartitionChanges')
self.pt.log()
for c in self.em.getConnectionList():
if c.getUUID() is not None:
n = self.nm.getNodeByUUID(c.getUUID())
if n.getNodeType() in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE, ADMIN_NODE_TYPE):
# Split the packet if too big.
size = len(cell_list)
start = 0
while size:
amt = min(10000, size)
p = protocol.notifyPartitionChanges(ptid,
cell_list[start:start+amt])
c.notify(p)
size -= amt
start += amt
def sendPartitionTable(self, conn):
""" Send the partition table through the given connection """
row_list = []
for offset in xrange(self.pt.getPartitions()):
row_list.append((offset, self.pt.getRow(offset)))
# Split the packet if too huge.
if len(row_list) == 1000:
conn.notify(protocol.sendPartitionTable( self.pt.getID(), row_list))
del row_list[:]
if row_list:
conn.notify(protocol.sendPartitionTable(self.pt.getID(), row_list))
def sendNodesInformations(self, conn):
""" Send informations on all nodes through the given connection """
node_list = []
for n in self.nm.getNodeList():
if n.getNodeType() != ADMIN_NODE_TYPE:
try:
ip_address, port = n.getServer()
except TypeError:
ip_address, port = '0.0.0.0', 0
node_list.append((n.getNodeType(), ip_address, port,
n.getUUID() or INVALID_UUID, n.getState()))
# Split the packet if too huge.
if len(node_list) == 10000:
conn.notify(protocol.notifyNodeInformation(node_list))
del node_list[:]
if node_list:
conn.notify(protocol.notifyNodeInformation(node_list))
def buildFromScratch(self):
nm, em, pt = self.nm, self.em, self.pt
logging.debug('creating a new partition table, wait for a storage node')
# wait for some empty storage nodes, their are accepted
while len(nm.getStorageNodeList()) == 0:
em.poll(1)
# take the first node available
node = nm.getStorageNodeList()[0]
node.setState(protocol.RUNNING_STATE)
self.broadcastNodeInformation(node)
# build the partition with this node
pt.setID(pack('!Q', 1))
pt.make([node])
def recoverStatus(self):
"""Recover the status about the cluster. Obtain the last OID, the last TID,
and the last Partition Table ID from storage nodes, then get back the latest
partition table or make a new table from scratch, if this is the first time."""
logging.info('begin the recovery of the status')
self.changeClusterState(protocol.RECOVERING)
em = self.em
self.loid = INVALID_OID
self.ltid = INVALID_TID
self.pt.setID(INVALID_PTID)
self.target_uuid = None
# collect the last partition table available
start_time = time()
while self.cluster_state == protocol.RECOVERING:
em.poll(1)
# FIXME: remove this timeout to force manual startup
if start_time + 5 <= time():
self.changeClusterState(protocol.VERIFYING)
logging.info('startup allowed')
if self.pt.getID() == INVALID_PTID:
self.buildFromScratch()
# FIXME: storage node with existing partition but not in the selected PT
# must switch to PENDING state or be disconnected to restarts from nothing
logging.debug('cluster starts with this partition table :')
self.pt.log()
def verifyTransaction(self, tid):
em = self.em
uuid_set = set()
# Determine to which nodes I should ask.
partition = self.getPartition(tid)
transaction_uuid_list = [cell.getUUID() for cell \
in self.pt.getCellList(partition, readable=True)]
if len(transaction_uuid_list) == 0:
raise VerificationFailure
uuid_set.update(transaction_uuid_list)
# Gather OIDs.
self.asking_uuid_dict = {}
self.unfinished_oid_set = set()
for conn in em.getConnectionList():
uuid = conn.getUUID()
if uuid in transaction_uuid_list:
self.asking_uuid_dict[uuid] = False
conn.ask(protocol.askTransactionInformation(tid))
if len(self.asking_uuid_dict) == 0:
raise VerificationFailure
while 1:
em.poll(1)
if not self.pt.operational():
raise VerificationFailure
if False not in self.asking_uuid_dict.values():
break
if self.unfinished_oid_set is None or len(self.unfinished_oid_set) == 0:
# Not commitable.
return None
else:
# Verify that all objects are present.
for oid in self.unfinished_oid_set:
self.asking_uuid_dict.clear()
partition = self.getPartition(oid)
object_uuid_list = [cell.getUUID() for cell \
in self.pt.getCellList(partition, readable=True)]
if len(object_uuid_list) == 0:
raise VerificationFailure
uuid_set.update(object_uuid_list)
self.object_present = True
for conn in em.getConnectionList():
uuid = conn.getUUID()
if uuid in object_uuid_list:
self.asking_uuid_dict[uuid] = False
conn.ask(protocol.askObjectPresent(oid, tid))
while 1:
em.poll(1)
if not self.pt.operational():
raise VerificationFailure
if False not in self.asking_uuid_dict.values():
break
if not self.object_present:
# Not commitable.
return None
return uuid_set
def verifyData(self):
"""Verify the data in storage nodes and clean them up, if necessary."""
em, nm = self.em, self.nm
self.changeClusterState(protocol.VERIFYING)
# wait for any missing node
logging.debug('waiting for the cluster to be operational')
while not self.pt.operational():
em.poll(1)
logging.info('start to verify data')
# Gather all unfinished transactions.
#
# FIXME this part requires more brainstorming. Currently, this deals with
# only unfinished transactions. But how about finished transactions?
# Suppose that A and B have an unfinished transaction. First, A and B are
# asked to commit the transaction. Then, A succeeds. B gets down. Now,
# A believes that the transaction has been committed, while B still believes
# that the transaction is unfinished. Next, if B goes back and A is working,
# no problem; because B's unfinished transaction will be committed correctly.
# However, when B goes back, if A is down, what happens? If the state is
# not very good, B may be asked to abort the transaction!
#
# This situation won't happen frequently, and B shouldn't be asked to drop
# the transaction, if the cluster is not ready. However, there might be
# some corner cases where this may happen. That's why more brainstorming
# is required.
self.asking_uuid_dict = {}
self.unfinished_tid_set = set()
for conn in em.getConnectionList():
uuid = conn.getUUID()
if uuid is not None:
node = nm.getNodeByUUID(uuid)
if node.getNodeType() == STORAGE_NODE_TYPE:
self.asking_uuid_dict[uuid] = False
conn.ask(protocol.askUnfinishedTransactions())
while 1:
em.poll(1)
if not self.pt.operational():
raise VerificationFailure
if False not in self.asking_uuid_dict.values():
break
# Gather OIDs for each unfinished TID, and verify whether the transaction
# can be finished or must be aborted. This could be in parallel in theory,
# but not so easy. Thus do it one-by-one at the moment.
for tid in self.unfinished_tid_set:
uuid_set = self.verifyTransaction(tid)
if uuid_set is None:
# Make sure that no node has this transaction.
for conn in em.getConnectionList():
uuid = conn.getUUID()
if uuid is not None:
node = nm.getNodeByUUID(uuid)
if node.getNodeType() == STORAGE_NODE_TYPE:
conn.notify(protocol.deleteTransaction(tid))
else:
for conn in em.getConnectionList():
uuid = conn.getUUID()
if uuid in uuid_set:
conn.ask(protocol.commitTransaction(tid))
# If possible, send the packets now.
em.poll(0)
# At this stage, all non-working nodes are out-of-date.
cell_list = self.pt.outdate()
# Tweak the partition table, if the distribution of storage nodes
# is not uniform.
cell_list.extend(self.pt.tweak())
# If anything changed, send the changes.
if cell_list:
self.broadcastPartitionChanges(self.pt.setNextID(), cell_list)
def provideService(self):
"""This is the normal mode for a primary master node. Handle transactions
and stop the service only if a catastrophy happens or the user commits
a shutdown."""
logging.info('provide service')
em = self.em
nm = self.nm
self.changeClusterState(protocol.RUNNING)
# This dictionary is used to hold information on transactions being finished.
self.finishing_transaction_dict = {}
# Now everything is passive.
expiration = 10
while 1:
t = 0
try:
em.poll(1)
# implement an expiration of temporary down nodes.
# If a temporary down storage node is expired, it moves to
# down state, and the partition table must drop the node,
# thus repartitioning must be performed.
current_time = time()
if current_time >= t + 1:
t = current_time
for node in nm.getStorageNodeList():
if node.getState() == TEMPORARILY_DOWN_STATE \
and node.getLastStateChange() + expiration < current_time:
logging.warning('%s is down, have to notify the admin' % (node, ))
# XXX: here we should notify the administrator that
# a node seems dead and should be dropped from the
# partition table. This should not be done
# automaticaly to avoid data lost.
node.setState(DOWN_STATE)
except OperationFailure:
# If not operational, send Stop Operation packets to storage nodes
# and client nodes. Abort connections to client nodes.
logging.critical('No longer operational, so stopping the service')
for conn in em.getConnectionList():
node = nm.getNodeByUUID(conn.getUUID())
if node is not None and node.getNodeType() in \
(STORAGE_NODE_TYPE, CLIENT_NODE_TYPE):
conn.notify(protocol.stopOperation())
if node.getNodeType() == CLIENT_NODE_TYPE:
conn.abort()
# Then, go back, and restart.
return
def playPrimaryRole(self):
logging.info('play the primary role with %s (%s:%d)',
dump(self.uuid), *(self.server))
# all incoming connections identify through this handler
self.listening_conn.setHandler(handlers.IdentificationHandler(self))
# If I know any storage node, make sure that they are not in the running state,
# because they are not connected at this stage.
for node in self.nm.getStorageNodeList():
if node.getState() == RUNNING_STATE:
node.setState(TEMPORARILY_DOWN_STATE)
# recover the cluster status at startup
self.recoverStatus()
while 1:
try:
self.verifyData()
except VerificationFailure:
continue
self.provideService()
def playSecondaryRole(self):
"""I play a secondary role, thus only wait for a primary master to fail."""
logging.info('play the secondary role with %s (%s:%d)',
dump(self.uuid), *(self.server))
handler = handlers.PrimaryMasterHandler(self)
em = self.em
# Make sure that every connection has the secondary event handler.
for conn in em.getConnectionList():
conn.setHandler(handler)
while 1:
em.poll(1)
def changeClusterState(self, state):
""" Change the cluster state and apply right handler on each connections """
if self.cluster_state == state:
return
nm, em = self.nm, self.em
# select the storage handler
if state == protocol.BOOTING:
storage_handler = handlers.RecoveryHandler
elif state == protocol.RECOVERING:
storage_handler = handlers.RecoveryHandler
elif state == protocol.VERIFYING:
storage_handler = handlers.VerificationHandler
elif state == protocol.RUNNING:
storage_handler = handlers.StorageServiceHandler
else:
RuntimeError('Unexpected node type')
# change handlers
notification_packet = protocol.notifyClusterInformation(state)
for conn in em.getConnectionList():
node = nm.getNodeByUUID(conn.getUUID())
if conn.isListeningConnection() or node is None:
# not identified or listening, keep the identification handler
continue
node_type = node.getNodeType()
conn.notify(notification_packet)
if node_type in (ADMIN_NODE_TYPE, MASTER_NODE_TYPE):
# those node types keep their own handler
continue
if node_type == CLIENT_NODE_TYPE:
if state != protocol.RUNNING:
conn.close()
handler = handlers.ClientServiceHandler
elif node_type == STORAGE_NODE_TYPE:
handler = storage_handler
handler = handler(self)
conn.setHandler(handler)
handler.connectionCompleted(conn)
self.cluster_state = state
def getNextOID(self):
if self.loid is None:
raise RuntimeError, 'I do not know the last OID'
oid = unpack('!Q', self.loid)[0]
self.loid = pack('!Q', oid + 1)
return self.loid
def getNextTID(self):
tm = time()
gmt = gmtime(tm)
upper = ((((gmt.tm_year - 1900) * 12 + gmt.tm_mon - 1) * 31 \
+ gmt.tm_mday - 1) * 24 + gmt.tm_hour) * 60 + gmt.tm_min
lower = int((gmt.tm_sec % 60 + (tm - int(tm))) / (60.0 / 65536.0 / 65536.0))
tid = pack('!LL', upper, lower)
if tid <= self.ltid:
upper, lower = unpack('!LL', self.ltid)
if lower == 0xffffffff:
# This should not happen usually.
from datetime import timedelta, datetime
d = datetime(gmt.tm_year, gmt.tm_mon, gmt.tm_mday,
gmt.tm_hour, gmt.tm_min) \
+ timedelta(0, 60)
upper = ((((d.year - 1900) * 12 + d.month - 1) * 31 \
+ d.day - 1) * 24 + d.hour) * 60 + d.minute
lower = 0
else:
lower += 1
tid = pack('!LL', upper, lower)
self.ltid = tid
return tid
def getPartition(self, oid_or_tid):
return unpack('!Q', oid_or_tid)[0] % self.pt.getPartitions()
def getNewOIDList(self, num_oids):
return [self.getNextOID() for i in xrange(num_oids)]
def getNewUUID(self, node_type):
# build an UUID
uuid = os.urandom(15)
while uuid == INVALID_UUID[1:]:
uuid = os.urandom(15)
# look for the prefix
prefix = UUID_NAMESPACES.get(node_type, None)
if prefix is None:
raise RuntimeError, 'No UUID namespace found for this node type'
return prefix + uuid
def isValidUUID(self, uuid, addr):
node = self.nm.getNodeByUUID(uuid)
if node is not None and node.getServer() is not None and node.getServer() != addr:
return False
return uuid != self.uuid and uuid != INVALID_UUID
def getClusterState(self):
return self.cluster_state
def shutdown(self):
"""Close all connections and exit"""
# change handler
handler = handlers.ShutdownHandler(self)
for c in self.em.getConnectionList():
c.setHandler(handler)
# wait for all transaction to be finished
while 1:
self.em.poll(1)
if len(self.finishing_transaction_dict) == 0:
if self.cluster_state == protocol.RUNNING:
sys.exit("Application has been asked to shut down")
else:
# no more transaction, ask clients to shutdown
logging.info("asking all clients to shutdown")
for c in self.em.getConnectionList():
node = self.nm.getNodeByUUID(c.getUUID())
if node.getType() == CLIENT_NODE_TYPE:
ip_address, port = node.getServer()
node_list = [(node.getType(), ip_address, port, node.getUUID(), DOWN_STATE)]
c.notify(protocol.notifyNodeInformation(node_list))
# then ask storages and master nodes to shutdown
logging.info("asking all remaining nodes to shutdown")
for c in self.em.getConnectionList():
node = self.nm.getNodeByUUID(c.getUUID())
if node.getType() in (STORAGE_NODE_TYPE, MASTER_NODE_TYPE):
ip_address, port = node.getServer()
node_list = [(node.getType(), ip_address, port, node.getUUID(), DOWN_STATE)]
c.notify(protocol.notifyNodeInformation(node_list))
# then shutdown
sys.exit("Cluster has been asked to shut down")
def identifyStorageNode(self, uuid, node):
# TODO: check all cases here, when server address change...
# in verification and running states, if the node is unknown but the
# uuid != INVALID_UUID, we have to give it a new uuid, but in recovery
# the node must keep it's UUID
state = protocol.RUNNING_STATE
handler = None
if self.cluster_state == protocol.RECOVERING:
if uuid == protocol.INVALID_UUID:
logging.info('reject empty storage node')
raise protocol.NotReadyError
handler = handlers.RecoveryHandler
elif self.cluster_state == protocol.VERIFYING:
if uuid == INVALID_UUID or node is None:
# if node is unknown, it has been forget when the current
# partition was validated by the admin
uuid = INVALID_UUID
state = protocol.PENDING_STATE
handler = handlers.VerificationHandler
elif self.cluster_state == protocol.RUNNING:
if uuid == INVALID_UUID or node is None:
# same as for verification
uuid = INVALID_UUID
state = protocol.PENDING_STATE
handler = handlers.StorageServiceHandler
elif self.cluster_state == protocol.STOPPING:
# FIXME: raise a ShutdowningError ?
raise protocol.NotReadyError
else:
raise RuntimeError('unhandled cluster state')
return (uuid, state, handler)
def identifyNode(self, node_type, uuid, node):
state = protocol.RUNNING_STATE
handler = handlers.IdentificationHandler
if node_type == protocol.ADMIN_NODE_TYPE:
# always accept admin nodes
klass = AdminNode
handler = handlers.AdministrationHandler
logging.info('Accept an admin %s' % dump(uuid))
elif node_type == protocol.MASTER_NODE_TYPE:
# always put other master in waiting state
klass = MasterNode
handler = handlers.SecondaryMasterHandler
logging.info('Accept a master %s' % dump(uuid))
elif node_type == protocol.CLIENT_NODE_TYPE:
# refuse any client before running
if self.cluster_state != protocol.RUNNING:
logging.info('reject a connection from a client')
raise protocol.NotReadyError
klass = ClientNode
handler = handlers.ClientServiceHandler
logging.info('Accept a client %s' % dump(uuid))
elif node_type == protocol.STORAGE_NODE_TYPE:
klass = StorageNode
(uuid, state, handler) = self.identifyStorageNode(uuid, node)
logging.info('Accept a storage (%s)' % state)
return (uuid, node, state, handler, klass)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/ 0000775 0000000 0000000 00000000000 11227104631 0023315 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/__init__.py 0000664 0000000 0000000 00000023072 11227104631 0025432 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.handler import EventHandler
class MasterHandler(EventHandler):
"""This class implements a generic part of the event handlers."""
def _nodeLost(self, conn, node):
# override this method in sub-handlers to do specific actions when a
# node is lost
pass
def _dropIt(self, conn, node, new_state):
if node is None or node.getState() == new_state:
return
if new_state != protocol.BROKEN_STATE and node.getState() == protocol.PENDING_STATE:
# was in pending state, so drop it from the node manager to forget
# it and do not set in running state when it comes back
logging.info('drop a pending node from the node manager')
self.app.nm.remove(node)
node.setState(new_state)
# clean node related data in specialized handlers
self._nodeLost(conn, node)
self.app.broadcastNodeInformation(node)
def connectionClosed(self, conn):
node = self.app.nm.getNodeByUUID(conn.getUUID())
self._dropIt(conn, node, protocol.TEMPORARILY_DOWN_STATE)
def timeoutExpired(self, conn):
node = self.app.nm.getNodeByUUID(conn.getUUID())
self._dropIt(conn, node, protocol.TEMPORARILY_DOWN_STATE)
def peerBroken(self, conn):
node = self.app.nm.getNodeByUUID(conn.getUUID())
self._dropIt(conn, node, protocol.BROKEN_STATE)
def handleNotifyNodeInformation(self, conn, packet, node_list):
logging.error('ignoring Notify Node Information in %s', self.__class__.__name__)
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
logging.error('ignoring Answer Last IDs in %s' % self.__class__.__name__)
def handleAnswerPartitionTable(self, conn, packet, ptid, cell_list):
logging.error('ignoring Answer Partition Table in %s' % self.__class__.__name__)
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
logging.error('ignoring Answer Unfinished Transactions in %s' % self.__class__.__name__)
def handleAnswerTransactionInformation(self, conn, packet, tid, user, desc, ext, oid_list):
logging.error('ignoring Answer Transactin Information in %s' % self.__class__.__name__)
def handleTidNotFound(self, conn, packet, message):
logging.error('ignoring Answer OIDs By TID in %s' % self.__class__.__name__)
def handleAnswerObjectPresent(self, conn, packet, oid, tid):
logging.error('ignoring Answer Object Present in %s' % self.__class__.__name__)
def handleOidNotFound(self, conn, packet, message):
logging.error('ignoring OID Not Found in %s' % self.__class__.__name__)
def handleAskNewTID(self, conn, packet):
logging.error('ignoring Ask New TID in %s' % self.__class__.__name__)
def handleAskNewOIDs(self, conn, packet, num_oids):
logging.error('ignoring Ask New OIDs in %s' % self.__class__.__name__)
def handleFinishTransaction(self, conn, packet, oid_list, tid):
logging.error('ignoring Finish Transaction in %s' % self.__class__.__name__)
def handleNotifyInformationLocked(self, conn, packet, tid):
logging.error('ignoring Notify Information Locked in %s' % self.__class__.__name__)
def handleAbortTransaction(self, conn, packet, tid):
logging.error('ignoring Abort Transaction in %s' % self.__class__.__name__)
def handleAskLastIDs(self, conn, packet):
logging.error('ignoring Ask Last IDs in %s' % self.__class__.__name__)
def handleAskUnfinishedTransactions(self, conn, packet):
logging.error('ignoring ask unfinished transactions in %s' % self.__class__.__name__)
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
logging.error('ignoring notify partition changes in %s' % self.__class__.__name__)
def handleNotifyClusterInformation(self, conn, packet, state):
logging.error('ignoring notify cluster information in %s' % self.__class__.__name__)
def handleAskPrimaryMaster(self, conn, packet):
if conn.getConnector() is None:
# Connection can be closed by peer after he sent AskPrimaryMaster
# if he finds the primary master before we answer him.
# The connection gets closed before this message gets processed
# because this message might have been queued, but connection
# interruption takes effect as soon as received.
return
app = self.app
if app.primary:
primary_uuid = app.uuid
elif app.primary_master_node is not None:
primary_uuid = app.primary_master_node.getUUID()
else:
primary_uuid = protocol.INVALID_UUID
known_master_list = [app.server + (app.uuid, )]
for n in app.nm.getMasterNodeList():
if n.getState() == protocol.BROKEN_STATE:
continue
known_master_list.append(n.getServer() + \
(n.getUUID() or protocol.INVALID_UUID, ))
conn.answer(protocol.answerPrimaryMaster(primary_uuid,
known_master_list), packet)
def handleAskClusterState(self, conn, packet):
assert conn.getUUID() != protocol.INVALID_UUID
state = self.app.getClusterState()
conn.answer(protocol.answerClusterState(state), packet)
def handleAskNodeInformation(self, conn, packet):
self.app.sendNodesInformations(conn)
conn.answer(protocol.answerNodeInformation([]), packet)
def handleAskPartitionTable(self, conn, packet, offset_list):
assert len(offset_list) == 0
app = self.app
app.sendPartitionTable(conn)
conn.answer(protocol.answerPartitionTable(app.pt.getID(), []), packet)
class BaseServiceHandler(MasterHandler):
"""This class deals with events for a service phase."""
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
if node_type in (protocol.CLIENT_NODE_TYPE, protocol.ADMIN_NODE_TYPE):
# No interest.
continue
if uuid == protocol.INVALID_UUID:
# No interest.
continue
if app.uuid == uuid:
# This looks like me...
if state == protocol.RUNNING_STATE:
# Yes, I know it.
continue
else:
# What?! What happened to me?
raise RuntimeError, 'I was told that I am bad'
addr = (ip_address, port)
node = app.nm.getNodeByUUID(uuid)
if node is None:
node = app.nm.getNodeByServer(addr)
if node is None:
# I really don't know such a node. What is this?
continue
else:
if node.getServer() != addr:
# This is different from what I know.
continue
if node.getState() == state:
# No change. Don't care.
continue
node.setState(state)
# Something wrong happened possibly. Cut the connection to
# this node, if any, and notify the information to others.
# XXX this can be very slow.
# XXX does this need to be closed in all cases ?
c = app.em.getConnectionByUUID(uuid)
if c is not None:
c.close()
app.broadcastNodeInformation(node)
if node.getNodeType() == protocol.STORAGE_NODE_TYPE:
if state == protocol.TEMPORARILY_DOWN_STATE:
cell_list = app.pt.outdate()
if len(cell_list) != 0:
ptid = app.pt.setNextID()
app.broadcastPartitionChanges(ptid, cell_list)
def handleAskLastIDs(self, conn, packet):
app = self.app
conn.answer(protocol.answerLastIDs(app.loid, app.ltid, app.pt.getID()), packet)
def handleAskUnfinishedTransactions(self, conn, packet):
app = self.app
p = protocol.answerUnfinishedTransactions(app.finishing_transaction_dict.keys())
conn.answer(p, packet)
# Import all master handlers in the current namespace
from neo.master.handlers.administration import AdministrationHandler
from neo.master.handlers.election import ClientElectionHandler, ServerElectionHandler
from neo.master.handlers.identification import IdentificationHandler
from neo.master.handlers.recovery import RecoveryHandler
from neo.master.handlers.secondary import SecondaryMasterHandler, PrimaryMasterHandler
from neo.master.handlers.shutdown import ShutdownHandler
from neo.master.handlers.verification import VerificationHandler
from neo.master.handlers.storage import StorageServiceHandler
from neo.master.handlers.client import ClientServiceHandler
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/administration.py 0000664 0000000 0000000 00000014043 11227104631 0026716 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.master.handlers import MasterHandler
from neo.protocol import RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
STORAGE_NODE_TYPE, HIDDEN_STATE, PENDING_STATE, RUNNING
from neo.util import dump
class AdministrationHandler(MasterHandler):
"""This class deals with messages from the admin node only"""
def _nodeLost(self, conn, node):
self.app.nm.remove(node)
def handleAskPrimaryMaster(self, conn, packet):
app = self.app
# I'm the primary
conn.answer(protocol.answerPrimaryMaster(app.uuid, []), packet)
# TODO: Admin will ask itself for these data
app.sendNodesInformations(conn)
app.sendPartitionTable(conn)
def handleSetClusterState(self, conn, packet, name, state):
self.checkClusterName(name)
self.app.changeClusterState(state)
p = protocol.noError('cluster state changed')
conn.answer(p, packet)
if state == protocol.STOPPING:
self.app.cluster_state = state
self.app.shutdown()
def handleSetNodeState(self, conn, packet, uuid, state, modify_partition_table):
logging.info("set node state for %s-%s : %s" % (dump(uuid), state, modify_partition_table))
app = self.app
node = app.nm.getNodeByUUID(uuid)
if node is None:
p = protocol.protocolError('invalid uuid')
conn.answer(p, packet)
return
if uuid == app.uuid:
# get message for self
if state == RUNNING_STATE:
# yes I know
p = protocol.noError('node state changed')
conn.answer(p, packet)
return
else:
# I was asked to shutdown
node.setState(state)
p = protocol.noError('node state changed')
conn.answer(p, packet)
app.shutdown()
if node.getState() == state:
# no change, just notify admin node
p = protocol.noError('node state changed')
conn.answer(p, packet)
else:
# first make sure to have a connection to the node
node_conn = None
conn_found = False
for node_conn in app.em.getConnectionList():
if node_conn.getUUID() == node.getUUID():
conn_found = True
break
if conn_found is False:
# no connection to the node
p = protocol.protocolError('no connection to the node')
conn.notify(p)
return
node.setState(state)
p = protocol.noError('state changed')
conn.answer(p, packet)
app.broadcastNodeInformation(node)
# If this is a storage node, ask it to start.
if node.getNodeType() == STORAGE_NODE_TYPE and state == RUNNING_STATE \
and self.app.cluster_state == RUNNING:
logging.info("asking sn to start operation")
node_conn.notify(protocol.startOperation())
# modify the partition table if required
if modify_partition_table and node.getNodeType() == STORAGE_NODE_TYPE:
if state in (DOWN_STATE, TEMPORARILY_DOWN_STATE, HIDDEN_STATE):
# remove from pt
cell_list = app.pt.dropNode(node)
else:
# add to pt
cell_list = app.pt.addNode(node)
if len(cell_list) != 0:
ptid = app.pt.setNextID()
app.broadcastPartitionChanges(ptid, cell_list)
else:
# outdate node in partition table
cell_list = app.pt.outdate()
if len(cell_list) != 0:
ptid = app.pt.setNextID()
app.broadcastPartitionChanges(ptid, cell_list)
def handleAddPendingNodes(self, conn, packet, uuid_list):
uuids = ', '.join([dump(uuid) for uuid in uuid_list])
logging.debug('Add nodes %s' % uuids)
app, nm, em, pt = self.app, self.app.nm, self.app.em, self.app.pt
cell_list = []
uuid_set = set()
# take all pending nodes
for node in nm.getStorageNodeList():
if node.getState() == PENDING_STATE:
uuid_set.add(node.getUUID())
# keep only selected nodes
if uuid_list:
uuid_set = uuid_set.intersection(set(uuid_list))
# nothing to do
if not uuid_set:
logging.warning('No nodes added')
p = protocol.noError('no nodes added')
conn.answer(p, packet)
return
uuids = ', '.join([dump(uuid) for uuid in uuid_set])
logging.info('Adding nodes %s' % uuids)
# switch nodes to running state
for uuid in uuid_set:
node = nm.getNodeByUUID(uuid)
new_cells = pt.addNode(node)
cell_list.extend(new_cells)
node.setState(RUNNING_STATE)
app.broadcastNodeInformation(node)
# start nodes
for s_conn in em.getConnectionList():
if s_conn.getUUID() in uuid_set:
s_conn.notify(protocol.startOperation())
# broadcast the new partition table
app.broadcastPartitionChanges(app.pt.setNextID(), cell_list)
p = protocol.noError('node added')
conn.answer(p, packet)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/client.py 0000664 0000000 0000000 00000011030 11227104631 0025140 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import CLIENT_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, \
UP_TO_DATE_STATE, FEEDING_STATE, DISCARDED_STATE, \
STORAGE_NODE_TYPE, ADMIN_NODE_TYPE, OUT_OF_DATE_STATE, \
HIDDEN_STATE, INVALID_UUID, INTERNAL_ERROR_CODE
from neo.master.handlers import BaseServiceHandler
from neo.protocol import UnexpectedPacketError
from neo.util import dump
class FinishingTransaction(object):
"""This class describes a finishing transaction."""
def __init__(self, conn):
self._conn = conn
self._msg_id = None
self._oid_list = None
self._uuid_set = None
self._locked_uuid_set = set()
def getConnection(self):
return self._conn
def setMessageId(self, msg_id):
self._msg_id = msg_id
def getMessageId(self):
return self._msg_id
def setOIDList(self, oid_list):
self._oid_list = oid_list
def getOIDList(self):
return self._oid_list
def setUUIDSet(self, uuid_set):
self._uuid_set = uuid_set
def getUUIDSet(self):
return self._uuid_set
def addLockedUUID(self, uuid):
if uuid in self._uuid_set:
self._locked_uuid_set.add(uuid)
def allLocked(self):
return self._uuid_set == self._locked_uuid_set
class ClientServiceHandler(BaseServiceHandler):
""" Handler dedicated to client during service state """
def connectionCompleted(self, conn):
pass
def _nodeLost(self, conn, node):
app = self.app
for tid, t in app.finishing_transaction_dict.items():
if t.getConnection() is conn:
del app.finishing_transaction_dict[tid]
def handleAbortTransaction(self, conn, packet, tid):
try:
del self.app.finishing_transaction_dict[tid]
except KeyError:
logging.warn('aborting transaction %s does not exist', dump(tid))
pass
def handleAskNewTID(self, conn, packet):
app = self.app
tid = app.getNextTID()
app.finishing_transaction_dict[tid] = FinishingTransaction(conn)
conn.answer(protocol.answerNewTID(tid), packet)
def handleAskNewOIDs(self, conn, packet, num_oids):
app = self.app
oid_list = app.getNewOIDList(num_oids)
conn.answer(protocol.answerNewOIDs(oid_list), packet)
def handleFinishTransaction(self, conn, packet, oid_list, tid):
app = self.app
# If the given transaction ID is later than the last TID, the peer
# is crazy.
if app.ltid < tid:
raise UnexpectedPacketError
# Collect partitions related to this transaction.
getPartition = app.getPartition
partition_set = set()
partition_set.add(getPartition(tid))
partition_set.update((getPartition(oid) for oid in oid_list))
# Collect the UUIDs of nodes related to this transaction.
uuid_set = set()
for part in partition_set:
uuid_set.update((cell.getUUID() for cell in app.pt.getCellList(part) \
if cell.getNodeState() != HIDDEN_STATE))
# Request locking data.
# build a new set as we may not send the message to all nodes as some
# might be not reachable at that time
used_uuid_set = set()
for c in app.em.getConnectionList():
if c.getUUID() in uuid_set:
c.ask(protocol.lockInformation(tid), timeout=60)
used_uuid_set.add(c.getUUID())
try:
t = app.finishing_transaction_dict[tid]
t.setOIDList(oid_list)
t.setUUIDSet(used_uuid_set)
t.setMessageId(packet.getId())
except KeyError:
logging.warn('finishing transaction %s does not exist', dump(tid))
pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/election.py 0000664 0000000 0000000 00000025415 11227104631 0025500 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, \
DOWN_STATE
from neo.master.handlers import MasterHandler
from neo.exception import ElectionFailure
from neo.protocol import INVALID_UUID
from neo.node import MasterNode
class ElectionHandler(MasterHandler):
"""This class deals with events for a primary master election."""
def handleNotifyNodeInformation(self, conn, packet, node_list):
uuid = conn.getUUID()
if uuid is None:
raise protocol.UnexpectedPacketError
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
if node_type != MASTER_NODE_TYPE:
# No interest.
continue
# Register new master nodes.
addr = (ip_address, port)
if app.server == addr:
# This is self.
continue
else:
node = app.nm.getNodeByServer(addr)
if node is None:
node = MasterNode(server = addr)
app.nm.add(node)
app.unconnected_master_node_set.add(addr)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if node.getUUID() is None:
node.setUUID(uuid)
if state in (node.getState(), RUNNING_STATE):
# No change. Don't care.
continue
if state == RUNNING_STATE:
# No problem.
continue
# Something wrong happened possibly. Cut the connection to
# this node, if any, and notify the information to others.
# XXX this can be very slow.
for c in app.em.getConnectionList():
if c.getUUID() == uuid:
c.close()
node.setState(state)
class ClientElectionHandler(MasterHandler):
def packetReceived(self, conn, packet):
node = self.app.nm.getNodeByServer(conn.getAddress())
if node.getState() != BROKEN_STATE:
node.setState(RUNNING_STATE)
MasterHandler.packetReceived(self, conn, packet)
def connectionStarted(self, conn):
app = self.app
addr = conn.getAddress()
app.unconnected_master_node_set.remove(addr)
app.negotiating_master_node_set.add(addr)
MasterHandler.connectionStarted(self, conn)
def connectionCompleted(self, conn):
conn.ask(protocol.askPrimaryMaster())
MasterHandler.connectionCompleted(self, conn)
def connectionClosed(self, conn):
self.connectionFailed(conn)
MasterHandler.connectionClosed(self, conn)
def timeoutExpired(self, conn):
self.connectionFailed(conn)
MasterHandler.timeoutExpired(self, conn)
def connectionFailed(self, conn):
app = self.app
addr = conn.getAddress()
app.negotiating_master_node_set.discard(addr)
node = app.nm.getNodeByServer(addr)
if node.getState() == RUNNING_STATE:
app.unconnected_master_node_set.add(addr)
node.setState(TEMPORARILY_DOWN_STATE)
elif node.getState() == TEMPORARILY_DOWN_STATE:
app.unconnected_master_node_set.add(addr)
MasterHandler.connectionFailed(self, conn)
def peerBroken(self, conn):
app = self.app
addr = conn.getAddress()
node = app.nm.getNodeByServer(addr)
if node is not None:
node.setState(DOWN_STATE)
app.negotiating_master_node_set.discard(addr)
MasterHandler.peerBroken(self, conn)
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, num_partitions,
num_replicas, your_uuid):
app = self.app
node = app.nm.getNodeByServer(conn.getAddress())
if node_type != MASTER_NODE_TYPE:
# The peer is not a master node!
logging.error('%s:%d is not a master node', ip_address, port)
app.nm.remove(node)
app.negotiating_master_node_set.discard(node.getServer())
conn.close()
return
if conn.getAddress() != (ip_address, port):
# The server address is different! Then why was
# the connection successful?
logging.error('%s:%d is waiting for %s:%d',
conn.getAddress()[0], conn.getAddress()[1], ip_address, port)
app.nm.remove(node)
app.negotiating_master_node_set.discard(node.getServer())
conn.close()
return
if your_uuid != app.uuid:
# uuid conflict happened, accept the new one and restart election
app.uuid = your_uuid
raise ElectionFailure, 'new uuid supplied'
conn.setUUID(uuid)
node.setUUID(uuid)
if app.uuid < uuid:
# I lost.
app.primary = False
app.negotiating_master_node_set.discard(conn.getAddress())
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid, known_master_list):
if conn.getConnector() is None:
# Connection can be closed by peer after he sent
# AnswerPrimaryMaster if he finds the primary master before we
# give him our UUID.
# The connection gets closed before this message gets processed
# because this message might have been queued, but connection
# interruption takes effect as soon as received.
return
app = self.app
# Register new master nodes.
for ip_address, port, uuid in known_master_list:
addr = (ip_address, port)
if app.server == addr:
# This is self.
continue
else:
n = app.nm.getNodeByServer(addr)
if n is None:
n = MasterNode(server = addr)
app.nm.add(n)
app.unconnected_master_node_set.add(addr)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if n.getUUID() is None or n.getUUID() != uuid:
n.setUUID(uuid)
if primary_uuid != INVALID_UUID:
# The primary master is defined.
if app.primary_master_node is not None \
and app.primary_master_node.getUUID() != primary_uuid:
# There are multiple primary master nodes. This is
# dangerous.
raise ElectionFailure, 'multiple primary master nodes'
primary_node = app.nm.getNodeByUUID(primary_uuid)
if primary_node is None:
# I don't know such a node. Probably this information
# is old. So ignore it.
logging.warning('received an unknown primary node UUID')
else:
if primary_node.getUUID() == primary_uuid:
# Whatever the situation is, I trust this master.
app.primary = False
app.primary_master_node = primary_node
# Request a node idenfitication.
conn.ask(protocol.requestNodeIdentification(MASTER_NODE_TYPE,
app.uuid, app.server[0], app.server[1], app.name))
class ServerElectionHandler(MasterHandler):
def handleReelectPrimaryMaster(self, conn, packet):
raise ElectionFailure, 'reelection requested'
def peerBroken(self, conn):
app = self.app
addr = conn.getAddress()
node = app.nm.getNodeByServer(addr)
if node is not None and node.getUUID() is not None:
node.setState(BROKEN_STATE)
MasterHandler.peerBroken(self, conn)
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
if conn.getConnector() is None:
# Connection can be closed by peer after he sent
# RequestNodeIdentification if he finds the primary master before
# we answer him.
# The connection gets closed before this message gets processed
# because this message might have been queued, but connection
# interruption takes effect as soon as received.
return
self.checkClusterName(name)
app = self.app
if node_type != MASTER_NODE_TYPE:
logging.info('reject a connection from a non-master')
raise protocol.NotReadyError
addr = (ip_address, port)
node = app.nm.getNodeByServer(addr)
if node is None:
node = MasterNode(server = addr, uuid = uuid)
app.nm.add(node)
app.unconnected_master_node_set.add(addr)
else:
# If this node is broken, reject it.
if node.getUUID() == uuid:
if node.getState() == BROKEN_STATE:
raise protocol.BrokenNodeDisallowedError
# supplied another uuid in case of conflict
while not app.isValidUUID(uuid, addr):
uuid = app.getNewUUID(node_type)
node.setUUID(uuid)
conn.setUUID(uuid)
p = protocol.acceptNodeIdentification(MASTER_NODE_TYPE, app.uuid,
app.server[0], app.server[1], app.pt.getPartitions(),
app.pt.getReplicas(), uuid)
conn.answer(p, packet)
def handleAnnouncePrimaryMaster(self, conn, packet):
uuid = conn.getUUID()
if uuid is None:
raise protocol.UnexpectedPacketError
app = self.app
if app.primary:
# I am also the primary... So restart the election.
raise ElectionFailure, 'another primary arises'
node = app.nm.getNodeByUUID(uuid)
app.primary = False
app.primary_master_node = node
logging.info('%s is the primary', node)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/identification.py 0000664 0000000 0000000 00000007157 11227104631 0026672 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.master.handlers import MasterHandler
class IdentificationHandler(MasterHandler):
"""This class deals with messages from the admin node only"""
def _nodeLost(self, conn, node):
logging.warning('lost a node in IdentificationHandler : %s' % node)
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
self.checkClusterName(name)
app, nm = self.app, self.app.nm
server = (ip_address, port)
node_by_uuid = nm.getNodeByUUID(uuid)
node_by_addr = nm.getNodeByServer(server)
# handle conflicts and broken nodes
node = node_by_uuid or node_by_addr
if node_by_uuid is not None:
if node.getServer() == server:
if node.getState() == protocol.BROKEN_STATE:
raise protocol.BrokenNodeDisallowedError
# the node is still alive
node.setState(protocol.RUNNING_STATE)
if node.getServer() != server:
if node.getState() == protocol.RUNNING_STATE:
# still running, reject this new node
raise protocol.ProtocolError('invalid server address')
# this node has changed its address
node.setServer(server)
node.setState(protocol.RUNNING_STATE)
if node_by_uuid is None and node_by_addr is not None:
if node.getState() == protocol.RUNNING_STATE:
# still running, reject this new node
raise protocol.ProtocolError('invalid server address')
# FIXME: here the node was known with a different uuid but with the
# same address, is it safe to forgot the old, even if he's not
# running ?
node.setServer(server)
node.setState(protocol.RUNNING_STATE)
# ask the app the node identification, if refused, an exception is raised
result = self.app.identifyNode(node_type, uuid, node)
(uuid, node, state, handler, klass) = result
if uuid == protocol.INVALID_UUID:
# no valid uuid, give it one
uuid = app.getNewUUID(node_type)
if node is None:
# new node
node = klass(uuid=uuid, server=(ip_address, port))
app.nm.add(node)
handler = handler(self.app)
# set up the node
node.setUUID(uuid)
node.setState(state)
# set up the connection
conn.setUUID(uuid)
conn.setHandler(handler)
# answer
args = (protocol.MASTER_NODE_TYPE, app.uuid, app.server[0], app.server[1],
app.pt.getPartitions(), app.pt.getReplicas(), uuid)
conn.answer(protocol.acceptNodeIdentification(*args), packet)
# trigger the event
handler.connectionCompleted(conn)
app.broadcastNodeInformation(node)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/recovery.py 0000664 0000000 0000000 00000010540 11227104631 0025525 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import RUNNING_STATE, BROKEN_STATE, \
TEMPORARILY_DOWN_STATE, CLIENT_NODE_TYPE, ADMIN_NODE_TYPE
from neo.master.handlers import MasterHandler
from neo.protocol import UnexpectedPacketError, INVALID_UUID, INVALID_PTID
from neo.node import StorageNode
from neo.util import dump
class RecoveryHandler(MasterHandler):
"""This class deals with events for a recovery phase."""
def connectionCompleted(self, conn):
# ask the last IDs to perform the recovery
conn.ask(protocol.askLastIDs())
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
if node_type in (CLIENT_NODE_TYPE, ADMIN_NODE_TYPE):
# No interest.
continue
if uuid == INVALID_UUID:
# No interest.
continue
if app.uuid == uuid:
# This looks like me...
if state == RUNNING_STATE:
# Yes, I know it.
continue
else:
# What?! What happened to me?
raise RuntimeError, 'I was told that I am bad'
addr = (ip_address, port)
node = app.nm.getNodeByUUID(uuid)
if node is None:
node = app.nm.getNodeByServer(addr)
if node is None:
# I really don't know such a node. What is this?
continue
else:
if node.getServer() != addr:
# This is different from what I know.
continue
if node.getState() == state:
# No change. Don't care.
continue
if state == RUNNING_STATE:
# No problem.
continue
# Something wrong happened possibly. Cut the connection to this node,
# if any, and notify the information to others.
# XXX this can be very slow.
c = app.em.getConnectionByUUID(uuid)
if c is not None:
c.close()
node.setState(state)
app.broadcastNodeInformation(node)
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
app = self.app
# Get max values.
if app.loid < loid:
app.loid = loid
if app.ltid < ltid:
app.ltid = ltid
if app.pt.getID() == INVALID_PTID or app.pt.getID() < lptid:
# something newer
app.pt.setID(lptid)
app.target_uuid = conn.getUUID()
app.pt.clear()
conn.ask(protocol.askPartitionTable([]))
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
uuid = conn.getUUID()
app = self.app
if uuid != app.target_uuid:
# If this is not from a target node, ignore it.
logging.warn('got answer partition table from %s while waiting for %s',
dump(uuid), dump(app.target_uuid))
return
for offset, cell_list in row_list:
if offset >= app.pt.getPartitions() or app.pt.hasOffset(offset):
# There must be something wrong.
raise UnexpectedPacketError
for uuid, state in cell_list:
n = app.nm.getNodeByUUID(uuid)
if n is None:
n = StorageNode(uuid = uuid)
n.setState(TEMPORARILY_DOWN_STATE)
app.nm.add(n)
app.pt.setCell(offset, n, state)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/secondary.py 0000664 0000000 0000000 00000007535 11227104631 0025670 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.protocol import MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, DOWN_STATE
from neo.master.handlers import MasterHandler
from neo.exception import ElectionFailure, PrimaryFailure
from neo.protocol import UnexpectedPacketError, INVALID_UUID
from neo.node import MasterNode
class SecondaryMasterHandler(MasterHandler):
""" Handler used by primary to handle secondary masters"""
def connectionCompleted(self, conn):
pass
def handleAnnouncePrimaryMaster(self, conn, packet):
raise ElectionFailure, 'another primary arises'
def handleReelectPrimaryMaster(self, conn, packet):
raise ElectionFailure, 'reelection requested'
def handleNotifyNodeInformation(self, conn, packet, node_list):
logging.error('/!\ NotifyNodeInformation packet from secondary master')
class PrimaryMasterHandler(MasterHandler):
""" Handler used by secondaries to handle primary master"""
def _nodeLost(self, conn, node):
# XXX: why in down state ?
self.app.primary_master_node.setState(DOWN_STATE)
raise PrimaryFailure, 'primary master is dead'
def packetReceived(self, conn, packet):
if not conn.isServerConnection():
node = self.app.nm.getNodeByServer(conn.getAddress())
if node.getState() != BROKEN_STATE:
node.setState(RUNNING_STATE)
MasterHandler.packetReceived(self, conn, packet)
def handleAnnouncePrimaryMaster(self, conn, packet):
raise UnexpectedPacketError
def handleReelectPrimaryMaster(self, conn, packet):
raise ElectionFailure, 'reelection requested'
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
if node_type != MASTER_NODE_TYPE:
# No interest.
continue
# Register new master nodes.
addr = (ip_address, port)
if app.server == addr:
# This is self.
continue
else:
n = app.nm.getNodeByServer(addr)
if n is None:
n = MasterNode(server = addr)
app.nm.add(n)
if uuid != INVALID_UUID:
# If I don't know the UUID yet, believe what the peer
# told me at the moment.
if n.getUUID() is None:
n.setUUID(uuid)
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, num_partitions,
num_replicas, your_uuid):
app = self.app
node = app.nm.getNodeByServer(conn.getAddress())
assert node_type == MASTER_NODE_TYPE
assert conn.getAddress() == (ip_address, port)
if your_uuid != app.uuid:
# uuid conflict happened, accept the new one
app.uuid = your_uuid
conn.setUUID(uuid)
node.setUUID(uuid)
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid, known_master_list):
pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/shutdown.py 0000664 0000000 0000000 00000010536 11227104631 0025547 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import CLIENT_NODE_TYPE, ADMIN_NODE_TYPE, INVALID_UUID, \
RUNNING_STATE, STORAGE_NODE_TYPE, TEMPORARILY_DOWN_STATE, STOPPING
from neo.master.handlers import BaseServiceHandler
from neo import decorators
from neo.util import dump
class ShutdownHandler(BaseServiceHandler):
"""This class deals with events for a shutting down phase."""
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
logging.error('reject any new connection')
raise protocol.ProtocolError('cluster is shutting down')
def handleAskPrimaryMaster(self, conn, packet):
logging.error('reject any new demand for primary master')
raise protocol.ProtocolError('cluster is shutting down')
@decorators.identification_required
@decorators.restrict_node_types(CLIENT_NODE_TYPE)
def handleAskNewTID(self, conn, packet):
logging.error('reject any new demand for new tid')
raise protocol.ProtocolError('cluster is shutting down')
@decorators.identification_required
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
uuid = conn.getUUID()
conn_node = app.nm.getNodeByUUID(uuid)
if conn_node is None:
raise RuntimeError('I do not know the uuid %r' % dump(uuid))
if app.cluster_state == STOPPING and len(app.finishing_transaction_dict) == 0:
# do not care about these messages as we are shutting down all nodes
return
for node_type, ip_address, port, uuid, state in node_list:
if node_type in (CLIENT_NODE_TYPE, ADMIN_NODE_TYPE):
# No interest.
continue
if uuid == INVALID_UUID:
# No interest.
continue
if app.uuid == uuid:
# This looks like me...
if state == RUNNING_STATE:
# Yes, I know it.
continue
else:
# What?! What happened to me?
raise RuntimeError, 'I was told that I am bad'
addr = (ip_address, port)
node = app.nm.getNodeByUUID(uuid)
if node is None:
node = app.nm.getNodeByServer(addr)
if node is None:
# I really don't know such a node. What is this?
continue
else:
if node.getServer() != addr:
# This is different from what I know.
continue
if node.getState() == state:
# No change. Don't care.
continue
if state == node.getState():
# No problem.
continue
node.setState(state)
# Something wrong happened possibly. Cut the connection to
# this node, if any, and notify the information to others.
# XXX this can be very slow.
# XXX does this need to be closed in all cases ?
for c in app.em.getConnectionList():
if c.getUUID() == uuid:
c.close()
logging.debug('broadcasting node information')
app.broadcastNodeInformation(node)
if node.getNodeType() == STORAGE_NODE_TYPE:
if state == TEMPORARILY_DOWN_STATE:
cell_list = app.pt.outdate()
if len(cell_list) != 0:
ptid = app.pt.setNextID()
app.broadcastPartitionChanges(ptid, cell_list)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/storage.py 0000664 0000000 0000000 00000013161 11227104631 0025335 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import CLIENT_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, \
UP_TO_DATE_STATE, FEEDING_STATE, DISCARDED_STATE, \
STORAGE_NODE_TYPE, ADMIN_NODE_TYPE, OUT_OF_DATE_STATE, \
HIDDEN_STATE, INVALID_UUID, INTERNAL_ERROR_CODE
from neo.master.handlers import BaseServiceHandler
from neo.protocol import UnexpectedPacketError
from neo.exception import OperationFailure
from neo.util import dump
class StorageServiceHandler(BaseServiceHandler):
""" Handler dedicated to storages during service state """
def connectionCompleted(self, conn):
node = self.app.nm.getNodeByUUID(conn.getUUID())
if node.getState() == RUNNING_STATE:
conn.notify(protocol.startOperation())
def _nodeLost(self, conn, node):
if not self.app.pt.operational():
raise OperationFailure, 'cannot continue operation'
def handleNotifyInformationLocked(self, conn, packet, tid):
uuid = conn.getUUID()
app = self.app
node = app.nm.getNodeByUUID(uuid)
# If the given transaction ID is later than the last TID, the peer
# is crazy.
if app.ltid < tid:
raise UnexpectedPacketError
try:
t = app.finishing_transaction_dict[tid]
t.addLockedUUID(uuid)
if t.allLocked():
# I have received all the answers now. So send a Notify
# Transaction Finished to the initiated client node,
# Invalidate Objects to the other client nodes, and Unlock
# Information to relevant storage nodes.
for c in app.em.getConnectionList():
uuid = c.getUUID()
if uuid is not None:
node = app.nm.getNodeByUUID(uuid)
if node.getNodeType() == CLIENT_NODE_TYPE:
if c is t.getConnection():
p = protocol.notifyTransactionFinished(tid)
c.notify(p, t.getMessageId())
else:
p = protocol.invalidateObjects(t.getOIDList(), tid)
c.notify(p)
elif node.getNodeType() == STORAGE_NODE_TYPE:
if uuid in t.getUUIDSet():
p = protocol.unlockInformation(tid)
c.notify(p)
del app.finishing_transaction_dict[tid]
except KeyError:
# What is this?
pass
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
app = self.app
# If I get a bigger value here, it is dangerous.
if app.loid < loid or app.ltid < ltid or app.pt.getID() < lptid:
logging.critical('got later information in service')
raise OperationFailure
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
# This should be sent when a cell becomes up-to-date because
# a replication has finished.
uuid = conn.getUUID()
app = self.app
node = app.nm.getNodeByUUID(uuid)
new_cell_list = []
for cell in cell_list:
if cell[2] != UP_TO_DATE_STATE:
logging.warn('only up-to-date state should be sent')
continue
if uuid != cell[1]:
logging.warn('only a cell itself should send this packet')
continue
offset = cell[0]
logging.debug("node %s is up for offset %s" %(dump(node.getUUID()), offset))
# check the storage said it is up to date for a partition it was assigne to
for xcell in app.pt.getCellList(offset):
if xcell.getNode().getUUID() == node.getUUID() and \
xcell.getState() not in (OUT_OF_DATE_STATE, UP_TO_DATE_STATE):
msg = "node %s telling that it is UP TO DATE for offset \
%s but where %s for that offset" %(dump(node.getUUID()), offset, xcell.getState())
logging.warning(msg)
self.handleError(conn, packet, INTERNAL_ERROR_CODE, msg)
return
app.pt.setCell(offset, node, UP_TO_DATE_STATE)
new_cell_list.append(cell)
# If the partition contains a feeding cell, drop it now.
for feeding_cell in app.pt.getCellList(offset):
if feeding_cell.getState() == FEEDING_STATE:
app.pt.removeCell(offset, feeding_cell.getNode())
new_cell_list.append((offset, feeding_cell.getUUID(),
DISCARDED_STATE))
break
if new_cell_list:
ptid = app.pt.setNextID()
app.broadcastPartitionChanges(ptid, new_cell_list)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/handlers/verification.py 0000664 0000000 0000000 00000012770 11227104631 0026360 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.protocol import CLIENT_NODE_TYPE, RUNNING_STATE, BROKEN_STATE, \
TEMPORARILY_DOWN_STATE, ADMIN_NODE_TYPE
from neo.master.handlers import MasterHandler
from neo.exception import VerificationFailure
from neo.protocol import INVALID_UUID
from neo.util import dump
class VerificationHandler(MasterHandler):
"""This class deals with events for a verification phase."""
def connectionCompleted(self, conn):
pass
def _nodeLost(self, conn, node):
if not self.app.pt.operational():
raise VerificationFailure, 'cannot continue verification'
def handleNotifyNodeInformation(self, conn, packet, node_list):
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
if node_type in (CLIENT_NODE_TYPE, ADMIN_NODE_TYPE):
# No interest.
continue
if uuid == INVALID_UUID:
# No interest.
continue
if app.uuid == uuid:
# This looks like me...
if state == RUNNING_STATE:
# Yes, I know it.
continue
else:
# What?! What happened to me?
raise RuntimeError, 'I was told that I am bad'
addr = (ip_address, port)
node = app.nm.getNodeByUUID(uuid)
if node is None:
node = app.nm.getNodeByServer(addr)
if node is None:
# I really don't know such a node. What is this?
continue
else:
if node.getServer() != addr:
# This is different from what I know.
continue
if node.getState() == state:
# No change. Don't care.
continue
if state == RUNNING_STATE:
# No problem.
continue
# Something wrong happened possibly. Cut the connection to this node,
# if any, and notify the information to others.
# XXX this can be very slow.
c = app.em.getConnectionByUUID(uuid)
if c is not None:
c.close()
node.setState(state)
app.broadcastNodeInformation(node)
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
app = self.app
# If I get a bigger value here, it is dangerous.
if app.loid < loid or app.ltid < ltid or app.lptid < lptid:
logging.critical('got later information in verification')
raise VerificationFailure
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
uuid = conn.getUUID()
logging.info('got unfinished transactions %s from %s:%d',
tid_list, *(conn.getAddress()))
app = self.app
if app.asking_uuid_dict.get(uuid, True):
# No interest.
return
app.unfinished_tid_set.update(tid_list)
app.asking_uuid_dict[uuid] = True
def handleAnswerTransactionInformation(self, conn, packet, tid,
user, desc, ext, oid_list):
uuid = conn.getUUID()
logging.info('got OIDs %s for %s from %s:%d',
oid_list, tid, *(conn.getAddress()))
app = self.app
if app.asking_uuid_dict.get(uuid, True):
# No interest.
return
oid_set = set(oid_list)
if app.unfinished_oid_set is None:
# Someone does not agree.
pass
elif len(app.unfinished_oid_set) == 0:
# This is the first answer.
app.unfinished_oid_set.update(oid_set)
elif app.unfinished_oid_set != oid_set:
app.unfinished_oid_set = None
app.asking_uuid_dict[uuid] = True
def handleTidNotFound(self, conn, packet, message):
uuid = conn.getUUID()
logging.info('TID not found: %s', message)
app = self.app
if app.asking_uuid_dict.get(uuid, True):
# No interest.
return
app.unfinished_oid_set = None
app.asking_uuid_dict[uuid] = True
def handleAnswerObjectPresent(self, conn, packet, oid, tid):
uuid = conn.getUUID()
logging.info('object %s:%s found', dump(oid), dump(tid))
app = self.app
if app.asking_uuid_dict.get(uuid, True):
# No interest.
return
app.asking_uuid_dict[uuid] = True
def handleOidNotFound(self, conn, packet, message):
uuid = conn.getUUID()
logging.info('OID not found: %s', message)
app = self.app
if app.asking_uuid_dict.get(uuid, True):
# No interest.
return
app.object_present = False
app.asking_uuid_dict[uuid] = True
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/pt.py 0000664 0000000 0000000 00000024651 11227104631 0022522 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import neo.pt
from neo import protocol
from struct import pack, unpack
from neo.protocol import OUT_OF_DATE_STATE, FEEDING_STATE, \
DISCARDED_STATE, RUNNING_STATE, BROKEN_STATE
class PartitionTable(neo.pt.PartitionTable):
"""This class manages a partition table for the primary master node"""
def setID(self, id):
self.id = id
def setNextID(self):
if self.id == protocol.INVALID_PTID:
raise RuntimeError, 'I do not know the last Partition Table ID'
last_id = unpack('!Q', self.id)[0]
self.id = pack('!Q', last_id + 1)
return self.id
def make(self, node_list):
"""Make a new partition table from scratch."""
# start with the first PTID
self.id = pack('!Q', 1)
# First, filter the list of nodes.
node_list = [n for n in node_list \
if n.getState() == RUNNING_STATE and n.getUUID() is not None]
if len(node_list) == 0:
# Impossible.
raise RuntimeError, \
'cannot make a partition table with an empty storage node list'
# Take it into account that the number of storage nodes may be less than the
# number of replicas.
repeats = min(self.nr + 1, len(node_list))
index = 0
for offset in xrange(self.np):
row = []
for i in xrange(repeats):
node = node_list[index]
row.append(neo.pt.Cell(node))
self.count_dict[node] = self.count_dict.get(node, 0) + 1
index += 1
if index == len(node_list):
index = 0
self.partition_list[offset] = row
self.num_filled_rows = self.np
def findLeastUsedNode(self, excluded_node_list = ()):
min_count = self.np + 1
min_node = None
for node, count in self.count_dict.iteritems():
if min_count > count \
and node not in excluded_node_list \
and node.getState() == RUNNING_STATE:
min_node = node
min_count = count
return min_node
def dropNode(self, node):
cell_list = []
uuid = node.getUUID()
for offset, row in enumerate(self.partition_list):
if row is not None:
for cell in row:
if cell.getNode() is node:
if cell.getState() != FEEDING_STATE:
# If this cell is not feeding, find another node
# to be added.
node_list = [c.getNode() for c in row]
n = self.findLeastUsedNode(node_list)
if n is not None:
row.append(neo.pt.Cell(n, OUT_OF_DATE_STATE))
self.count_dict[n] += 1
cell_list.append((offset, n.getUUID(),
OUT_OF_DATE_STATE))
row.remove(cell)
cell_list.append((offset, uuid, DISCARDED_STATE))
break
try:
del self.count_dict[node]
except KeyError:
pass
return cell_list
def addNode(self, node):
"""Add a node. Take it into account that it might not be really a new
node. The strategy is, if a row does not contain a good number of
cells, add this node to the row, unless the node is already present
in the same row. Otherwise, check if this node should replace another
cell."""
cell_list = []
node_count = self.count_dict.get(node, 0)
for offset, row in enumerate(self.partition_list):
feeding_cell = None
max_count = 0
max_cell = None
num_cells = 0
skip = False
for cell in row:
if cell.getNode() == node:
skip = True
break
if cell.getState() == FEEDING_STATE:
feeding_cell = cell
else:
num_cells += 1
count = self.count_dict[cell.getNode()]
if count > max_count:
max_count = count
max_cell = cell
if skip:
continue
if num_cells <= self.nr:
row.append(neo.pt.Cell(node, OUT_OF_DATE_STATE))
cell_list.append((offset, node.getUUID(), OUT_OF_DATE_STATE))
node_count += 1
else:
if max_count - node_count > 1:
if feeding_cell is not None \
or max_cell.getState() == OUT_OF_DATE_STATE:
# If there is a feeding cell already or it is
# out-of-date, just drop the node.
row.remove(max_cell)
cell_list.append((offset, max_cell.getUUID(),
DISCARDED_STATE))
self.count_dict[max_cell.getNode()] -= 1
else:
# Otherwise, use it as a feeding cell for safety.
max_cell.setState(FEEDING_STATE)
cell_list.append((offset, max_cell.getUUID(),
FEEDING_STATE))
# Don't count a feeding cell.
self.count_dict[max_cell.getNode()] -= 1
row.append(neo.pt.Cell(node, OUT_OF_DATE_STATE))
cell_list.append((offset, node.getUUID(),
OUT_OF_DATE_STATE))
node_count += 1
self.count_dict[node] = node_count
self.log()
return cell_list
def tweak(self):
"""Test if nodes are distributed uniformly. Otherwise, correct the
partition table."""
changed_cell_list = []
for offset, row in enumerate(self.partition_list):
removed_cell_list = []
feeding_cell = None
out_of_date_cell_list = []
up_to_date_cell_list = []
for cell in row:
if cell.getNodeState() == BROKEN_STATE:
# Remove a broken cell.
removed_cell_list.append(cell)
elif cell.getState() == FEEDING_STATE:
if feeding_cell is None:
feeding_cell = cell
else:
# Remove an excessive feeding cell.
removed_cell_list.append(cell)
elif cell.getState() == OUT_OF_DATE_STATE:
out_of_date_cell_list.append(cell)
else:
up_to_date_cell_list.append(cell)
# If all cells are up-to-date, a feeding cell is not required.
if len(out_of_date_cell_list) == 0 and feeding_cell is not None:
removed_cell_list.append(feeding_cell)
ideal_num = self.nr + 1
while len(out_of_date_cell_list) + len(up_to_date_cell_list) > ideal_num:
# This row contains too many cells.
if len(up_to_date_cell_list) > 1:
# There are multiple up-to-date cells, so choose whatever
# used too much.
cell_list = out_of_date_cell_list + up_to_date_cell_list
else:
# Drop an out-of-date cell.
cell_list = out_of_date_cell_list
max_count = 0
chosen_cell = None
for cell in cell_list:
count = self.count_dict[cell.getNode()]
if max_count < count:
max_count = count
chosen_cell = cell
removed_cell_list.append(chosen_cell)
try:
out_of_date_cell_list.remove(chosen_cell)
except ValueError:
up_to_date_cell_list.remove(chosen_cell)
# Now remove cells really.
for cell in removed_cell_list:
row.remove(cell)
if cell.getState() != FEEDING_STATE:
self.count_dict[cell.getNode()] -= 1
changed_cell_list.append((offset, cell.getUUID(), DISCARDED_STATE))
# Add cells, if a row contains less than the number of replicas.
for offset, row in enumerate(self.partition_list):
num_cells = 0
for cell in row:
if cell.getState() != FEEDING_STATE:
num_cells += 1
while num_cells <= self.nr:
node = self.findLeastUsedNode([cell.getNode() for cell in row])
if node is None:
break
row.append(neo.pt.Cell(node, OUT_OF_DATE_STATE))
changed_cell_list.append((offset, node.getUUID(), OUT_OF_DATE_STATE))
self.count_dict[node] += 1
num_cells += 1
# FIXME still not enough. It is necessary to check if it is possible
# to reduce differences between frequently used nodes and rarely used
# nodes by replacing cells.
self.log()
return changed_cell_list
def outdate(self):
"""Outdate all non-working nodes."""
cell_list = []
for offset, row in enumerate(self.partition_list):
for cell in row:
if cell.getNodeState() != RUNNING_STATE \
and cell.getState() != OUT_OF_DATE_STATE:
cell.setState(OUT_OF_DATE_STATE)
cell_list.append((offset, cell.getUUID(), OUT_OF_DATE_STATE))
return cell_list
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/ 0000775 0000000 0000000 00000000000 11227104631 0022657 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/__init__.py 0000664 0000000 0000000 00000001502 11227104631 0024766 0 ustar 00root root 0000000 0000000 from neo.master.tests.testMasterApp import MasterAppTests
from neo.master.tests.testMasterPT import MasterPartitionTableTests
from neo.master.tests.testElectionHandler import MasterServerElectionTests
from neo.master.tests.testElectionHandler import MasterClientElectionTests
from neo.master.tests.testRecoveryHandler import MasterRecoveryTests
from neo.master.tests.testClientHandler import MasterClientHandlerTests
from neo.master.tests.testStorageHandler import MasterStorageHandlerTests
from neo.master.tests.testVerificationHandler import MasterVerificationTests
__all__ = [
'MasterAppTests',
'MasterServerElectionTests',
'MasterClientElectionTests',
'MasterRecoveryTests',
'MasterClientHandlerTests',
'MasterStorageHandlerTests',
'MasterVerificationeTests',
'MasterPartitionTableTests',
]
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/connector.py 0000664 0000000 0000000 00000004073 11227104631 0025227 0 ustar 00root root 0000000 0000000 from mock import Mock
import logging
connector_cpt = 0
from neo.protocol import Packet, INVALID_UUID, MASTER_NODE_TYPE
import os
# master node with the highest uuid will be declared as PMN
previous_uuid = None
def getNewUUID():
uuid = INVALID_UUID
previous = globals()["previous_uuid"]
while uuid == INVALID_UUID or (previous is \
not None and uuid > previous):
uuid = os.urandom(16)
logging.info("previous > uuid %s"%(previous > uuid))
globals()["previous_uuid"] = uuid
return uuid
class DoNothingConnector(Mock):
def __init__(self, s=None):
logging.info("initializing connector")
self.desc = globals()['connector_cpt']
globals()['connector_cpt'] = globals()['connector_cpt']+ 1
self.packet_cpt = 0
Mock.__init__(self)
def getAddress(self):
return self.addr
def makeClientConnection(self, addr):
self.addr = addr
def getDescriptor(self):
return self.desc
class TestElectionConnector(DoNothingConnector):
def receive(self):
""" simulate behavior of election """
if self.packet_cpt == 0:
# first : identify
logging.info("in patched analyse / IDENTIFICATION")
p = Packet()
self.uuid = getNewUUID()
p.acceptNodeIdentification(1,
MASTER_NODE_TYPE,
self.uuid,
self.getAddress()[0],
self.getAddress()[1],
1009,
2
)
self.packet_cpt += 1
return p.encode()
elif self.packet_cpt == 1:
# second : answer primary master nodes
logging.info("in patched analyse / ANSWER PM")
p = Packet()
p.answerPrimaryMaster(2,
INVALID_UUID,
[]
)
self.packet_cpt += 1
return p.encode()
else:
# then do nothing
from neo.connector import ConnectorTryAgainException
raise ConnectorTryAgainException
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testClientHandler.py 0000664 0000000 0000000 00000052174 11227104631 0026656 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from struct import pack, unpack
from neo.tests.base import NeoTestBase
import neo.master
from neo import protocol
from neo.protocol import Packet, INVALID_UUID
from neo.master.handlers import ClientServiceHandler
from neo.master.app import Application
from neo.protocol import ERROR, PING, PONG, ANNOUNCE_PRIMARY_MASTER, \
REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, \
ASK_LAST_IDS, ANSWER_LAST_IDS, NOTIFY_PARTITION_CHANGES, \
ASK_UNFINISHED_TRANSACTIONS, ASK_NEW_TID, FINISH_TRANSACTION, \
NOTIFY_INFORMATION_LOCKED, ASK_NEW_OIDS, ABORT_TRANSACTION, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.exception import OperationFailure, ElectionFailure
from neo.node import MasterNode, StorageNode
class MasterClientHandlerTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile(master_number=1, replicas=1)
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.pt.setID(pack('!Q', 1))
self.app.em = Mock({"getConnectionList" : []})
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.service = ClientServiceHandler(self.app)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10010
self.master_address = ('127.0.0.1', self.master_port)
self.client_address = ('127.0.0.1', self.client_port)
self.storage_address = ('127.0.0.1', self.storage_port)
# register the storage
kw = {'uuid':self.getNewUUID(), 'server': self.master_address}
self.app.nm.add(StorageNode(**kw))
def tearDown(self):
NeoTestBase.tearDown(self)
def getLastUUID(self):
return self.uuid
def identifyToMasterNode(self, node_type=STORAGE_NODE_TYPE, ip="127.0.0.1",
port=10021):
"""Do first step of identification to MN """
# register the master itself
uuid = self.getNewUUID()
self.app.nm.add(MasterNode(uuid, (ip, port)))
return uuid
# Tests
def test_05_handleNotifyNodeInformation(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=NOTIFY_NODE_INFORMATION)
# tell the master node that is not running any longer, it must raises
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, self.app.uuid, DOWN_STATE),]
self.assertRaises(RuntimeError, service.handleNotifyNodeInformation, conn, packet, node_list)
# tell the master node that it's running, nothing change
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, self.app.uuid, RUNNING_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about a client node, don't care
new_uuid = self.getNewUUID()
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(CLIENT_NODE_TYPE, '127.0.0.1', self.client_port, new_uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about an unknown node, don't care
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', 11010, new_uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about a known node but with bad address, don't care
self.app.nm.add(StorageNode(("127.0.0.1", 11011), self.getNewUUID()))
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', 11012, uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify node is running, as PMN already know it, nothing is done
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, RUNNING_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify node is temp down, must be taken into account
ptid = self.app.pt.getID()
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, TEMPORARILY_DOWN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
sn = self.app.nm.getStorageNodeList()[0]
self.assertEquals(sn.getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(ptid, self.app.pt.getID())
# notify node is broken, must be taken into account and partition must changed
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
sn = self.app.nm.getStorageNodeList()[0]
self.assertEquals(sn.getState(), BROKEN_STATE)
self.failUnless(ptid < self.app.pt.getID())
def test_06_handleAnswerLastIDs(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_LAST_IDS)
loid = self.app.loid
ltid = self.app.ltid
lptid = self.app.pt.getID()
# do not care if client node call it
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
node_list = []
self.checkUnexpectedPacketRaised(service.handleAnswerLastIDs, conn, packet, None, None, None)
self.assertEquals(loid, self.app.loid)
self.assertEquals(ltid, self.app.ltid)
self.assertEquals(lptid, self.app.pt.getID())
# send information which are later to what PMN knows, this must raise
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = []
new_ptid = unpack('!Q', lptid)[0]
new_ptid = pack('!Q', new_ptid + 1)
self.failUnless(new_ptid > self.app.pt.getID())
self.assertRaises(OperationFailure, service.handleAnswerLastIDs, conn, packet, None, None, new_ptid)
self.assertEquals(loid, self.app.loid)
self.assertEquals(ltid, self.app.ltid)
self.assertEquals(lptid, self.app.pt.getID())
def test_07_handleAskNewTID(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_NEW_TID)
ltid = self.app.ltid
# client call it
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewTID(conn, packet)
self.failUnless(ltid < self.app.ltid)
self.assertEquals(len(self.app.finishing_transaction_dict), 1)
tid = self.app.finishing_transaction_dict.keys()[0]
self.assertEquals(tid, self.app.ltid)
def test_08_handleAskNewOIDs(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_NEW_OIDS)
loid = self.app.loid
# client call it
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewOIDs(conn, packet, 1)
self.failUnless(loid < self.app.loid)
def test_09_handleFinishTransaction(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=FINISH_TRANSACTION)
packet.setId(9)
# give an older tid than the PMN known, must abort
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
oid_list = []
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
self.checkUnexpectedPacketRaised(service.handleFinishTransaction, conn, packet, oid_list, new_tid)
old_node = self.app.nm.getNodeByUUID(uuid)
self.app.nm.remove(old_node)
self.app.pt.dropNode(old_node)
# do the right job
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
storage_uuid = self.identifyToMasterNode()
storage_conn = self.getFakeConnection(storage_uuid, self.storage_address)
self.assertNotEquals(uuid, client_uuid)
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewTID(conn, packet)
oid_list = []
tid = self.app.ltid
conn = self.getFakeConnection(client_uuid, self.client_address)
self.app.em = Mock({"getConnectionList" : [conn, storage_conn]})
service.handleFinishTransaction(conn, packet, oid_list, tid)
self.checkLockInformation(storage_conn)
self.assertEquals(len(self.app.finishing_transaction_dict), 1)
apptid = self.app.finishing_transaction_dict.keys()[0]
self.assertEquals(tid, apptid)
txn = self.app.finishing_transaction_dict.values()[0]
self.assertEquals(len(txn.getOIDList()), 0)
self.assertEquals(len(txn.getUUIDSet()), 1)
self.assertEquals(txn.getMessageId(), 9)
def test_11_handleAbortTransaction(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ABORT_TRANSACTION)
# give a bad tid, must not failed, just ignored it
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
self.assertEqual(len(self.app.finishing_transaction_dict.keys()), 0)
service.handleAbortTransaction(conn, packet, None)
self.assertEqual(len(self.app.finishing_transaction_dict.keys()), 0)
# give a known tid
conn = self.getFakeConnection(client_uuid, self.client_address)
tid = self.app.ltid
self.app.finishing_transaction_dict[tid] = None
self.assertEqual(len(self.app.finishing_transaction_dict.keys()), 1)
service.handleAbortTransaction(conn, packet, tid)
self.assertEqual(len(self.app.finishing_transaction_dict.keys()), 0)
def test_12_handleAskLastIDs(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_LAST_IDS)
# give a uuid
conn = self.getFakeConnection(uuid, self.storage_address)
ptid = self.app.pt.getID()
tid = self.app.ltid
oid = self.app.loid
service.handleAskLastIDs(conn, packet)
packet = self.checkAnswerLastIDs(conn, answered_packet=packet)
loid, ltid, lptid = protocol._decodeAnswerLastIDs(packet._body)
self.assertEqual(loid, oid)
self.assertEqual(ltid, tid)
self.assertEqual(lptid, ptid)
def test_13_handleAskUnfinishedTransactions(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_UNFINISHED_TRANSACTIONS)
# give a uuid
conn = self.getFakeConnection(uuid, self.storage_address)
service.handleAskUnfinishedTransactions(conn, packet)
packet = self.checkAnswerUnfinishedTransactions(conn, answered_packet=packet)
tid_list = protocol._decodeAnswerUnfinishedTransactions(packet._body)[0]
self.assertEqual(len(tid_list), 0)
# create some transaction
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
conn = self.getFakeConnection(uuid, self.storage_address)
service.handleAskUnfinishedTransactions(conn, packet)
packet = self.checkAnswerUnfinishedTransactions(conn, answered_packet=packet)
tid_list = protocol._decodeAnswerUnfinishedTransactions(packet._body)[0]
self.assertEqual(len(tid_list), 3)
def test_15_peerBroken(self):
service = self.service
uuid = self.identifyToMasterNode()
# add a second storage node and then declare it as broken
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.peerBroken(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), BROKEN_STATE)
self.failUnless(lptid < self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.peerBroken, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), BROKEN_STATE)
self.failUnless(lptid < self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.peerBroken(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
def test_16_timeoutExpired(self):
service = self.service
uuid = self.identifyToMasterNode()
# add a second storage node and then declare it as temp down
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.timeoutExpired(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.timeoutExpired, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.timeoutExpired(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
def test_17_connectionClosed(self):
service = self.service
uuid = self.identifyToMasterNode()
# add a second storage node and then declare it as temp down
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.connectionClosed(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.connectionClosed, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.connectionClosed(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testElectionHandler.py 0000664 0000000 0000000 00000105152 11227104631 0027175 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from struct import pack, unpack
from neo.tests.base import NeoTestBase
from neo import protocol
from neo.protocol import Packet, INVALID_UUID
from neo.master.handlers import ClientElectionHandler, ServerElectionHandler
from neo.master.app import Application
from neo.protocol import ERROR, REQUEST_NODE_IDENTIFICATION, ACCEPT_NODE_IDENTIFICATION, \
PING, PONG, ASK_PRIMARY_MASTER, ANSWER_PRIMARY_MASTER, ANNOUNCE_PRIMARY_MASTER, \
REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, START_OPERATION, \
STOP_OPERATION, ASK_LAST_IDS, ANSWER_LAST_IDS, ASK_PARTITION_TABLE, \
ANSWER_PARTITION_TABLE, SEND_PARTITION_TABLE, NOTIFY_PARTITION_CHANGES, \
ASK_UNFINISHED_TRANSACTIONS, ANSWER_UNFINISHED_TRANSACTIONS, \
ASK_OBJECT_PRESENT, ANSWER_OBJECT_PRESENT, \
DELETE_TRANSACTION, COMMIT_TRANSACTION, ASK_NEW_TID, ANSWER_NEW_TID, \
FINISH_TRANSACTION, NOTIFY_TRANSACTION_FINISHED, LOCK_INFORMATION, \
NOTIFY_INFORMATION_LOCKED, INVALIDATE_OBJECTS, UNLOCK_INFORMATION, \
ASK_NEW_OIDS, ANSWER_NEW_OIDS, ASK_STORE_OBJECT, ANSWER_STORE_OBJECT, \
ABORT_TRANSACTION, ASK_STORE_TRANSACTION, ANSWER_STORE_TRANSACTION, \
ASK_OBJECT, ANSWER_OBJECT, ASK_TIDS, ANSWER_TIDS, ASK_TRANSACTION_INFORMATION, \
ANSWER_TRANSACTION_INFORMATION, ASK_OBJECT_HISTORY, ANSWER_OBJECT_HISTORY, \
ASK_OIDS, ANSWER_OIDS, \
NOT_READY_CODE, OID_NOT_FOUND_CODE, SERIAL_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \
PROTOCOL_ERROR_CODE, TIMEOUT_ERROR_CODE, BROKEN_NODE_DISALLOWED_CODE, \
INTERNAL_ERROR_CODE, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.exception import OperationFailure, ElectionFailure
from neo.node import MasterNode, StorageNode
from neo.master.tests.connector import DoNothingConnector
from neo.connection import ClientConnection
# patch connection so that we can register _addPacket messages
# in mock object
def _addPacket(self, packet):
if self.connector is not None:
self.connector._addPacket(packet)
def expectMessage(self, packet):
if self.connector is not None:
self.connector.expectMessage(packet)
class MasterClientElectionTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile()
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.em = Mock({"getConnectionList" : []})
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.election = ClientElectionHandler(self.app)
self.app.unconnected_master_node_set = set()
self.app.negotiating_master_node_set = set()
for node in self.app.nm.getMasterNodeList():
self.app.unconnected_master_node_set.add(node.getServer())
node.setState(RUNNING_STATE)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10011
# apply monkey patches
self._addPacket = ClientConnection._addPacket
self.expectMessage = ClientConnection.expectMessage
ClientConnection._addPacket = _addPacket
ClientConnection.expectMessage = expectMessage
def tearDown(self):
# restore patched methods
ClientConnection._addPacket = self._addPacket
ClientConnection.expectMessage = self.expectMessage
NeoTestBase.tearDown(self)
def test_01_connectionStarted(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.election.connectionStarted(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
def test_02_connectionCompleted(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
self.election.connectionCompleted(conn)
self.checkCalledRequestNodeIdentification(conn)
def test_03_connectionFailed(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.election.connectionStarted(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.election.connectionFailed(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
class MasterServerElectionTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile()
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.em = Mock({"getConnectionList" : []})
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.election = ServerElectionHandler(self.app)
self.app.unconnected_master_node_set = set()
self.app.negotiating_master_node_set = set()
for node in self.app.nm.getMasterNodeList():
self.app.unconnected_master_node_set.add(node.getServer())
node.setState(RUNNING_STATE)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10011
# apply monkey patches
self._addPacket = ClientConnection._addPacket
self.expectMessage = ClientConnection.expectMessage
ClientConnection._addPacket = _addPacket
ClientConnection.expectMessage = expectMessage
def tearDown(self):
NeoTestBase.tearDown(self)
# restore environnement
ClientConnection._addPacket = self._addPacket
ClientConnection.expectMessage = self.expectMessage
# Common methods
def getNewUUID(self):
uuid = INVALID_UUID
while uuid == INVALID_UUID:
uuid = os.urandom(16)
self.uuid = uuid
return uuid
def getLastUUID(self):
return self.uuid
def identifyToMasterNode(self, node_type=STORAGE_NODE_TYPE, ip="127.0.0.1",
port=10021):
"""Do first step of identification to MN
"""
uuid = self.getNewUUID()
return uuid
# Method to test the kind of packet returned in answer
def checkCalledRequestNodeIdentification(self, conn, packet_number=0):
""" Check Request Node Identification has been send"""
self.assertEquals(len(conn.mockGetNamedCalls("ask")), 1)
self.assertEquals(len(conn.mockGetNamedCalls("abort")), 0)
call = conn.mockGetNamedCalls("ask")[packet_number]
packet = call.getParam(0)
self.assertTrue(isinstance(packet, Packet))
self.assertEquals(packet.getType(), REQUEST_NODE_IDENTIFICATION)
def checkCalledAskPrimaryMaster(self, conn, packet_number=0):
""" Check ask primary master has been send"""
call = conn.mockGetNamedCalls("_addPacket")[packet_number]
packet = call.getParam(0)
self.assertTrue(isinstance(packet, Packet))
self.assertEquals(packet.getType(),ASK_PRIMARY_MASTER)
def checkCalledAnswerPrimaryMaster(self, conn, packet_number=0):
""" Check Answer primaty master message has been send"""
call = conn.mockGetNamedCalls("answer")[packet_number]
packet = call.getParam(0)
self.assertTrue(isinstance(packet, Packet))
self.assertEquals(packet.getType(), ANSWER_PRIMARY_MASTER)
# Tests
def test_04_connectionClosed(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.election.connectionClosed(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_05_timeoutExpired(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.election.timeoutExpired(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_06_peerBroken1(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.election.peerBroken(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), DOWN_STATE)
def test_06_peerBroken2(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
# Without a client connection
conn = Mock({"getUUID" : uuid,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port),})
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.election.connectionStarted(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.election.peerBroken(conn)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), BROKEN_STATE)
def test_07_packetReceived(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
p = protocol.acceptNodeIdentification(MASTER_NODE_TYPE, uuid,
"127.0.0.1", self.master_port, 1009, 2, self.app.uuid)
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
node = self.app.nm.getNodeByServer(conn.getAddress())
node.setState(DOWN_STATE)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), DOWN_STATE)
self.election.packetReceived(conn, p)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
def test_08_handleAcceptNodeIdentification1(self):
# test with storage node, must be rejected
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
args = (MASTER_NODE_TYPE, uuid, '127.0.0.1', self.master_port,
self.app.pt.getPartitions(), self.app.pt.getReplicas(), self.app.uuid)
p = protocol.acceptNodeIdentification(*args)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getUUID(), None)
self.assertEqual(conn.getUUID(), None)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.election.handleAcceptNodeIdentification(conn, p, STORAGE_NODE_TYPE,
uuid, "127.0.0.1", self.master_port,
self.app.pt.getPartitions(),
self.app.pt.getReplicas(),
self.app.uuid
)
self.assertEqual(conn.getConnector(), None)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
def test_08_handleAcceptNodeIdentification2(self):
# test with bad address, must be rejected
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
args = (MASTER_NODE_TYPE, uuid, '127.0.0.1', self.master_port,
self.app.pt.getPartitions(), self.app.pt.getReplicas(), self.app.uuid)
p = protocol.acceptNodeIdentification(*args)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getUUID(), None)
self.assertEqual(conn.getUUID(), None)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.election.handleAcceptNodeIdentification(conn, p, STORAGE_NODE_TYPE,
uuid, "127.0.0.2", self.master_port,
self.app.pt.getPartitions(),
self.app.pt.getReplicas(),
self.app.uuid)
self.assertEqual(conn.getConnector(), None)
def test_08_handleAcceptNodeIdentification3(self):
# test with master node, must be ok
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
args = (MASTER_NODE_TYPE, uuid, '127.0.0.1', self.master_port,
self.app.pt.getPartitions(), self.app.pt.getReplicas(), self.app.uuid)
p = protocol.acceptNodeIdentification(*args)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getUUID(), None)
self.assertEqual(conn.getUUID(), None)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.election.handleAcceptNodeIdentification(conn, p, MASTER_NODE_TYPE,
uuid, "127.0.0.1", self.master_port,
self.app.pt.getPartitions(),
self.app.pt.getReplicas(),
self.app.uuid)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getUUID(), uuid)
self.assertEqual(conn.getUUID(), uuid)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),2)
self.checkCalledAskPrimaryMaster(conn.getConnector(), 1)
def test_09_handleAnswerPrimaryMaster1(self):
# test with master node and greater uuid
uuid = self.getNewUUID()
while uuid < self.app.uuid:
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
conn.setUUID(uuid)
p = protocol.askPrimaryMaster()
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.election.handleAnswerPrimaryMaster(conn, p, INVALID_UUID, [])
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(self.app.primary, False)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
def test_09_handleAnswerPrimaryMaster2(self):
# test with master node and lesser uuid
uuid = self.getNewUUID()
while uuid > self.app.uuid:
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
conn.setUUID(uuid)
p = protocol.askPrimaryMaster()
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.election.handleAnswerPrimaryMaster(conn, p, INVALID_UUID, [])
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(self.app.primary, None)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
def test_09_handleAnswerPrimaryMaster3(self):
# test with master node and given uuid for PMN
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
conn.setUUID(uuid)
p = protocol.askPrimaryMaster()
self.app.nm.add(MasterNode(("127.0.0.1", self.master_port), uuid))
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 2)
self.assertEqual(self.app.primary_master_node, None)
self.election.handleAnswerPrimaryMaster(conn, p, uuid, [])
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 2)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertNotEqual(self.app.primary_master_node, None)
self.assertEqual(self.app.primary, False)
def test_09_handleAnswerPrimaryMaster4(self):
# test with master node and unknown uuid for PMN
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
conn.setUUID(uuid)
p = protocol.askPrimaryMaster()
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(self.app.primary_master_node, None)
self.election.handleAnswerPrimaryMaster(conn, p, uuid, [])
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertEqual(self.app.primary_master_node, None)
self.assertEqual(self.app.primary, None)
def test_09_handleAnswerPrimaryMaster5(self):
# test with master node and new uuid for PMN
uuid = self.getNewUUID()
conn = ClientConnection(self.app.em, self.election, addr = ("127.0.0.1", self.master_port),
connector_handler = DoNothingConnector)
conn.setUUID(uuid)
p = protocol.askPrimaryMaster()
self.app.nm.add(MasterNode(("127.0.0.1", self.master_port), uuid))
self.assertEqual(len(self.app.unconnected_master_node_set), 0)
self.assertEqual(len(self.app.negotiating_master_node_set), 1)
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 2)
self.assertEqual(self.app.primary_master_node, None)
master_uuid = self.getNewUUID()
self.election.handleAnswerPrimaryMaster(conn, p, master_uuid, [("127.0.0.1", self.master_port+1, master_uuid),])
self.assertEqual(len(conn.getConnector().mockGetNamedCalls("_addPacket")),1)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 3)
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
self.assertNotEqual(self.app.primary_master_node, None)
self.assertEqual(self.app.primary, False)
# Now tell it's another node which is primary, it must raise
self.assertRaises(ElectionFailure, self.election.handleAnswerPrimaryMaster, conn, p, uuid, [])
def test_10_handleRequestNodeIdentification(self):
election = self.election
uuid = self.getNewUUID()
args = (MASTER_NODE_TYPE, uuid, '127.0.0.1', self.storage_port, 'INVALID_NAME')
packet = protocol.requestNodeIdentification(*args)
# test alien cluster
conn = Mock({"_addPacket" : None, "abort" : None,
"isServerConnection" : True})
self.checkProtocolErrorRaised(
election.handleRequestNodeIdentification,
conn,
packet=packet,
node_type=MASTER_NODE_TYPE,
uuid=uuid,
ip_address='127.0.0.1',
port=self.storage_port,
name="INVALID_NAME",)
# test connection of a storage node
conn = Mock({"_addPacket" : None, "abort" : None, "expectMessage" : None,
"isServerConnection" : True})
self.checkNotReadyErrorRaised(
election.handleRequestNodeIdentification,
conn,
packet=packet,
node_type=STORAGE_NODE_TYPE,
uuid=uuid,
ip_address='127.0.0.1',
port=self.storage_port,
name=self.app.name,)
# known node
conn = Mock({"_addPacket" : None, "abort" : None, "expectMessage" : None,
"isServerConnection" : True})
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
node = self.app.nm.getMasterNodeList()[0]
self.assertEqual(node.getUUID(), None)
self.assertEqual(node.getState(), RUNNING_STATE)
election.handleRequestNodeIdentification(conn,
packet=packet,
node_type=MASTER_NODE_TYPE,
uuid=uuid,
ip_address='127.0.0.1',
port=self.master_port,
name=self.app.name,)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(node.getUUID(), uuid)
self.assertEqual(node.getState(), RUNNING_STATE)
self.checkAcceptNodeIdentification(conn, answered_packet=packet)
# unknown node
conn = Mock({"_addPacket" : None, "abort" : None, "expectMessage" : None,
"isServerConnection" : True})
new_uuid = self.getNewUUID()
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.assertEqual(len(self.app.unconnected_master_node_set), 1)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
election.handleRequestNodeIdentification(conn,
packet=packet,
node_type=MASTER_NODE_TYPE,
uuid=new_uuid,
ip_address='127.0.0.1',
port=self.master_port+1,
name=self.app.name,)
self.assertEqual(len(self.app.nm.getMasterNodeList()), 2)
self.checkAcceptNodeIdentification(conn, answered_packet=packet)
self.assertEqual(len(self.app.unconnected_master_node_set), 2)
self.assertEqual(len(self.app.negotiating_master_node_set), 0)
# broken node
conn = Mock({"_addPacket" : None, "abort" : None, "expectMessage" : None,
"isServerConnection" : True})
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port+1))
self.assertEqual(node.getUUID(), new_uuid)
self.assertEqual(node.getState(), RUNNING_STATE)
node.setState(BROKEN_STATE)
self.assertEqual(node.getState(), BROKEN_STATE)
self.checkBrokenNodeDisallowedErrorRaised(
election.handleRequestNodeIdentification,
conn,
packet=packet,
node_type=MASTER_NODE_TYPE,
uuid=new_uuid,
ip_address='127.0.0.1',
port=self.master_port+1,
name=self.app.name,)
def test_11_handleAskPrimaryMaster(self):
election = self.election
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = protocol.askPrimaryMaster()
conn = Mock({"_addPacket" : None,
"getUUID" : uuid,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
election.handleAskPrimaryMaster(conn, packet)
self.assertEquals(len(conn.mockGetNamedCalls("answer")), 1)
self.assertEquals(len(conn.mockGetNamedCalls("abort")), 0)
self.checkCalledAnswerPrimaryMaster(conn, 0)
def test_12_handleAnnouncePrimaryMaster(self):
election = self.election
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=ANNOUNCE_PRIMARY_MASTER)
# No uuid
conn = Mock({"_addPacket" : None,
"getUUID" : None,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertEqual(len(self.app.nm.getMasterNodeList()), 1)
self.checkIdenficationRequired(election.handleAnnouncePrimaryMaster, conn, packet)
# announce
conn = Mock({"_addPacket" : None,
"getUUID" : uuid,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertEqual(self.app.primary, None)
self.assertEqual(self.app.primary_master_node, None)
election.handleAnnouncePrimaryMaster(conn, packet)
self.assertEqual(self.app.primary, False)
self.assertNotEqual(self.app.primary_master_node, None)
# set current as primary, and announce another, must raise
conn = Mock({"_addPacket" : None,
"getUUID" : uuid,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port)})
self.app.primary = True
self.assertEqual(self.app.primary, True)
self.assertRaises(ElectionFailure, election.handleAnnouncePrimaryMaster, conn, packet)
def test_13_handleReelectPrimaryMaster(self):
election = self.election
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = protocol.askPrimaryMaster()
# No uuid
conn = Mock({"_addPacket" : None,
"getUUID" : None,
"isServerConnection" : True,
"getAddress" : ("127.0.0.1", self.master_port)})
self.assertRaises(ElectionFailure, election.handleReelectPrimaryMaster, conn, packet)
def test_14_handleNotifyNodeInformation(self):
election = self.election
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=NOTIFY_NODE_INFORMATION)
# do not answer if no uuid
conn = Mock({"getUUID" : None,
"getAddress" : ("127.0.0.1", self.master_port)})
node_list = []
self.checkIdenficationRequired(election.handleNotifyNodeInformation, conn, packet, node_list)
# tell the master node about itself, must do nothing
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port - 1, self.app.uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
election.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
# tell about a storage node, do nothing
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.master_port - 1, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
election.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
# tell about a client node, do nothing
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
node_list = [(CLIENT_NODE_TYPE, '127.0.0.1', self.master_port - 1, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
election.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
# tell about another master node
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port + 1, self.getNewUUID(), RUNNING_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port+1))
self.assertEqual(node, None)
election.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port+1))
self.assertNotEqual(node, None)
self.assertEqual(node.getServer(), ("127.0.0.1", self.master_port+1))
self.assertEqual(node.getState(), RUNNING_STATE)
# tell that node is down
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port + 1, self.getNewUUID(), DOWN_STATE),]
election.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port+1))
self.assertNotEqual(node, None)
self.assertEqual(node.getServer(), ("127.0.0.1", self.master_port+1))
self.assertEqual(node.getState(), DOWN_STATE)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testMasterApp.py 0000664 0000000 0000000 00000011637 11227104631 0026035 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, logging, os
from mock import Mock
from neo.tests.base import NeoTestBase
from neo.master.app import Application
from neo.protocol import INVALID_PTID, INVALID_OID, INVALID_TID, \
INVALID_UUID, Packet, NOTIFY_NODE_INFORMATION
from neo.node import MasterNode, ClientNode, StorageNode
from neo.storage.mysqldb import p64, u64
class MasterAppTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile()
self.app = Application(config, "master1")
self.app.pt.clear()
def tearDown(self):
NeoTestBase.tearDown(self)
def test_02_getNextOID(self):
# must raise as we don"t have one
self.assertEqual(self.app.loid, INVALID_OID)
self.app.loid = None
self.assertRaises(RuntimeError, self.app.getNextOID)
# set one
self.app.loid = p64(23)
noid = self.app.getNextOID()
self.assertEqual(self.app.loid, noid)
self.failUnless(u64(self.app.loid) > 23)
self.assertEqual(u64(self.app.loid), 24)
def test_03_getNextTID(self):
self.assertEqual(self.app.ltid, INVALID_TID)
ntid = self.app.getNextTID()
self.assertEqual(self.app.ltid, ntid)
# generate new one
tid = self.app.getNextTID()
self.assertEqual(self.app.ltid, tid)
self.failUnless(tid > ntid)
def test_04_getPartition(self):
self.app.pt.num_partitions = 3
p = self.app.getPartition(p64(1))
self.assertEqual(p, 1)
p = self.app.getPartition(p64(2))
self.assertEqual(p, 2)
p = self.app.getPartition(p64(1009)) # 1009 defined in config
self.assertEqual(p, 0)
def test_05_getNewOIDList(self):
oid_list = self.app.getNewOIDList(15)
self.assertEqual(len(oid_list), 15)
i = 1
# begin from 0, so generated oid from 1 to 15
for oid in oid_list:
self.assertEqual(u64(oid), i)
i+=1
def test_06_broadcastNodeInformation(self):
# defined some nodes to which data will be send
master_uuid = self.getNewUUID()
master = MasterNode(uuid=master_uuid)
storage_uuid = self.getNewUUID()
storage = StorageNode(uuid=storage_uuid)
client_uuid = self.getNewUUID()
client = ClientNode(uuid=client_uuid)
self.app.nm.add(master)
self.app.nm.add(storage)
self.app.nm.add(client)
# create conn and patch em
master_conn = Mock({"getUUID" : master_uuid})
storage_conn = Mock({"getUUID" : storage_uuid})
client_conn = Mock({"getUUID" : client_uuid})
self.app.em = Mock({"getConnectionList" : (master_conn, storage_conn, client_conn)})
# no address defined, not send to client node
c_node = ClientNode(uuid = self.getNewUUID())
self.app.broadcastNodeInformation(c_node)
# check conn
self.checkNoPacketSent(client_conn)
self.checkNotifyNodeInformation(master_conn)
self.checkNotifyNodeInformation(storage_conn)
# address defined and client type
master_conn = Mock({"getUUID" : master_uuid})
storage_conn = Mock({"getUUID" : storage_uuid})
client_conn = Mock({"getUUID" : client_uuid})
self.app.em = Mock({"getConnectionList" : (master_conn, storage_conn, client_conn)})
s_node = ClientNode(uuid = self.getNewUUID(), server=("127.1.0.1", 3361))
self.app.broadcastNodeInformation(c_node)
# check conn
self.checkNoPacketSent(client_conn)
self.checkNotifyNodeInformation(master_conn)
self.checkNotifyNodeInformation(storage_conn)
# address defined and storage type
master_conn = Mock({"getUUID" : master_uuid})
storage_conn = Mock({"getUUID" : storage_uuid})
client_conn = Mock({"getUUID" : client_uuid})
self.app.em = Mock({"getConnectionList" : (master_conn, storage_conn, client_conn)})
s_node = StorageNode(uuid = self.getNewUUID(), server=("127.0.0.1", 1351))
self.app.broadcastNodeInformation(s_node)
# check conn
self.checkNotifyNodeInformation(client_conn)
self.checkNotifyNodeInformation(master_conn)
self.checkNotifyNodeInformation(storage_conn)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testMasterPT.py 0000664 0000000 0000000 00000050660 11227104631 0025637 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, os
from mock import Mock
from neo.tests.base import NeoTestBase
from neo.protocol import UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, \
DISCARDED_STATE, RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
BROKEN_STATE, INVALID_UUID
from neo.pt import Cell
from neo.master.pt import PartitionTable
from neo.node import StorageNode
class MasterPartitionTableTests(NeoTestBase):
def _test_01_setNextID(self):
pt = PartitionTable(100, 2)
# must raise as we don"t have one
self.assertEqual(pt.getID(), INVALID_PTID)
self.assertRaises(RuntimeError, pt.setNextID)
# set one
pt.setID(p64(23))
nptid = pt.setNextID()
self.assertEqual(pt.getID(), nptid)
self.assertTrue(u64(pt.getID()) > 23)
self.assertEqual(u64(pt.getID()), 24)
def test_02_PartitionTable_creation(self):
num_partitions = 5
num_replicas = 3
pt = PartitionTable(num_partitions, num_replicas)
self.assertEqual(pt.np, num_partitions)
self.assertEqual(pt.nr, num_replicas)
self.assertEqual(pt.num_filled_rows, 0)
partition_list = pt.partition_list
self.assertEqual(len(partition_list), num_partitions)
for x in xrange(num_partitions):
part = partition_list[x]
self.failUnless(isinstance(part, list))
self.assertEqual(len(part), 0)
self.assertEqual(len(pt.count_dict), 0)
# no nodes or cells for now
self.assertEqual(len(pt.getNodeList()), 0)
for x in xrange(num_partitions):
self.assertEqual(len(pt.getCellList(x)), 0)
self.assertEqual(len(pt.getCellList(x, True)), 0)
self.assertEqual(len(pt.getRow(x)), 0)
self.assertFalse(pt.operational())
self.assertFalse(pt.filled())
self.assertRaises(RuntimeError, pt.make, [])
self.assertFalse(pt.operational())
self.assertFalse(pt.filled())
def test_11_findLeastUsedNode(self):
num_partitions = 5
num_replicas = 2
pt = PartitionTable(num_partitions, num_replicas)
# add nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
pt.setCell(0, sn1, UP_TO_DATE_STATE)
pt.setCell(1, sn1, UP_TO_DATE_STATE)
pt.setCell(2, sn1, UP_TO_DATE_STATE)
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19001)
sn2 = StorageNode(server2, uuid2)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
pt.setCell(1, sn2, UP_TO_DATE_STATE)
uuid3 = self.getNewUUID()
server3 = ("127.0.0.3", 19001)
sn3 = StorageNode(server3, uuid3)
pt.setCell(0, sn3, UP_TO_DATE_STATE)
# test
node = pt.findLeastUsedNode()
self.assertEqual(node, sn3)
node = pt.findLeastUsedNode((sn3,))
self.assertEqual(node, sn2)
node = pt.findLeastUsedNode((sn3,sn2))
self.assertEqual(node, sn1)
def test_13_outdate(self):
# create nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19002)
sn2 = StorageNode(server2, uuid2)
uuid3 = self.getNewUUID()
server3 = ("127.0.0.3", 19003)
sn3 = StorageNode(server3, uuid3)
uuid4 = self.getNewUUID()
server4 = ("127.0.0.4", 19004)
sn4 = StorageNode(server4, uuid4)
uuid5 = self.getNewUUID()
server5 = ("127.0.0.5", 19005)
sn5 = StorageNode(server5, uuid5)
# create partition table
num_partitions = 5
num_replicas = 3
pt = PartitionTable(num_partitions, num_replicas)
pt.setCell(0, sn1, OUT_OF_DATE_STATE)
sn1.setState(RUNNING_STATE)
pt.setCell(1, sn2, UP_TO_DATE_STATE)
sn2.setState(TEMPORARILY_DOWN_STATE)
pt.setCell(2, sn3, UP_TO_DATE_STATE)
sn3.setState(DOWN_STATE)
pt.setCell(3, sn4, UP_TO_DATE_STATE)
sn4.setState(BROKEN_STATE)
pt.setCell(4, sn5, UP_TO_DATE_STATE)
sn5.setState(RUNNING_STATE)
# outdate nodes
cells_outdated = pt.outdate()
self.assertEqual(len(cells_outdated), 3)
for offset, uuid, state in cells_outdated:
self.failUnless(offset in (1,2,3))
self.failUnless(uuid in (uuid2,uuid3,uuid4))
self.assertEqual(state, OUT_OF_DATE_STATE)
# check each cell
# part 1, already outdated
cells = pt.getCellList(0)
self.assertEqual(len(cells), 1)
cell = cells[0]
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
# part 2, must be outdated
cells = pt.getCellList(1)
self.assertEqual(len(cells), 1)
cell = cells[0]
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
# part 3, must be outdated
cells = pt.getCellList(2)
self.assertEqual(len(cells), 1)
cell = cells[0]
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
# part 4, already outdated
cells = pt.getCellList(3)
self.assertEqual(len(cells), 1)
cell = cells[0]
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
# part 5, remains running
cells = pt.getCellList(4)
self.assertEqual(len(cells), 1)
cell = cells[0]
self.assertEqual(cell.getState(), UP_TO_DATE_STATE)
def test_14_addNode(self):
num_partitions = 5
num_replicas = 2
pt = PartitionTable(num_partitions, num_replicas)
# add nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
# add it to an empty pt
cell_list = pt.addNode(sn1)
self.assertEqual(len(cell_list), 5)
# it must be added to all partitions
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 1)
self.assertEqual(pt.getCellList(x)[0].getState(), OUT_OF_DATE_STATE)
self.assertEqual(pt.getCellList(x)[0].getNode(), sn1)
self.assertEqual(pt.count_dict[sn1], 5)
# add same node again, must remain the same
cell_list = pt.addNode(sn1)
self.assertEqual(len(cell_list), 0)
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 1)
self.assertEqual(pt.getCellList(x)[0].getState(), OUT_OF_DATE_STATE)
self.assertEqual(pt.getCellList(x)[0].getNode(), sn1)
self.assertEqual(pt.count_dict[sn1], 5)
# add a second node to fill the partition table
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19002)
sn2 = StorageNode(server2, uuid2)
# add it
cell_list = pt.addNode(sn2)
self.assertEqual(len(cell_list), 5)
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 2)
self.assertEqual(pt.getCellList(x)[0].getState(), OUT_OF_DATE_STATE)
self.failUnless(pt.getCellList(x)[0].getNode() in (sn1, sn2))
# test the most used node is remove from some partition
uuid3 = self.getNewUUID()
server3 = ("127.0.0.3", 19001)
sn3 = StorageNode(server3, uuid3)
uuid4 = self.getNewUUID()
server4 = ("127.0.0.4", 19001)
sn4 = StorageNode(server4, uuid4)
uuid5 = self.getNewUUID()
server5 = ("127.0.0.5", 1900)
sn5 = StorageNode(server5, uuid5)
# partition looks like:
# 0 : sn1, sn2
# 1 : sn1, sn3
# 2 : sn1, sn4
# 3 : sn1, sn5
num_partitions = 4
num_replicas = 1
pt = PartitionTable(num_partitions, num_replicas)
# node most used is out of date, just dropped
pt.setCell(0, sn1, OUT_OF_DATE_STATE)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
pt.setCell(1, sn1, OUT_OF_DATE_STATE)
pt.setCell(1, sn3, UP_TO_DATE_STATE)
pt.setCell(2, sn1, OUT_OF_DATE_STATE)
pt.setCell(2, sn4, UP_TO_DATE_STATE)
pt.setCell(3, sn1, OUT_OF_DATE_STATE)
pt.setCell(3, sn5, UP_TO_DATE_STATE)
uuid6 = self.getNewUUID()
server6 = ("127.0.0.6", 19006)
sn6 = StorageNode(server6, uuid6)
cell_list = pt.addNode(sn6)
# sn1 is removed twice and sn6 is added twice
self.assertEqual(len(cell_list), 4)
for offset, uuid, state in cell_list:
if offset in (0,1):
if uuid == uuid1:
self.assertEqual(state, DISCARDED_STATE)
elif uuid == uuid6:
self.assertEqual(state, OUT_OF_DATE_STATE)
else:
self.failUnless(uuid in (uuid1, uuid6))
else:
self.failUnless(offset in (0, 1))
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 2)
# there is a feeding cell, just dropped
pt.clear()
pt.setCell(0, sn1, UP_TO_DATE_STATE)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
pt.setCell(0, sn3, FEEDING_STATE)
pt.setCell(1, sn1, UP_TO_DATE_STATE)
pt.setCell(1, sn2, FEEDING_STATE)
pt.setCell(1, sn3, UP_TO_DATE_STATE)
pt.setCell(2, sn1, UP_TO_DATE_STATE)
pt.setCell(2, sn4, FEEDING_STATE)
pt.setCell(2, sn5, UP_TO_DATE_STATE)
pt.setCell(3, sn1, UP_TO_DATE_STATE)
pt.setCell(3, sn4, UP_TO_DATE_STATE)
pt.setCell(3, sn5, FEEDING_STATE)
cell_list = pt.addNode(sn6)
# sn1 is removed twice and sn6 is added twice
self.assertEqual(len(cell_list), 4)
for offset, uuid, state in cell_list:
if offset in (0,1):
if uuid == uuid1:
self.assertEqual(state, DISCARDED_STATE)
elif uuid == uuid6:
self.assertEqual(state, OUT_OF_DATE_STATE)
else:
self.failUnless(uuid in (uuid1, uuid6))
else:
self.failUnless(offset in (0, 1))
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 3)
# there is no feeding cell, marked as feeding
pt.clear()
pt.setCell(0, sn1, UP_TO_DATE_STATE)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
pt.setCell(1, sn1, UP_TO_DATE_STATE)
pt.setCell(1, sn3, UP_TO_DATE_STATE)
pt.setCell(2, sn1, UP_TO_DATE_STATE)
pt.setCell(2, sn4, UP_TO_DATE_STATE)
pt.setCell(3, sn1, UP_TO_DATE_STATE)
pt.setCell(3, sn5, UP_TO_DATE_STATE)
cell_list = pt.addNode(sn6)
# sn1 is removed twice and sn6 is added twice
self.assertEqual(len(cell_list), 4)
for offset, uuid, state in cell_list:
if offset in (0,1):
if uuid == uuid1:
self.assertEqual(state, FEEDING_STATE)
elif uuid == uuid6:
self.assertEqual(state, OUT_OF_DATE_STATE)
else:
self.failUnless(uuid in (uuid1, uuid6))
else:
self.failUnless(offset in (0, 1))
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 3)
def test_15_dropNode(self):
num_partitions = 4
num_replicas = 2
pt = PartitionTable(num_partitions, num_replicas)
# add nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19002)
sn2 = StorageNode(server2, uuid2)
uuid3 = self.getNewUUID()
server3 = ("127.0.0.3", 19001)
sn3 = StorageNode(server3, uuid3)
uuid4 = self.getNewUUID()
server4 = ("127.0.0.4", 19001)
sn4 = StorageNode(server4, uuid4)
# partition looks like:
# 0 : sn1, sn2
# 1 : sn1, sn3
# 2 : sn1, sn3
# 3 : sn1, sn4
# node is not feeding, so retrive least use node to replace it
# so sn2 must be repaced by sn4 in partition 0
pt.setCell(0, sn1, UP_TO_DATE_STATE)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
pt.setCell(1, sn1, UP_TO_DATE_STATE)
pt.setCell(1, sn3, UP_TO_DATE_STATE)
pt.setCell(2, sn1, UP_TO_DATE_STATE)
pt.setCell(2, sn3, UP_TO_DATE_STATE)
pt.setCell(3, sn1, UP_TO_DATE_STATE)
pt.setCell(3, sn4, UP_TO_DATE_STATE)
cell_list = pt.dropNode(sn2)
self.assertEqual(len(cell_list), 2)
for offset, uuid, state in cell_list:
self.assertEqual(offset, 0)
if uuid == uuid2:
self.assertEqual(state, DISCARDED_STATE)
elif uuid == uuid4:
self.assertEqual(state, OUT_OF_DATE_STATE)
else:
self.failUnless(uuid in (uuid2, uuid4))
for x in xrange(num_replicas):
self.assertEqual(len(pt.getCellList(x)), 2)
# same test but with feeding state, no other will be added
pt.clear()
pt.setCell(0, sn1, UP_TO_DATE_STATE)
pt.setCell(0, sn2, FEEDING_STATE)
pt.setCell(1, sn1, UP_TO_DATE_STATE)
pt.setCell(1, sn3, UP_TO_DATE_STATE)
pt.setCell(2, sn1, UP_TO_DATE_STATE)
pt.setCell(2, sn3, UP_TO_DATE_STATE)
pt.setCell(3, sn1, UP_TO_DATE_STATE)
pt.setCell(3, sn4, UP_TO_DATE_STATE)
cell_list = pt.dropNode(sn2)
self.assertEqual(len(cell_list), 1)
for offset, uuid, state in cell_list:
self.assertEqual(offset, 0)
self.assertEqual(state, DISCARDED_STATE)
self.assertEqual(uuid ,uuid2)
for x in xrange(num_replicas):
if x == 0:
self.assertEqual(len(pt.getCellList(x)), 1)
else:
self.assertEqual(len(pt.getCellList(x)), 2)
def test_16_make(self):
num_partitions = 5
num_replicas = 1
pt = PartitionTable(num_partitions, num_replicas)
# add nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
# add not running node
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19001)
sn2 = StorageNode(server2, uuid2)
sn2.setState(TEMPORARILY_DOWN_STATE)
# add node without uuid
server3 = ("127.0.0.3", 19001)
sn3 = StorageNode(server3, None)
# add clear node
uuid4 = self.getNewUUID()
server4 = ("127.0.0.4", 19001)
sn4 = StorageNode(server4, uuid4)
uuid5 = self.getNewUUID()
server5 = ("127.0.0.5", 1900)
sn5 = StorageNode(server5, uuid5)
# make the table
pt.make([sn1, sn2, sn3, sn4, sn5,])
# check it's ok, only running nodes and node with uuid
# must be present
for x in xrange(num_partitions):
cells = pt.getCellList(x)
self.assertEqual(len(cells), 2)
nodes = [x.getNode() for x in cells]
for node in nodes:
self.failUnless(node in (sn1, sn4, sn5))
self.failUnless(node not in (sn2, sn3))
self.assertTrue(pt.filled())
self.assertTrue(pt.operational())
# create a pt with less nodes
pt.clear()
self.assertFalse(pt.filled())
self.assertFalse(pt.operational())
pt.make([sn1,])
# check it's ok
for x in xrange(num_partitions):
cells = pt.getCellList(x)
self.assertEqual(len(cells), 1)
nodes = [x.getNode() for x in cells]
for node in nodes:
self.assertEqual(node, sn1)
self.assertTrue(pt.filled())
self.assertTrue(pt.operational())
def test_17_tweak(self):
# remove broken node
# remove if too many feeding nodes
# remove feeding if all cells are up to date
# if too many cells, remove most used cell
# if not enought cell, add least used node
# create nodes
uuid1 = self.getNewUUID()
server1 = ("127.0.0.1", 19001)
sn1 = StorageNode(server1, uuid1)
uuid2 = self.getNewUUID()
server2 = ("127.0.0.2", 19002)
sn2 = StorageNode(server2, uuid2)
uuid3 = self.getNewUUID()
server3 = ("127.0.0.3", 19003)
sn3 = StorageNode(server3, uuid3)
uuid4 = self.getNewUUID()
server4 = ("127.0.0.4", 19004)
sn4 = StorageNode(server4, uuid4)
uuid5 = self.getNewUUID()
server5 = ("127.0.0.5", 19005)
sn5 = StorageNode(server5, uuid5)
# create partition table
# 0 : sn1(discarded), sn2(up), -> sn2 must remain
# 1 : sn1(feeding), sn2(feeding), sn3(up) -> one feeding and sn3 must remain
# 2 : sn1(feeding), sn2(up), sn3(up) -> sn2 and sn3 must remain, feeding must go away
# 3 : sn1(up), sn2(up), sn3(up), sn4(up) -> only 3 cell must remain
# 4 : sn1(up), sn5(up) -> one more cell must be added
num_partitions = 5
num_replicas = 2
pt = PartitionTable(num_partitions, num_replicas)
# part 0
pt.setCell(0, sn1, DISCARDED_STATE)
pt.setCell(0, sn2, UP_TO_DATE_STATE)
# part 1
pt.setCell(1, sn1, FEEDING_STATE)
pt.setCell(1, sn2, FEEDING_STATE)
pt.setCell(1, sn3, OUT_OF_DATE_STATE)
# part 2
pt.setCell(2, sn1, FEEDING_STATE)
pt.setCell(2, sn2, UP_TO_DATE_STATE)
pt.setCell(2, sn3, UP_TO_DATE_STATE)
# part 3
pt.setCell(3, sn1, UP_TO_DATE_STATE)
pt.setCell(3, sn2, UP_TO_DATE_STATE)
pt.setCell(3, sn3, UP_TO_DATE_STATE)
pt.setCell(3, sn4, UP_TO_DATE_STATE)
# part 4
pt.setCell(4, sn1, UP_TO_DATE_STATE)
pt.setCell(4, sn5, UP_TO_DATE_STATE)
# now tweak the table
pt.tweak()
# check part 1
cells = pt.getCellList(0)
self.assertEqual(len(cells), 3)
for cell in cells:
self.assertNotEqual(cell.getState(), DISCARDED_STATE)
if cell.getNode() == sn2:
self.assertEqual(cell.getState(), UP_TO_DATE_STATE)
else:
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
self.failUnless(sn2 in [x.getNode() for x in cells])
# check part 2
cells = pt.getCellList(1)
self.assertEqual(len(cells), 4)
for cell in cells:
if cell.getNode() == sn1:
self.assertEqual(cell.getState(), FEEDING_STATE)
else:
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
self.failUnless(sn3 in [x.getNode() for x in cells])
self.failUnless(sn1 in [x.getNode() for x in cells])
# check part 3
cells = pt.getCellList(2)
self.assertEqual(len(cells), 3)
for cell in cells:
if cell.getNode() in (sn2, sn3):
self.assertEqual(cell.getState(), UP_TO_DATE_STATE)
else:
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
self.failUnless(sn3 in [x.getNode() for x in cells])
self.failUnless(sn2 in [x.getNode() for x in cells])
# check part 4
cells = pt.getCellList(3)
self.assertEqual(len(cells), 3)
for cell in cells:
self.assertEqual(cell.getState(), UP_TO_DATE_STATE)
# check part 5
cells = pt.getCellList(4)
self.assertEqual(len(cells), 3)
for cell in cells:
if cell.getNode() in (sn1, sn5):
self.assertEqual(cell.getState(), UP_TO_DATE_STATE)
else:
self.assertEqual(cell.getState(), OUT_OF_DATE_STATE)
self.failUnless(sn1 in [x.getNode() for x in cells])
self.failUnless(sn5 in [x.getNode() for x in cells])
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testRecoveryHandler.py 0000664 0000000 0000000 00000027216 11227104631 0027235 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from struct import pack, unpack
from neo.tests.base import NeoTestBase
from neo import protocol
from neo.protocol import Packet, INVALID_UUID
from neo.master.handlers import RecoveryHandler
from neo.master.app import Application
from neo.protocol import ERROR, REQUEST_NODE_IDENTIFICATION, ACCEPT_NODE_IDENTIFICATION, \
PING, PONG, ASK_PRIMARY_MASTER, ANSWER_PRIMARY_MASTER, ANNOUNCE_PRIMARY_MASTER, \
REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, START_OPERATION, \
STOP_OPERATION, ASK_LAST_IDS, ANSWER_LAST_IDS, ASK_PARTITION_TABLE, \
ANSWER_PARTITION_TABLE, SEND_PARTITION_TABLE, NOTIFY_PARTITION_CHANGES, \
ASK_UNFINISHED_TRANSACTIONS, ANSWER_UNFINISHED_TRANSACTIONS, \
ASK_OBJECT_PRESENT, ANSWER_OBJECT_PRESENT, \
DELETE_TRANSACTION, COMMIT_TRANSACTION, ASK_NEW_TID, ANSWER_NEW_TID, \
FINISH_TRANSACTION, NOTIFY_TRANSACTION_FINISHED, LOCK_INFORMATION, \
NOTIFY_INFORMATION_LOCKED, INVALIDATE_OBJECTS, UNLOCK_INFORMATION, \
ASK_NEW_OIDS, ANSWER_NEW_OIDS, ASK_STORE_OBJECT, ANSWER_STORE_OBJECT, \
ABORT_TRANSACTION, ASK_STORE_TRANSACTION, ANSWER_STORE_TRANSACTION, \
ASK_OBJECT, ANSWER_OBJECT, ASK_TIDS, ANSWER_TIDS, ASK_TRANSACTION_INFORMATION, \
ANSWER_TRANSACTION_INFORMATION, ASK_OBJECT_HISTORY, ANSWER_OBJECT_HISTORY, \
ASK_OIDS, ANSWER_OIDS, \
NOT_READY_CODE, OID_NOT_FOUND_CODE, SERIAL_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \
PROTOCOL_ERROR_CODE, TIMEOUT_ERROR_CODE, BROKEN_NODE_DISALLOWED_CODE, \
INTERNAL_ERROR_CODE, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.exception import OperationFailure, ElectionFailure
from neo.node import MasterNode, StorageNode
from neo.master.tests.connector import DoNothingConnector
from neo.connection import ClientConnection
class MasterRecoveryTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile()
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.recovery = RecoveryHandler(self.app)
self.app.unconnected_master_node_set = set()
self.app.negotiating_master_node_set = set()
for node in self.app.nm.getMasterNodeList():
self.app.unconnected_master_node_set.add(node.getServer())
node.setState(RUNNING_STATE)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10011
self.master_address = ('127.0.0.1', self.master_port)
self.storage_address = ('127.0.0.1', self.storage_port)
def tearDown(self):
NeoTestBase.tearDown(self)
# Common methods
def getLastUUID(self):
return self.uuid
def identifyToMasterNode(self, node_type=STORAGE_NODE_TYPE, ip="127.0.0.1",
port=10021):
"""Do first step of identification to MN
"""
uuid = self.getNewUUID()
return uuid
# Tests
def test_01_connectionClosed(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.recovery.connectionClosed(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_02_timeoutExpired(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.recovery.timeoutExpired(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_03_peerBroken(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.recovery.peerBroken(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), BROKEN_STATE)
def test_08_handleNotifyNodeInformation(self):
recovery = self.recovery
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=NOTIFY_NODE_INFORMATION)
# tell about a client node, do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(CLIENT_NODE_TYPE, '127.0.0.1', self.client_port, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
recovery.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
# tell the master node about itself, if running must do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port-1, self.app.uuid, RUNNING_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
recovery.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
# tell the master node about itself, if down must raise
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port-1, self.app.uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
self.assertRaises(RuntimeError, recovery.handleNotifyNodeInformation, conn, packet, node_list)
# tell about an unknown storage node, do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.master_port - 1, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
recovery.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
# tell about a known node but different address
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.2', self.master_port, uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
recovery.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
# tell about a known node
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
recovery.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), DOWN_STATE)
def test_09_handleAnswerLastIDs(self):
recovery = self.recovery
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_LAST_IDS)
loid = self.app.loid
ltid = self.app.ltid
lptid = self.app.pt.getID()
# send information which are later to what PMN knows, this must update target node
conn = self.getFakeConnection(uuid, self.storage_port)
node_list = []
new_ptid = unpack('!Q', lptid)[0]
new_ptid = pack('!Q', new_ptid + 1)
oid = unpack('!Q', loid)[0]
new_oid = pack('!Q', oid + 1)
upper, lower = unpack('!LL', ltid)
new_tid = pack('!LL', upper, lower + 10)
self.failUnless(new_ptid > self.app.pt.getID())
self.failUnless(new_oid > self.app.loid)
self.failUnless(new_tid > self.app.ltid)
self.assertEquals(self.app.target_uuid, None)
recovery.handleAnswerLastIDs(conn, packet, new_oid, new_tid, new_ptid)
self.assertEquals(new_oid, self.app.loid)
self.assertEquals(new_tid, self.app.ltid)
self.assertEquals(new_ptid, self.app.pt.getID())
self.assertEquals(self.app.target_uuid,uuid)
def test_10_handleAnswerPartitionTable(self):
recovery = self.recovery
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=ANSWER_PARTITION_TABLE)
# not from target node, ignore
uuid = self.identifyToMasterNode(STORAGE_NODE_TYPE, port=self.storage_port)
conn = self.getFakeConnection(uuid, self.storage_port)
self.assertNotEquals(self.app.target_uuid, uuid)
offset = 1
cell_list = [(offset, uuid, UP_TO_DATE_STATE)]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
recovery.handleAnswerPartitionTable(conn, packet, None, cell_list)
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
# from target node, taken into account
conn = self.getFakeConnection(uuid, self.storage_port)
self.assertNotEquals(self.app.target_uuid, uuid)
self.app.target_uuid = uuid
self.assertEquals(self.app.target_uuid, uuid)
offset = 1
cell_list = [(offset, ((uuid, UP_TO_DATE_STATE,),),)]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
recovery.handleAnswerPartitionTable(conn, packet, None, cell_list)
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, UP_TO_DATE_STATE)
# give a bad offset, must send error
conn = self.getFakeConnection(uuid, self.storage_port)
self.assertEquals(self.app.target_uuid, uuid)
offset = 1000000
self.assertFalse(self.app.pt.hasOffset(offset))
cell_list = [(offset, ((uuid, DOWN_STATE,),),)]
self.checkUnexpectedPacketRaised(recovery.handleAnswerPartitionTable, conn, packet, None, cell_list)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testStorageHandler.py 0000664 0000000 0000000 00000055107 11227104631 0027043 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from struct import pack, unpack
from neo.tests.base import NeoTestBase
import neo.master
from neo import protocol
from neo.protocol import Packet, INVALID_UUID
from neo.master.handlers import StorageServiceHandler
from neo.master.app import Application
from neo.protocol import ERROR, PING, PONG, ANNOUNCE_PRIMARY_MASTER, \
REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, \
ASK_LAST_IDS, ANSWER_LAST_IDS, NOTIFY_PARTITION_CHANGES, \
ASK_UNFINISHED_TRANSACTIONS, ASK_NEW_TID, FINISH_TRANSACTION, \
NOTIFY_INFORMATION_LOCKED, ASK_NEW_OIDS, ABORT_TRANSACTION, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.exception import OperationFailure, ElectionFailure
from neo.node import MasterNode, StorageNode
class MasterStorageHandlerTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile(master_number=1, replicas=1)
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.pt.setID(pack('!Q', 1))
self.app.em = Mock({"getConnectionList" : []})
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.service = StorageServiceHandler(self.app)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10010
self.master_address = ('127.0.0.1', self.master_port)
self.client_address = ('127.0.0.1', self.client_port)
self.storage_address = ('127.0.0.1', self.storage_port)
def tearDown(self):
NeoTestBase.tearDown(self)
def getLastUUID(self):
return self.uuid
def identifyToMasterNode(self, node_type=STORAGE_NODE_TYPE, ip="127.0.0.1",
port=10021):
"""Do first step of identification to MN
"""
uuid = self.getNewUUID()
return uuid
def test_05_handleNotifyNodeInformation(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=NOTIFY_NODE_INFORMATION)
# tell the master node that is not running any longer, it must raises
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, self.app.uuid, DOWN_STATE),]
self.assertRaises(RuntimeError, service.handleNotifyNodeInformation, conn, packet, node_list)
# tell the master node that it's running, nothing change
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, self.app.uuid, RUNNING_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about a client node, don't care
new_uuid = self.getNewUUID()
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(CLIENT_NODE_TYPE, '127.0.0.1', self.client_port, new_uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about an unknown node, don't care
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', 11010, new_uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify about a known node but with bad address, don't care
self.app.nm.add(StorageNode(("127.0.0.1", 11011), self.getNewUUID()))
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', 11012, uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify node is running, as PMN already know it, nothing is done
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, RUNNING_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
# notify node is temp down, must be taken into account
ptid = self.app.pt.getID()
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, TEMPORARILY_DOWN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
sn = self.app.nm.getStorageNodeList()[0]
self.assertEquals(sn.getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(ptid, self.app.pt.getID())
# notify node is broken, must be taken into account and partition must changed
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.storage_port, uuid, BROKEN_STATE),]
service.handleNotifyNodeInformation(conn, packet, node_list)
for call in conn.mockGetAllCalls():
self.assertEquals(call.getName(), "getUUID")
sn = self.app.nm.getStorageNodeList()[0]
self.assertEquals(sn.getState(), BROKEN_STATE)
self.failUnless(ptid < self.app.pt.getID())
def test_06_handleAnswerLastIDs(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_LAST_IDS)
loid = self.app.loid
ltid = self.app.ltid
lptid = self.app.pt.getID()
# send information which are later to what PMN knows, this must raise
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = []
new_ptid = unpack('!Q', lptid)[0]
new_ptid = pack('!Q', new_ptid + 1)
self.failUnless(new_ptid > self.app.pt.getID())
self.assertRaises(OperationFailure, service.handleAnswerLastIDs, conn, packet, None, None, new_ptid)
self.assertEquals(loid, self.app.loid)
self.assertEquals(ltid, self.app.ltid)
self.assertEquals(lptid, self.app.pt.getID())
def test_10_handleNotifyInformationLocked(self):
service = self.service
uuid = self.identifyToMasterNode(port=10020)
packet = Packet(msg_type=NOTIFY_INFORMATION_LOCKED)
# give an older tid than the PMN known, must abort
conn = self.getFakeConnection(uuid, self.storage_address)
oid_list = []
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
self.checkUnexpectedPacketRaised(service.handleNotifyInformationLocked, conn, packet, new_tid)
old_node = self.app.nm.getNodeByUUID(uuid)
# job done through dispatch -> peerBroken
self.app.nm.remove(old_node)
self.app.pt.dropNode(old_node)
# do the right job
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE, port=self.client_port)
storage_uuid_1 = self.identifyToMasterNode()
storage_uuid_2 = self.identifyToMasterNode(port=10022)
storage_conn_1 = self.getFakeConnection(storage_uuid_1, ("127.0.0.1", self.storage_port))
storage_conn_2 = self.getFakeConnection(storage_uuid_2, ("127.0.0.1", 10022))
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewTID(conn, packet)
# clean mock object
conn.mockCalledMethods = {}
conn.mockAllCalledMethods = []
self.app.em = Mock({"getConnectionList" : [conn, storage_conn_1, storage_conn_2]})
oid_list = []
tid = self.app.ltid
service.handleFinishTransaction(conn, packet, oid_list, tid)
self.checkLockInformation(storage_conn_1)
self.checkLockInformation(storage_conn_2)
self.assertFalse(self.app.finishing_transaction_dict.values()[0].allLocked())
service.handleNotifyInformationLocked(storage_conn_1, packet, tid)
self.checkLockInformation(storage_conn_1)
self.checkLockInformation(storage_conn_2)
self.assertFalse(self.app.finishing_transaction_dict.values()[0].allLocked())
service.handleNotifyInformationLocked(storage_conn_2, packet, tid)
self.checkNotifyTransactionFinished(conn)
self.checkLockInformation(storage_conn_1)
self.checkLockInformation(storage_conn_2)
def test_12_handleAskLastIDs(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_LAST_IDS)
# give a uuid
conn = self.getFakeConnection(uuid, self.storage_address)
ptid = self.app.pt.getID()
tid = self.app.ltid
oid = self.app.loid
service.handleAskLastIDs(conn, packet)
packet = self.checkAnswerLastIDs(conn, answered_packet=packet)
loid, ltid, lptid = protocol._decodeAnswerLastIDs(packet._body)
self.assertEqual(loid, oid)
self.assertEqual(ltid, tid)
self.assertEqual(lptid, ptid)
def test_13_handleAskUnfinishedTransactions(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ASK_UNFINISHED_TRANSACTIONS)
# give a uuid
conn = self.getFakeConnection(uuid, self.storage_address)
service.handleAskUnfinishedTransactions(conn, packet)
packet = self.checkAnswerUnfinishedTransactions(conn, answered_packet=packet)
tid_list = protocol._decodeAnswerUnfinishedTransactions(packet._body)[0]
self.assertEqual(len(tid_list), 0)
# create some transaction
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
conn = self.getFakeConnection(uuid, self.storage_address)
service.handleAskUnfinishedTransactions(conn, packet)
packet = self.checkAnswerUnfinishedTransactions(conn, answered_packet=packet)
tid_list = protocol._decodeAnswerUnfinishedTransactions(packet._body)[0]
self.assertEqual(len(tid_list), 3)
def test_14_handleNotifyPartitionChanges(self):
service = self.service
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=NOTIFY_PARTITION_CHANGES)
# do not answer if not a storage node
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port=self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
self.checkUnexpectedPacketRaised(service.handleNotifyPartitionChanges,
conn, packet, None, None)
# send a bad state, must not be take into account
conn = self.getFakeConnection(uuid, self.storage_address)
storage_uuid = self.identifyToMasterNode(port=self.storage_port+1)
offset = 1
cell_list = [(offset, uuid, FEEDING_STATE),]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
service.handleNotifyPartitionChanges(conn, packet, self.app.pt.getID(), cell_list)
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
# send for another node, must not be take into account
conn = self.getFakeConnection(uuid, self.storage_address)
offset = 1
cell_list = [(offset, storage_uuid, UP_TO_DATE_STATE),]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
service.handleNotifyPartitionChanges(conn, packet, self.app.pt.getID(), cell_list)
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
# send for itself, must be taken into account
# and the feeding node must be removed
conn = self.getFakeConnection(uuid, self.storage_address)
cell_list = [(offset, uuid, UP_TO_DATE_STATE),]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
self.assertEquals(state, OUT_OF_DATE_STATE)
# mark the second storage node as feeding and say we are up to date
# second node must go to discarded state and first one to up to date state
self.app.pt.setCell(offset, self.app.nm.getNodeByUUID(storage_uuid), FEEDING_STATE)
cell_list = [(offset, uuid, UP_TO_DATE_STATE),]
cells = self.app.pt.getRow(offset)
for cell, state in cells:
if cell == storage_uuid:
self.assertEquals(state, FEEDING_STATE)
else:
self.assertEquals(state, OUT_OF_DATE_STATE)
lptid = self.app.pt.getID()
service.handleNotifyPartitionChanges(conn, packet, self.app.pt.getID(), cell_list)
self.failUnless(lptid < self.app.pt.getID())
cells = self.app.pt.getRow(offset)
for cell, state in cells:
if cell == uuid:
self.assertEquals(state, UP_TO_DATE_STATE)
else:
self.assertEquals(state, DISCARDED_STATE)
def test_15_peerBroken(self):
service = self.service
uuid = self.identifyToMasterNode()
# do nothing if no uuid
conn = self.getFakeConnection(None, self.storage_address)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
service.peerBroken(conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
# add a second storage node and then declare it as broken
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.peerBroken(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), BROKEN_STATE)
self.failUnless(lptid < self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.peerBroken, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), BROKEN_STATE)
self.failUnless(lptid < self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.peerBroken(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
def test_16_timeoutExpired(self):
service = self.service
uuid = self.identifyToMasterNode()
# do nothing if no uuid
conn = self.getFakeConnection(None, self.storage_address)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
service.timeoutExpired(conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
# add a second storage node and then declare it as temp down
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.timeoutExpired(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.timeoutExpired, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.timeoutExpired(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
def test_17_connectionClosed(self):
service = self.service
uuid = self.identifyToMasterNode()
# do nothing if no uuid
conn = self.getFakeConnection(None, self.storage_address)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
service.connectionClosed(conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
# add a second storage node and then declare it as temp down
self.identifyToMasterNode(port = self.storage_port+2)
storage_uuid = self.identifyToMasterNode(port = self.storage_port+1)
# filled the pt
self.app.pt.make(self.app.nm.getStorageNodeList())
self.assertTrue(self.app.pt.filled())
self.assertTrue(self.app.pt.operational())
conn = self.getFakeConnection(storage_uuid, ('127.0.0.1', self.storage_port+1))
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), RUNNING_STATE)
service.connectionClosed(conn)
self.assertEquals(self.app.nm.getNodeByUUID(storage_uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give an uuid, must raise as no other storage node available
conn = self.getFakeConnection(uuid, self.storage_address)
lptid = self.app.pt.getID()
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), RUNNING_STATE)
self.assertRaises(OperationFailure, service.connectionClosed, conn)
self.assertEquals(self.app.nm.getNodeByUUID(uuid).getState(), TEMPORARILY_DOWN_STATE)
self.assertEquals(lptid, self.app.pt.getID())
# give a client uuid which have unfinished transactions
client_uuid = self.identifyToMasterNode(node_type=CLIENT_NODE_TYPE,
port = self.client_port)
conn = self.getFakeConnection(client_uuid, self.client_address)
lptid = self.app.pt.getID()
packet = Packet(msg_type=ASK_NEW_TID)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
service.handleAskNewTID(conn, packet)
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid).getState(), RUNNING_STATE)
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 3)
service.connectionClosed(conn)
# node must be have been remove, and no more transaction must remains
self.assertEquals(self.app.nm.getNodeByUUID(client_uuid), None)
self.assertEquals(lptid, self.app.pt.getID())
self.assertEquals(len(self.app.finishing_transaction_dict.keys()), 0)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/master/tests/testVerificationHandler.py 0000664 0000000 0000000 00000044174 11227104631 0030063 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from struct import pack, unpack
import neo
from neo.tests.base import NeoTestBase
from neo.protocol import Packet, INVALID_UUID
from neo.master.handlers import VerificationHandler
from neo.master.app import Application
from neo import protocol
from neo.protocol import ERROR, ANNOUNCE_PRIMARY_MASTER, \
NOTIFY_NODE_INFORMATION, ANSWER_LAST_IDS, ANSWER_PARTITION_TABLE, \
ANSWER_UNFINISHED_TRANSACTIONS, ANSWER_OBJECT_PRESENT, \
ANSWER_TRANSACTION_INFORMATION, OID_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
from neo.exception import OperationFailure, ElectionFailure, VerificationFailure
from neo.node import MasterNode, StorageNode
from neo.master.tests.connector import DoNothingConnector
from neo.connection import ClientConnection
class MasterVerificationTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
# create an application object
config = self.getConfigFile(master_number=2)
self.app = Application(config, "master1")
self.app.pt.clear()
self.app.finishing_transaction_dict = {}
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server = server))
self.verification = VerificationHandler(self.app)
self.app.unconnected_master_node_set = set()
self.app.negotiating_master_node_set = set()
self.app.asking_uuid_dict = {}
self.app.unfinished_tid_set = set()
for node in self.app.nm.getMasterNodeList():
self.app.unconnected_master_node_set.add(node.getServer())
node.setState(RUNNING_STATE)
# define some variable to simulate client and storage node
self.client_port = 11022
self.storage_port = 10021
self.master_port = 10011
self.master_address = ('127.0.0.1', self.master_port)
self.storage_address = ('127.0.0.1', self.storage_port)
def tearDown(self):
NeoTestBase.tearDown(self)
# Common methods
def getLastUUID(self):
return self.uuid
def identifyToMasterNode(self, node_type=STORAGE_NODE_TYPE, ip="127.0.0.1",
port=10021):
"""Do first step of identification to MN
"""
uuid = self.getNewUUID()
return uuid
# Tests
def test_01_connectionClosed(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.verification.connectionClosed(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
# test a storage, must raise as cluster no longer op
uuid = self.identifyToMasterNode()
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.assertRaises(VerificationFailure, self.verification.connectionClosed,conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_02_timeoutExpired(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.verification.timeoutExpired(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
# test a storage, must raise as cluster no longer op
uuid = self.identifyToMasterNode()
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.assertRaises(VerificationFailure, self.verification.connectionClosed,conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_03_peerBroken(self):
uuid = self.identifyToMasterNode(node_type=MASTER_NODE_TYPE, port=self.master_port)
conn = self.getFakeConnection(uuid, self.master_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.verification.peerBroken(conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), BROKEN_STATE)
# test a storage, must raise as cluster no longer op
uuid = self.identifyToMasterNode()
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), RUNNING_STATE)
self.assertRaises(VerificationFailure, self.verification.connectionClosed,conn)
self.assertEqual(self.app.nm.getNodeByServer(conn.getAddress()).getState(), TEMPORARILY_DOWN_STATE)
def test_08_handleNotifyNodeInformation(self):
verification = self.verification
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=NOTIFY_NODE_INFORMATION)
# tell about a client node, do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(CLIENT_NODE_TYPE, '127.0.0.1', self.client_port, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
verification.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getClientNodeList()), 0)
# tell the master node about itself, if running must do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port-1, self.app.uuid, RUNNING_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
verification.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
# tell the master node about itself, if down must raise
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port-1, self.app.uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port-1))
self.assertEqual(node, None)
self.assertRaises(RuntimeError, verification.handleNotifyNodeInformation, conn, packet, node_list)
# tell about an unknown storage node, do nothing
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(STORAGE_NODE_TYPE, '127.0.0.1', self.master_port - 1, self.getNewUUID(), DOWN_STATE),]
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
verification.handleNotifyNodeInformation(conn, packet, node_list)
self.assertEqual(len(self.app.nm.getStorageNodeList()), 0)
# tell about a known node but different address
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.2', self.master_port, uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
verification.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
# tell about a known node
conn = self.getFakeConnection(uuid, self.master_address)
node_list = [(MASTER_NODE_TYPE, '127.0.0.1', self.master_port, uuid, DOWN_STATE),]
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), RUNNING_STATE)
verification.handleNotifyNodeInformation(conn, packet, node_list)
node = self.app.nm.getNodeByServer(("127.0.0.1", self.master_port))
self.assertEqual(node.getState(), DOWN_STATE)
def test_09_handleAnswerLastIDs(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_LAST_IDS)
loid = self.app.loid
ltid = self.app.ltid
lptid = self.app.pt.getID()
# send information which are later to what PMN knows, this must raise
conn = self.getFakeConnection(uuid, self.storage_address)
node_list = []
new_ptid = unpack('!Q', lptid)[0]
new_ptid = pack('!Q', new_ptid + 1)
oid = unpack('!Q', loid)[0]
new_oid = pack('!Q', oid + 1)
upper, lower = unpack('!LL', ltid)
new_tid = pack('!LL', upper, lower + 10)
self.failUnless(new_ptid > self.app.pt.getID())
self.failUnless(new_oid > self.app.loid)
self.failUnless(new_tid > self.app.ltid)
self.assertRaises(VerificationFailure, verification.handleAnswerLastIDs, conn, packet, new_oid, new_tid, new_ptid)
self.assertNotEquals(new_oid, self.app.loid)
self.assertNotEquals(new_tid, self.app.ltid)
self.assertNotEquals(new_ptid, self.app.pt.getID())
def test_10_handleAnswerPartitionTable(self):
verification = self.verification
uuid = self.identifyToMasterNode(MASTER_NODE_TYPE, port=self.master_port)
packet = Packet(msg_type=ANSWER_PARTITION_TABLE, )
conn = self.getFakeConnection(uuid, self.master_address)
verification.handleAnswerPartitionTable(conn, packet, None, [])
self.assertEqual(len(conn.mockGetAllCalls()), 0)
def test_11_handleAnswerUnfinishedTransactions(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_UNFINISHED_TRANSACTIONS)
# do nothing
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 0)
self.app.asking_uuid_dict[uuid] = True
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
self.assertEquals(len(self.app.unfinished_tid_set), 0)
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
verification.handleAnswerUnfinishedTransactions(conn, packet, [new_tid])
self.assertEquals(len(self.app.unfinished_tid_set), 0)
# update dict
conn = self.getFakeConnection(uuid, self.storage_address)
self.app.asking_uuid_dict[uuid] = False
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
self.assertEquals(len(self.app.unfinished_tid_set), 0)
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
verification.handleAnswerUnfinishedTransactions(conn, packet, [new_tid,])
self.assertTrue(self.app.asking_uuid_dict[uuid])
self.assertEquals(len(self.app.unfinished_tid_set), 1)
self.assertTrue(new_tid in self.app.unfinished_tid_set)
def test_12_handleAnswerTransactionInformation(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_TRANSACTION_INFORMATION)
# do nothing, as unfinished_oid_set is None
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 0)
self.app.asking_uuid_dict[uuid] = False
self.app.unfinished_oid_set = None
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
oid = unpack('!Q', self.app.loid)[0]
new_oid = pack('!Q', oid + 1)
verification.handleAnswerTransactionInformation(conn, packet, new_tid,
"user", "desc", "ext", [new_oid,])
self.assertEquals(self.app.unfinished_oid_set, None)
# do nothing as asking_uuid_dict is True
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = True
self.app.unfinished_oid_set = set()
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
self.assertEquals(len(self.app.unfinished_oid_set), 0)
verification.handleAnswerTransactionInformation(conn, packet, new_tid,
"user", "desc", "ext", [new_oid,])
self.assertEquals(len(self.app.unfinished_oid_set), 0)
# do work
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = False
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
self.assertEquals(len(self.app.unfinished_oid_set), 0)
verification.handleAnswerTransactionInformation(conn, packet, new_tid,
"user", "desc", "ext", [new_oid,])
self.assertEquals(len(self.app.unfinished_oid_set), 1)
self.assertTrue(new_oid in self.app.unfinished_oid_set)
# do not work as oid is diff
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = False
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
self.assertEquals(len(self.app.unfinished_oid_set), 1)
old_oid = new_oid
oid = unpack('!Q', old_oid)[0]
new_oid = pack('!Q', oid + 1)
self.assertNotEqual(new_oid, old_oid)
verification.handleAnswerTransactionInformation(conn, packet, new_tid,
"user", "desc", "ext", [new_oid,])
self.assertEquals(self.app.unfinished_oid_set, None)
def test_13_handleTidNotFound(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=TID_NOT_FOUND_CODE)
# do nothing as asking_uuid_dict is True
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 0)
self.app.asking_uuid_dict[uuid] = True
self.app.unfinished_oid_set = []
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
verification.handleTidNotFound(conn, packet, "msg")
self.assertNotEqual(self.app.unfinished_oid_set, None)
# do work as asking_uuid_dict is False
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = False
self.app.unfinished_oid_set = []
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
verification.handleTidNotFound(conn, packet, "msg")
self.assertEqual(self.app.unfinished_oid_set, None)
def test_14_handleAnswerObjectPresent(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=ANSWER_OBJECT_PRESENT)
# do nothing as asking_uuid_dict is True
upper, lower = unpack('!LL', self.app.ltid)
new_tid = pack('!LL', upper, lower + 10)
oid = unpack('!Q', self.app.loid)[0]
new_oid = pack('!Q', oid + 1)
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 0)
self.app.asking_uuid_dict[uuid] = True
self.assertTrue(self.app.asking_uuid_dict.has_key(uuid))
verification.handleAnswerObjectPresent(conn, packet, new_oid, new_tid)
# do work
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = False
self.assertFalse(self.app.asking_uuid_dict[uuid])
verification.handleAnswerObjectPresent(conn, packet, new_oid, new_tid)
self.assertTrue(self.app.asking_uuid_dict[uuid])
def test_15_handleOidNotFound(self):
verification = self.verification
uuid = self.identifyToMasterNode()
packet = Packet(msg_type=OID_NOT_FOUND_CODE)
# do nothinf as asking_uuid_dict is True
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 0)
self.app.asking_uuid_dict[uuid] = True
self.app.object_present = True
self.assertTrue(self.app.object_present)
verification.handleOidNotFound(conn, packet, "msg")
self.assertTrue(self.app.object_present)
# do work as asking_uuid_dict is False
conn = self.getFakeConnection(uuid, self.storage_address)
self.assertEquals(len(self.app.asking_uuid_dict), 1)
self.app.asking_uuid_dict[uuid] = False
self.assertFalse(self.app.asking_uuid_dict[uuid ])
self.assertTrue(self.app.object_present)
verification.handleOidNotFound(conn, packet, "msg")
self.assertFalse(self.app.object_present)
self.assertTrue(self.app.asking_uuid_dict[uuid ])
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/neoctl/ 0000775 0000000 0000000 00000000000 11227104631 0021506 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/neoctl/__init__.py 0000664 0000000 0000000 00000000000 11227104631 0023605 0 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/neoctl/app.py 0000664 0000000 0000000 00000016766 11227104631 0022660 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.protocol import node_types, node_states
from neo.protocol import INVALID_UUID, INVALID_PTID
from neo.event import EventManager
from neo.connection import ClientConnection
from neo.exception import OperationFailure
from neo.neoctl.handler import CommandEventHandler
from neo.connector import getConnectorHandler
from neo.util import bin
from neo import protocol
class ActionError(Exception): pass
def addAction(options):
"""
Change node state from "pending" to "running".
Parameters:
node uuid
UUID of node to add, or "all".
"""
if len(options) == 1 and options[0] == 'all':
uuid_list = []
else:
uuid_list = [bin(opt) for opt in options]
return protocol.addPendingNodes(uuid_list)
def setClusterAction(options):
"""
Set cluster of given name to given state.
Parameters:
cluster state
State to put the cluster in.
"""
# XXX: why do we ask for a cluster name ?
# We connect to only one cluster that we get from configuration file,
# anyway.
name, state = options
state = protocol.cluster_states.getFromStr(state)
if state is None:
raise ActionError('unknown cluster state')
return protocol.setClusterState(name, state)
def setNodeAction(options):
"""
Put given node into given state.
Parameters:
node uuid
UUID of target node
node state
Node state to set.
change partition table (optional)
If given with a 1 value, allow partition table to be changed.
"""
uuid = bin(options.pop(0))
state = node_states.getFromStr(options.pop(0))
if state is None:
raise ActionError('unknown state type')
if len(options):
modify = int(options.pop(0))
else:
modify = 0
return protocol.setNodeState(uuid, state, modify)
def printClusterAction(options):
"""
Print cluster state.
"""
return protocol.askClusterState()
def printNodeAction(options):
"""
Print nodes of a given type.
Parameters:
node type
Print known nodes of given type.
"""
node_type = node_types.getFromStr(options.pop(0))
if node_type is None:
raise ActionError('unknown node type')
return protocol.askNodeList(node_type)
def printPTAction(options):
"""
Print the partition table.
Parameters:
range
all Prints the entire partition table.
1 10 Prints rows 1 to 10 of partition table.
10 0 Prints rows from 10 to the end of partition table.
node uuid (optional)
If given, prints only the rows in which given node is used.
"""
offset = options.pop(0)
if offset == "all":
min_offset = 0
max_offset = 0
else:
min_offset = int(offset)
max_offset = int(options.pop(0))
if len(options):
uuid = bin(options.pop(0))
else:
uuid = INVALID_UUID
return protocol.askPartitionList(min_offset, max_offset, uuid)
action_dict = {
'print': {
'pt': printPTAction,
'node': printNodeAction,
'cluster': printClusterAction,
},
'set': {
'node': setNodeAction,
'cluster': setClusterAction,
},
'add': addAction,
}
class Application(object):
"""The storage node application."""
conn = None
def __init__(self, ip, port, handler):
self.connector_handler = getConnectorHandler(handler)
self.server = (ip, port)
self.em = EventManager()
self.ptid = INVALID_PTID
def getConnection(self):
if self.conn is None:
handler = CommandEventHandler(self)
# connect to admin node
self.trying_admin_node = False
conn = None
while 1:
self.em.poll(1)
if conn is None:
self.trying_admin_node = True
logging.debug('connecting to address %s:%d', *(self.server))
conn = ClientConnection(self.em, handler, \
addr = self.server,
connector_handler = self.connector_handler)
if self.trying_admin_node is False:
break
self.conn = conn
return self.conn
def doAction(self, packet):
conn = self.getConnection()
conn.ask(packet)
self.result = ""
while 1:
self.em.poll(1)
if len(self.result):
break
def __del__(self):
if self.conn is not None:
self.conn.close()
def execute(self, args):
"""Execute the command given."""
# print node type : print list of node of the given type (STORAGE_NODE_TYPE, MASTER_NODE_TYPE...)
# set node uuid state [1|0] : set the node for the given uuid to the state (RUNNING_STATE, DOWN_STATE...)
# and modify the partition if asked
# set cluster name [shutdown|operational] : either shutdown the cluster or mark it as operational
current_action = action_dict
level = 0
while current_action is not None and \
level < len(args) and \
isinstance(current_action, dict):
current_action = current_action.get(args[level])
level += 1
if callable(current_action):
try:
p = current_action(args[level:])
except ActionError, message:
self.result = message
else:
self.doAction(p)
else:
self.result = usage('unknown command')
return self.result
def _usage(action_dict, level=0):
result = []
append = result.append
sub_level = level + 1
for name, action in action_dict.iteritems():
append('%s%s' % (' ' * level, name))
if isinstance(action, dict):
append(_usage(action, level=sub_level))
else:
docstring_line_list = getattr(action, '__doc__',
'(no docstring)').split('\n')
# Strip empty lines at begining & end of line list
for end in (0, -1):
while len(docstring_line_list) \
and docstring_line_list[end] == '':
docstring_line_list.pop(end)
# Get the indentation of first line, to preserve other lines
# relative indentation.
first_line = docstring_line_list[0]
base_indentation = len(first_line) - len(first_line.lstrip())
result.extend([(' ' * sub_level) + x[base_indentation:] \
for x in docstring_line_list])
return '\n'.join(result)
def usage(message):
output_list = [message, 'Available commands:', _usage(action_dict)]
return '\n'.join(output_list)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/neoctl/handler.py 0000664 0000000 0000000 00000006330 11227104631 0023477 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from neo.handler import EventHandler
from neo.protocol import UnexpectedPacketError
from neo.exception import OperationFailure
from neo.util import dump
class CommandEventHandler(EventHandler):
""" Base handler for command """
def connectionAccepted(self, conn, s, addr):
"""Called when a connection is accepted."""
raise UnexpectedPacketError
def connectionCompleted(self, conn):
# connected to admin node
self.app.trying_admin_node = False
EventHandler.connectionCompleted(self, conn)
def connectionFailed(self, conn):
EventHandler.connectionFailed(self, conn)
raise OperationFailure, "impossible to connect to admin node %s:%d" % conn.getAddress()
def timeoutExpired(self, conn):
EventHandler.timeoutExpired(self, conn)
raise OperationFailure, "connection to admin node %s:%d timeout" % conn.getAddress()
def connectionClosed(self, conn):
if self.app.trying_admin_node:
raise OperationFailure, "cannot connect to admin node %s:%d" % conn.getAddress()
EventHandler.connectionClosed(self, conn)
def peerBroken(self, conn):
EventHandler.peerBroken(self, conn)
raise OperationFailure, "connect to admin node %s:%d broken" % conn.getAddress()
def handleAnswerPartitionList(self, conn, packet, ptid, row_list):
data = ""
if len(row_list) == 0:
data = "No partition"
else:
for offset, cell_list in row_list:
data += "\n%s | " %offset
for uuid, state in cell_list:
data += "%s - %s |" %(dump(uuid), state)
self.app.result = data
def handleAnswerNodeList(self, conn, packet, node_list):
data = ""
if len(node_list) == 0:
data = "No Node"
else:
for node_type, ip, port, uuid, state in node_list:
data += "\n%s - %s - %s:%s - %s" %(node_type, dump(uuid), ip, port, state)
self.app.result = data
def handleAnswerNodeState(self, conn, packet, uuid, state):
self.app.result = "Node %s set to state %s" %(dump(uuid), state)
def handleAnswerClusterState(self, conn, packet, state):
self.app.result = "Cluster state : %s" % state
def handleAnswerNewNodes(self, conn, packet, uuid_list):
uuids = ', '.join([dump(uuid) for uuid in uuid_list])
self.app.result = 'New storage nodes : %s' % uuids
def handleNoError(self, conn, packet, msg):
self.app.result = msg
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/node.py 0000664 0000000 0000000 00000014010 11227104631 0021515 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from time import time
import logging
from neo import protocol
from neo.protocol import RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
BROKEN_STATE, PENDING_STATE, HIDDEN_STATE, MASTER_NODE_TYPE, \
STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, ADMIN_NODE_TYPE, \
VALID_NODE_STATE_LIST
from neo.util import dump
class Node(object):
"""This class represents a node."""
def __init__(self, server = None, uuid = None):
self.state = RUNNING_STATE
self.server = server
self.uuid = uuid
self.manager = None
self.last_state_change = time()
def setManager(self, manager):
self.manager = manager
def getLastStateChange(self):
return self.last_state_change
def getState(self):
return self.state
def setState(self, new_state):
assert new_state in VALID_NODE_STATE_LIST
if self.state != new_state:
self.state = new_state
self.last_state_change = time()
def setServer(self, server):
if self.server != server:
if self.server is not None and self.manager is not None:
self.manager.unregisterServer(self)
self.server = server
if server is not None and self.manager is not None:
self.manager.registerServer(self)
def getServer(self):
return self.server
def setUUID(self, uuid):
if self.uuid != uuid:
if self.uuid is not None and self.manager is not None:
self.manager.unregisterUUID(self)
self.uuid = uuid
if uuid is not None and self.manager is not None:
self.manager.registerUUID(self)
def getUUID(self):
return self.uuid
def getNodeType(self):
raise NotImplementedError
def __str__(self):
server = self.getServer()
if server is None:
address, port = None, None
else:
address, port = server
uuid = self.getUUID()
return '%s (%s:%s)' % (dump(uuid), address, port)
class MasterNode(Node):
"""This class represents a master node."""
def getNodeType(self):
return MASTER_NODE_TYPE
class StorageNode(Node):
"""This class represents a storage node."""
def getNodeType(self):
return STORAGE_NODE_TYPE
class ClientNode(Node):
"""This class represents a client node."""
def getNodeType(self):
return CLIENT_NODE_TYPE
class AdminNode(Node):
"""This class represents an admin node."""
def getNodeType(self):
return ADMIN_NODE_TYPE
class NodeManager(object):
"""This class manages node status."""
def __init__(self):
self.node_list = []
self.server_dict = {}
self.uuid_dict = {}
def add(self, node):
node.setManager(self)
self.node_list.append(node)
if node.getServer() is not None:
self.registerServer(node)
if node.getUUID() is not None:
self.registerUUID(node)
def remove(self, node):
if node is None:
return
self.node_list.remove(node)
self.unregisterServer(node)
self.unregisterUUID(node)
def registerServer(self, node):
self.server_dict[node.getServer()] = node
def unregisterServer(self, node):
try:
del self.server_dict[node.getServer()]
except KeyError:
pass
def registerUUID(self, node):
self.uuid_dict[node.getUUID()] = node
def unregisterUUID(self, node):
try:
del self.uuid_dict[node.getUUID()]
except KeyError:
pass
def getNodeList(self, filter = None):
if filter is None:
return list(self.node_list)
return [n for n in self.node_list if filter(n)]
def getMasterNodeList(self):
return self.getNodeList(filter = lambda node: node.getNodeType() == MASTER_NODE_TYPE)
def getStorageNodeList(self):
return self.getNodeList(filter = lambda node: node.getNodeType() == STORAGE_NODE_TYPE)
def getClientNodeList(self):
return self.getNodeList(filter = lambda node: node.getNodeType() == CLIENT_NODE_TYPE)
def getNodeByServer(self, server):
return self.server_dict.get(server)
def getNodeByUUID(self, uuid):
if uuid in (None, protocol.INVALID_UUID):
return None
return self.uuid_dict.get(uuid)
def clear(self, filter=None):
for node in self.getNodeList():
if filter is not None and filter(node):
self.remove(node)
def log(self):
node_state_dict = { RUNNING_STATE: 'R',
TEMPORARILY_DOWN_STATE: 'T',
DOWN_STATE: 'D',
BROKEN_STATE: 'B',
HIDDEN_STATE: 'H',
PENDING_STATE: 'P'}
node_type_dict = {
MASTER_NODE_TYPE: 'M',
STORAGE_NODE_TYPE: 'S',
CLIENT_NODE_TYPE: 'C',
ADMIN_NODE_TYPE: 'A',
}
for uuid, node in sorted(self.uuid_dict.items()):
args = (
dump(uuid),
node_type_dict[node.getNodeType()],
node_state_dict[node.getState()]
)
logging.debug('nm: %s : %s/%s' % args)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/protocol.py 0000664 0000000 0000000 00000121407 11227104631 0022442 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import struct
from struct import pack, unpack
from socket import inet_ntoa, inet_aton
class EnumItem(int):
"""
Enumerated value type.
Not to be used outside of Enum class.
"""
def __new__(cls, enum, name, value):
instance = super(EnumItem, cls).__new__(cls, value)
instance.enum = enum
instance.name = name
return instance
def __eq__(self, other):
"""
Raise if compared type doesn't match.
"""
if not isinstance(other, EnumItem):
raise TypeError, 'Comparing an enum with an int.'
if other.enum is not self.enum:
raise TypeError, 'Comparing enums of incompatible types: %s ' \
'and %s' % (self, other)
return int(other) == int(self)
def __ne__(self, other):
return not(self == other)
def __str__(self):
return self.name
def __repr__(self):
return '' % (self.name, int(self), self.enum)
class Enum(object):
"""
C-style enumerated type support with extended typechecking.
Instantiate with a dict whose keys are variable names and values are
the value of that variable.
Variables are added to module's globals and can be used directly.
The purpose of this class is purely to prevent developper from
mistakenly comparing an enumerated value with a value from another enum,
or even not from any enum at all.
"""
def __init__(self, value_dict):
global_dict = globals()
self.enum_dict = enum_dict = {}
self.str_enum_dict = str_enum_dict = {}
for key, value in value_dict.iteritems():
# Only integer types are supported. This should be enough, and
# extending support to other types would only make moving to other
# languages harder.
if not isinstance(value, int):
raise TypeError, 'Enum class only support integer values.'
item = EnumItem(self, key, value)
global_dict[key] = enum_dict[value] = item
str_enum_dict[key] = item
def get(self, value, default=None):
return self.enum_dict.get(value, default)
def getFromStr(self, value, default=None):
return self.str_enum_dict.get(value, default)
def __getitem__(self, value):
return self.enum_dict[value]
# The protocol version (major, minor).
PROTOCOL_VERSION = (4, 0)
# Size restrictions.
MIN_PACKET_SIZE = 10
MAX_PACKET_SIZE = 0x100000
PACKET_HEADER_SIZE = 10
# Message types.
packet_types = Enum({
# Error is a special type of message, because this can be sent against any other message,
# even if such a message does not expect a reply usually. Any -> Any.
'ERROR': 0x8000,
# Check if a peer is still alive. Any -> Any.
'PING': 0x0001,
# Notify being alive. Any -> Any.
'PONG': 0x8001,
# Request a node identification. This must be the first packet for any connection.
# Any -> Any.
'REQUEST_NODE_IDENTIFICATION': 0x0002,
# Accept a node identification. This should be a reply to Request Node Identification.
# Any -> Any.
'ACCEPT_NODE_IDENTIFICATION': 0x8002,
# Ask a current primary master node. This must be the second message when connecting
# to a master node. Any -> M.
'ASK_PRIMARY_MASTER': 0x0003,
# Reply to Ask Primary Master. This message includes a list of known master nodes,
# to make sure that a peer has the same information. M -> Any.
'ANSWER_PRIMARY_MASTER': 0x8003,
# Announce a primary master node election. PM -> SM.
'ANNOUNCE_PRIMARY_MASTER': 0x0004,
# Force a re-election of a primary master node. M -> M.
'REELECT_PRIMARY_MASTER': 0x0005,
# Notify information about one or more nodes. Any -> PM, PM -> Any.
'NOTIFY_NODE_INFORMATION': 0x0006,
# Ask the last OID, the last TID and the last Partition Table ID that
# a storage node stores. Used to recover information. PM -> S, S -> PM.
'ASK_LAST_IDS': 0x0007,
# Reply to Ask Last IDs. S -> PM, PM -> S.
'ANSWER_LAST_IDS': 0x8007,
# Ask rows in a partition table that a storage node stores. Used to recover
# information. PM -> S.
'ASK_PARTITION_TABLE': 0x0008,
# Answer rows in a partition table. S -> PM.
'ANSWER_PARTITION_TABLE': 0x8008,
# Send rows in a partition table to update other nodes. PM -> S, C.
'SEND_PARTITION_TABLE': 0x0009,
# Notify a subset of a partition table. This is used to notify changes. PM -> S, C.
'NOTIFY_PARTITION_CHANGES': 0x000a,
# Tell a storage nodes to start an operation. Until a storage node receives this
# message, it must not serve client nodes. PM -> S.
'START_OPERATION': 0x000b,
# Tell a storage node to stop an operation. Once a storage node receives this message,
# it must not serve client nodes. PM -> S.
'STOP_OPERATION': 0x000c,
# Ask unfinished transactions' IDs. PM -> S.
'ASK_UNFINISHED_TRANSACTIONS': 0x000d,
# Answer unfinished transactions' IDs. S -> PM.
'ANSWER_UNFINISHED_TRANSACTIONS': 0x800d,
# Ask if an object is present. If not present, OID_NOT_FOUND should be returned. PM -> S.
'ASK_OBJECT_PRESENT': 0x000f,
# Answer that an object is present. PM -> S.
'ANSWER_OBJECT_PRESENT': 0x800f,
# Delete a transaction. PM -> S.
'DELETE_TRANSACTION': 0x0010,
# Commit a transaction. PM -> S.
'COMMIT_TRANSACTION': 0x0011,
# Ask a new transaction ID. C -> PM.
'ASK_NEW_TID': 0x0012,
# Answer a new transaction ID. PM -> C.
'ANSWER_NEW_TID': 0x8012,
# Finish a transaction. C -> PM.
'FINISH_TRANSACTION': 0x0013,
# Notify a transaction finished. PM -> C.
'NOTIFY_TRANSACTION_FINISHED': 0x8013,
# Lock information on a transaction. PM -> S.
'LOCK_INFORMATION': 0x0014,
# Notify information on a transaction locked. S -> PM.
'NOTIFY_INFORMATION_LOCKED': 0x8014,
# Invalidate objects. PM -> C.
'INVALIDATE_OBJECTS': 0x0015,
# Unlock information on a transaction. PM -> S.
'UNLOCK_INFORMATION': 0x0016,
# Ask new object IDs. C -> PM.
'ASK_NEW_OIDS': 0x0017,
# Answer new object IDs. PM -> C.
'ANSWER_NEW_OIDS': 0x8017,
# Ask to store an object. Send an OID, an original serial, a current
# transaction ID, and data. C -> S.
'ASK_STORE_OBJECT': 0x0018,
# Answer if an object has been stored. If an object is in conflict,
# a serial of the conflicting transaction is returned. In this case,
# if this serial is newer than the current transaction ID, a client
# node must not try to resolve the conflict. S -> C.
'ANSWER_STORE_OBJECT': 0x8018,
# Abort a transaction. C -> S, PM.
'ABORT_TRANSACTION': 0x0019,
# Ask to store a transaction. C -> S.
'ASK_STORE_TRANSACTION': 0x001a,
# Answer if transaction has been stored. S -> C.
'ANSWER_STORE_TRANSACTION': 0x801a,
# Ask a stored object by its OID and a serial or a TID if given. If a serial
# is specified, the specified revision of an object will be returned. If
# a TID is specified, an object right before the TID will be returned. S,C -> S.
'ASK_OBJECT': 0x001b,
# Answer the requested object. S -> C.
'ANSWER_OBJECT': 0x801b,
# Ask for TIDs between a range of offsets. The order of TIDs is descending,
# and the range is [first, last). C, S -> S.
'ASK_TIDS': 0x001d,
# Answer the requested TIDs. S -> C, S.
'ANSWER_TIDS': 0x801d,
# Ask information about a transaction. Any -> S.
'ASK_TRANSACTION_INFORMATION': 0x001e,
# Answer information (user, description) about a transaction. S -> Any.
'ANSWER_TRANSACTION_INFORMATION': 0x801e,
# Ask history information for a given object. The order of serials is
# descending, and the range is [first, last]. C, S -> S.
'ASK_OBJECT_HISTORY': 0x001f,
# Answer history information (serial, size) for an object. S -> C, S.
'ANSWER_OBJECT_HISTORY': 0x801f,
# Ask for OIDs between a range of offsets. The order of OIDs is descending,
# and the range is [first, last). S -> S.
'ASK_OIDS': 0x0020,
# Answer the requested OIDs. S -> S.
'ANSWER_OIDS': 0x8020,
# All the following messages are for neoctl to admin node
# Ask information about partition
'ASK_PARTITION_LIST': 0x0021,
# Answer information about partition
'ANSWER_PARTITION_LIST': 0x8021,
# Ask information about nodes
'ASK_NODE_LIST': 0x0022,
# Answer information about nodes
'ANSWER_NODE_LIST': 0x8022,
# Set the node state
'SET_NODE_STATE': 0x0023,
# Answer state of the node
'ANSWER_NODE_STATE': 0x8023,
# Ask the primary to include some pending node in the partition table
'ADD_PENDING_NODES': 0x0024,
# Anwer what are the nodes added in the partition table
'ANSWER_NEW_NODES': 0x8024,
# Ask node information
'ASK_NODE_INFORMATION': 0x0025,
# Answer node information
'ANSWER_NODE_INFORMATION': 0x8025,
# Set the cluster state
'SET_CLUSTER_STATE': 0x0026,
# Notify information about the cluster
'NOTIFY_CLUSTER_INFORMATION': 0x8027,
# Ask state of the cluster
'ASK_CLUSTER_STATE': 0x0028,
# Answer state of the cluster
'ANSWER_CLUSTER_STATE': 0x8028,
})
# Error codes.
NO_ERROR_CODE = 0
NOT_READY_CODE = 1
OID_NOT_FOUND_CODE = 2
SERIAL_NOT_FOUND_CODE = 3
TID_NOT_FOUND_CODE = 4
PROTOCOL_ERROR_CODE = 5
TIMEOUT_ERROR_CODE = 6
BROKEN_NODE_DISALLOWED_CODE = 7
INTERNAL_ERROR_CODE = 8
# Cluster states
cluster_states = Enum({
'BOOTING': 1,
'RECOVERING': 2,
'VERIFYING': 3,
'RUNNING': 4,
'STOPPING': 5,
})
VALID_CLUSTER_STATE_LIST = (BOOTING, RUNNING, STOPPING)
# Node types.
node_types = Enum({
'MASTER_NODE_TYPE' : 1,
'STORAGE_NODE_TYPE' : 2,
'CLIENT_NODE_TYPE' : 3,
'ADMIN_NODE_TYPE' : 4,
})
VALID_NODE_TYPE_LIST = (MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, ADMIN_NODE_TYPE)
# Node states.
node_states = Enum({
'RUNNING_STATE': 0,
'TEMPORARILY_DOWN_STATE': 1,
'DOWN_STATE': 2,
'BROKEN_STATE': 3,
'HIDDEN_STATE' : 4,
'PENDING_STATE': 5,
})
VALID_NODE_STATE_LIST = (RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE,
BROKEN_STATE, HIDDEN_STATE, PENDING_STATE)
# Partition cell states.
partition_cell_states = Enum({
'UP_TO_DATE_STATE': 0,
'OUT_OF_DATE_STATE': 1,
'FEEDING_STATE': 2,
'DISCARDED_STATE': 3,
})
VALID_CELL_STATE_LIST = (UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE,
DISCARDED_STATE)
# Other constants.
INVALID_UUID = '\0' * 16
INVALID_TID = '\0' * 8
INVALID_SERIAL = '\0' * 8
INVALID_OID = '\0' * 8
INVALID_PTID = '\0' * 8
INVALID_PARTITION = 0xffffffff
STORAGE_NS = 'S'
MASTER_NS = 'M'
CLIENT_NS = 'C'
ADMIN_NS = 'A'
UUID_NAMESPACES = {
STORAGE_NODE_TYPE: STORAGE_NS,
MASTER_NODE_TYPE: MASTER_NS,
CLIENT_NODE_TYPE: CLIENT_NS,
ADMIN_NODE_TYPE: ADMIN_NS,
}
class ProtocolError(Exception):
""" Base class for protocol errors, close the connection """
pass
class PacketMalformedError(ProtocolError):
""" Close the connection and set the node as broken"""
pass
class UnexpectedPacketError(ProtocolError):
""" Close the connection and set the node as broken"""
pass
class NotReadyError(ProtocolError):
""" Just close the connection """
pass
class BrokenNodeDisallowedError(ProtocolError):
""" Just close the connection """
pass
decode_table = {}
class Packet(object):
"""A packet."""
_id = None
_type = None
_len = None
def __init__(self, msg_type, body=''):
self._id = None
self._type = msg_type
self._body = body
def getId(self):
return self._id
def setId(self, id):
self._id = id
def getType(self):
return self._type
def __len__(self):
try:
return PACKET_HEADER_SIZE + len(self._body)
except TypeError:
return PACKET_HEADER_SIZE
def encode(self):
msg = pack('!LHL', self._id, self._type, PACKET_HEADER_SIZE + len(self._body)) + self._body
if len(msg) > MAX_PACKET_SIZE:
raise PacketMalformedError('message too big (%d)' % len(msg))
return msg
__str__ = encode
# Decoders.
def decode(self):
try:
method = decode_table[self._type]
except KeyError:
raise PacketMalformedError('unknown message type 0x%x' % self._type)
return method(self._body)
def isResponse(self):
return self._type & 0x8000 == 0x8000
# packet parser
def parse(msg):
if len(msg) < MIN_PACKET_SIZE:
return None
msg_id, msg_type, msg_len = unpack('!LHL', msg[:PACKET_HEADER_SIZE])
try:
msg_type = packet_types[msg_type]
except KeyError:
raise PacketMalformedError('Unknown packet type')
if msg_len > MAX_PACKET_SIZE:
raise PacketMalformedError('message too big (%d)' % msg_len)
if msg_len < MIN_PACKET_SIZE:
raise PacketMalformedError('message too small (%d)' % msg_len)
if len(msg) < msg_len:
# Not enough.
return None
packet = Packet(msg_type, msg[PACKET_HEADER_SIZE:msg_len])
packet.setId(msg_id)
return packet
def handle_errors(decoder):
""" Decorator to be used on encoding/decoding methods. Intercept struct
(pack/unpack) exceptions and wrap them in PacketMalformedError """
def wrapper(body):
try:
return decoder(body)
except struct.error, msg:
name = decoder.__name__
raise PacketMalformedError("%s fail (%s)" % (name, msg))
except PacketMalformedError, msg:
name = decoder.__name__
raise PacketMalformedError("%s fail (%s)" % (name, msg))
return wrapper
def _checkClusterState(state):
cluster_state = cluster_states.get(state)
if cluster_state is None:
raise PacketMalformedError('invalid node state %d' % state)
return cluster_state
def _checkNodeState(state):
node_state = node_states.get(state)
if node_state is None:
raise PacketMalformedError('invalid node state %d' % state)
return node_state
def _checkNodeType(type):
node_type = node_types.get(type)
if node_type is None:
raise PacketMalformedError('invalide node type %d' % type)
return node_type
def _readString(buffer, name, offset=0):
buffer = buffer[offset:]
(size, ) = unpack('!L', buffer[:4])
string = buffer[4:4+size]
if len(string) != size:
raise PacketMalformedError("can't read string <%s>" % name)
return (string, buffer[offset+size:])
# packet decoding
@handle_errors
def _decodeError(body):
(code, ) = unpack('!H', body[:2])
(message, _) = _readString(body, 'message', offset=2)
return (code, message)
decode_table[ERROR] = _decodeError
@handle_errors
def _decodePing(body):
pass
decode_table[PING] = _decodePing
@handle_errors
def _decodePong(body):
pass
decode_table[PONG] = _decodePong
@handle_errors
def _decodeRequestNodeIdentification(body):
r = unpack('!LLH16s4sH', body[:32])
major, minor, node_type, uuid, ip_address, port = r
ip_address = inet_ntoa(ip_address)
(name, _) = _readString(body, 'name', offset=32)
node_type = _checkNodeType(node_type)
if (major, minor) != PROTOCOL_VERSION:
raise PacketMalformedError('protocol version mismatch')
return node_type, uuid, ip_address, port, name
decode_table[REQUEST_NODE_IDENTIFICATION] = _decodeRequestNodeIdentification
@handle_errors
def _decodeAcceptNodeIdentification(body):
r = unpack('!H16s4sHLL16s', body)
node_type, uuid, ip_address, port, num_partitions, num_replicas, your_uuid = r
ip_address = inet_ntoa(ip_address)
node_type = _checkNodeType(node_type)
return (node_type, uuid, ip_address, port, num_partitions, num_replicas, your_uuid)
decode_table[ACCEPT_NODE_IDENTIFICATION] = _decodeAcceptNodeIdentification
@handle_errors
def _decodeAskPrimaryMaster(body):
pass
decode_table[ASK_PRIMARY_MASTER] = _decodeAskPrimaryMaster
@handle_errors
def _decodeAnswerPrimaryMaster(body):
(primary_uuid, n) = unpack('!16sL', body[:20])
known_master_list = []
for i in xrange(n):
ip_address, port, uuid = unpack('!4sH16s', body[20+i*22:42+i*22])
ip_address = inet_ntoa(ip_address)
known_master_list.append((ip_address, port, uuid))
return (primary_uuid, known_master_list)
decode_table[ANSWER_PRIMARY_MASTER] = _decodeAnswerPrimaryMaster
@handle_errors
def _decodeAnnouncePrimaryMaster(body):
pass
decode_table[ANNOUNCE_PRIMARY_MASTER] = _decodeAnnouncePrimaryMaster
@handle_errors
def _decodeReelectPrimaryMaster(body):
pass
decode_table[REELECT_PRIMARY_MASTER] = _decodeReelectPrimaryMaster
@handle_errors
def _decodeNotifyNodeInformation(body):
(n,) = unpack('!L', body[:4])
node_list = []
for i in xrange(n):
r = unpack('!H4sH16sH', body[4+i*26:30+i*26])
node_type, ip_address, port, uuid, state = r
ip_address = inet_ntoa(ip_address)
node_type = _checkNodeType(node_type)
state = _checkNodeState(state)
node_list.append((node_type, ip_address, port, uuid, state))
return (node_list,)
decode_table[NOTIFY_NODE_INFORMATION] = _decodeNotifyNodeInformation
@handle_errors
def _decodeAskLastIDs(body):
pass
decode_table[ASK_LAST_IDS] = _decodeAskLastIDs
@handle_errors
def _decodeAnswerLastIDs(body):
return unpack('!8s8s8s', body) # (loid, ltid, lptid)
decode_table[ANSWER_LAST_IDS] = _decodeAnswerLastIDs
@handle_errors
def _decodeAskPartitionTable(body):
(n,) = unpack('!L', body[:4])
offset_list = []
for i in xrange(n):
offset = unpack('!L', body[4+i*4:8+i*4])[0]
offset_list.append(offset)
return (offset_list,)
decode_table[ASK_PARTITION_TABLE] = _decodeAskPartitionTable
@handle_errors
def _decodeAnswerPartitionTable(body):
index = 12
(ptid, n) = unpack('!8sL', body[:index])
row_list = []
cell_list = []
for i in xrange(n):
offset, m = unpack('!LL', body[index:index+8])
index += 8
for j in xrange(m):
uuid, state = unpack('!16sH', body[index:index+18])
index += 18
state = partition_cell_states.get(state)
cell_list.append((uuid, state))
row_list.append((offset, tuple(cell_list)))
del cell_list[:]
return (ptid, row_list)
decode_table[ANSWER_PARTITION_TABLE] = _decodeAnswerPartitionTable
@handle_errors
def _decodeSendPartitionTable(body):
index = 12
(ptid, n,) = unpack('!8sL', body[:index])
row_list = []
cell_list = []
for i in xrange(n):
offset, m = unpack('!LL', body[index:index+8])
index += 8
for j in xrange(m):
uuid, state = unpack('!16sH', body[index:index+18])
index += 18
state = partition_cell_states.get(state)
cell_list.append((uuid, state))
row_list.append((offset, tuple(cell_list)))
del cell_list[:]
return (ptid, row_list)
decode_table[SEND_PARTITION_TABLE] = _decodeSendPartitionTable
@handle_errors
def _decodeNotifyPartitionChanges(body):
(ptid, n) = unpack('!8sL', body[:12])
cell_list = []
for i in xrange(n):
(offset, uuid, state) = unpack('!L16sH', body[12+i*22:34+i*22])
state = partition_cell_states.get(state)
cell_list.append((offset, uuid, state))
return ptid, cell_list
decode_table[NOTIFY_PARTITION_CHANGES] = _decodeNotifyPartitionChanges
@handle_errors
def _decodeStartOperation(body):
pass
decode_table[START_OPERATION] = _decodeStartOperation
@handle_errors
def _decodeStopOperation(body):
pass
decode_table[STOP_OPERATION] = _decodeStopOperation
@handle_errors
def _decodeAskUnfinishedTransactions(body):
pass
decode_table[ASK_UNFINISHED_TRANSACTIONS] = _decodeAskUnfinishedTransactions
@handle_errors
def _decodeAnswerUnfinishedTransactions(body):
(n,) = unpack('!L', body[:4])
tid_list = []
for i in xrange(n):
tid = unpack('8s', body[4+i*8:12+i*8])[0]
tid_list.append(tid)
return (tid_list,)
decode_table[ANSWER_UNFINISHED_TRANSACTIONS] = _decodeAnswerUnfinishedTransactions
@handle_errors
def _decodeAskObjectPresent(body):
return unpack('8s8s', body) # oid, tid
decode_table[ASK_OBJECT_PRESENT] = _decodeAskObjectPresent
@handle_errors
def _decodeAnswerObjectPresent(body):
return unpack('8s8s', body) # oid, tid
decode_table[ANSWER_OBJECT_PRESENT] = _decodeAnswerObjectPresent
@handle_errors
def _decodeDeleteTransaction(body):
return unpack('8s', body) # tid
decode_table[DELETE_TRANSACTION] = _decodeDeleteTransaction
@handle_errors
def _decodeCommitTransaction(body):
return unpack('8s', body) # tid
decode_table[COMMIT_TRANSACTION] = _decodeCommitTransaction
@handle_errors
def _decodeAskNewTID(body):
pass
decode_table[ASK_NEW_TID] = _decodeAskNewTID
@handle_errors
def _decodeAnswerNewTID(body):
return unpack('8s', body) # tid
decode_table[ANSWER_NEW_TID] = _decodeAnswerNewTID
@handle_errors
def _decodeAskNewOIDs(body):
return unpack('!H', body) # num oids
decode_table[ASK_NEW_OIDS] = _decodeAskNewOIDs
@handle_errors
def _decodeAnswerNewOIDs(body):
(n,) = unpack('!H', body[:2])
oid_list = []
for i in xrange(n):
oid = unpack('8s', body[2+i*8:10+i*8])[0]
oid_list.append(oid)
return (oid_list,)
decode_table[ANSWER_NEW_OIDS] = _decodeAnswerNewOIDs
@handle_errors
def _decodeFinishTransaction(body):
(tid, n) = unpack('!8sL', body[:12])
oid_list = []
for i in xrange(n):
oid = unpack('8s', body[12+i*8:20+i*8])[0]
oid_list.append(oid)
return (oid_list, tid)
decode_table[FINISH_TRANSACTION] = _decodeFinishTransaction
@handle_errors
def _decodeNotifyTransactionFinished(body):
return unpack('8s', body) # tid
decode_table[NOTIFY_TRANSACTION_FINISHED] = _decodeNotifyTransactionFinished
@handle_errors
def _decodeLockInformation(body):
return unpack('8s', body) # tid
decode_table[LOCK_INFORMATION] = _decodeLockInformation
@handle_errors
def _decodeNotifyInformationLocked(body):
return unpack('8s', body) # tid
decode_table[NOTIFY_INFORMATION_LOCKED] = _decodeNotifyInformationLocked
@handle_errors
def _decodeInvalidateObjects(body):
(tid, n) = unpack('!8sL', body[:12])
oid_list = []
for i in xrange(12, 12 + n * 8, 8):
oid = unpack('8s', body[i:i+8])[0]
oid_list.append(oid)
return (oid_list, tid)
decode_table[INVALIDATE_OBJECTS] = _decodeInvalidateObjects
@handle_errors
def _decodeUnlockInformation(body):
return unpack('8s', body) # tid
decode_table[UNLOCK_INFORMATION] = _decodeUnlockInformation
@handle_errors
def _decodeAbortTransaction(body):
return unpack('8s', body) # tid
decode_table[ABORT_TRANSACTION] = _decodeAbortTransaction
@handle_errors
def _decodeAskStoreObject(body):
r = unpack('!8s8s8sBL', body[:29])
oid, serial, tid, compression, checksum = r
(data, _) = _readString(body, 'data', offset=29)
return (oid, serial, compression, checksum, data, tid)
decode_table[ASK_STORE_OBJECT] = _decodeAskStoreObject
@handle_errors
def _decodeAnswerStoreObject(body):
return unpack('!B8s8s', body) # conflicting, oid, serial
decode_table[ANSWER_STORE_OBJECT] = _decodeAnswerStoreObject
@handle_errors
def _decodeAskStoreTransaction(body):
r = unpack('!8sLHHH', body[:18])
tid, oid_len, user_len, desc_len, ext_len = r
body = body[18:]
user = body[:user_len]
body = body[user_len:]
desc = body[:desc_len]
body = body[desc_len:]
ext = body[:ext_len]
body = body[ext_len:]
oid_list = []
for i in xrange(oid_len):
(oid, ) = unpack('8s', body[:8])
body = body[8:]
oid_list.append(oid)
return (tid, user, desc, ext, oid_list)
decode_table[ASK_STORE_TRANSACTION] = _decodeAskStoreTransaction
@handle_errors
def _decodeAnswerStoreTransaction(body):
return unpack('8s', body) # tid
decode_table[ANSWER_STORE_TRANSACTION] = _decodeAnswerStoreTransaction
@handle_errors
def _decodeAskObject(body):
return unpack('8s8s8s', body) # oid, serial, tid
decode_table[ASK_OBJECT] = _decodeAskObject
@handle_errors
def _decodeAnswerObject(body):
r = unpack('!8s8s8sBL', body[:29])
oid, serial_start, serial_end, compression, checksum = r
(data, _) = _readString(body, 'data', offset=29)
return (oid, serial_start, serial_end, compression, checksum, data)
decode_table[ANSWER_OBJECT] = _decodeAnswerObject
@handle_errors
def _decodeAskTIDs(body):
return unpack('!QQL', body) # first, last, partition
decode_table[ASK_TIDS] = _decodeAskTIDs
@handle_errors
def _decodeAnswerTIDs(body):
(n, ) = unpack('!L', body[:4])
tid_list = []
for i in xrange(n):
tid = unpack('8s', body[4+i*8:12+i*8])[0]
tid_list.append(tid)
return (tid_list,)
decode_table[ANSWER_TIDS] = _decodeAnswerTIDs
@handle_errors
def _decodeAskTransactionInformation(body):
return unpack('8s', body) # tid
decode_table[ASK_TRANSACTION_INFORMATION] = _decodeAskTransactionInformation
@handle_errors
def _decodeAnswerTransactionInformation(body):
r = unpack('!8sHHHL', body[:18])
tid, user_len, desc_len, ext_len, oid_len = r
body = body[18:]
user = body[:user_len]
body = body[user_len:]
desc = body[:desc_len]
body = body[desc_len:]
ext = body[:ext_len]
body = body[ext_len:]
oid_list = []
for i in xrange(oid_len):
(oid, ) = unpack('8s', body[:8])
body = body[8:]
oid_list.append(oid)
return (tid, user, desc, ext, oid_list)
decode_table[ANSWER_TRANSACTION_INFORMATION] = _decodeAnswerTransactionInformation
@handle_errors
def _decodeAskObjectHistory(body):
return unpack('!8sQQ', body) # oid, first, last
decode_table[ASK_OBJECT_HISTORY] = _decodeAskObjectHistory
@handle_errors
def _decodeAnswerObjectHistory(body):
(oid, length) = unpack('!8sL', body[:12])
history_list = []
for i in xrange(12, 12 + length * 12, 12):
serial, size = unpack('!8sL', body[i:i+12])
history_list.append((serial, size))
return (oid, history_list)
decode_table[ANSWER_OBJECT_HISTORY] = _decodeAnswerObjectHistory
@handle_errors
def _decodeAskOIDs(body):
return unpack('!QQL', body) # first, last, partition
decode_table[ASK_OIDS] = _decodeAskOIDs
@handle_errors
def _decodeAnswerOIDs(body):
(n,) = unpack('!L', body[:4])
oid_list = []
for i in xrange(n):
oid = unpack('8s', body[4+i*8:12+i*8])[0]
oid_list.append(oid)
return (oid_list,)
decode_table[ANSWER_OIDS] = _decodeAnswerOIDs
@handle_errors
def _decodeAskPartitionList(body):
return unpack('!LL16s', body) # min_offset, max_offset, uuid
decode_table[ASK_PARTITION_LIST] = _decodeAskPartitionList
@handle_errors
def _decodeAnswerPartitionList(body):
index = 12
(ptid, n) = unpack('!8sL', body[:index])
row_list = []
cell_list = []
for i in xrange(n):
offset, m = unpack('!LL', body[index:index+8])
index += 8
for j in xrange(m):
uuid, state = unpack('!16sH', body[index:index+18])
index += 18
state = partition_cell_states.get(state)
cell_list.append((uuid, state))
row_list.append((offset, tuple(cell_list)))
del cell_list[:]
return (ptid, row_list)
decode_table[ANSWER_PARTITION_LIST] = _decodeAnswerPartitionList
@handle_errors
def _decodeAskNodeList(body):
(node_type, ) = unpack('!H', body)
node_type = _checkNodeType(node_type)
return (node_type,)
decode_table[ASK_NODE_LIST] = _decodeAskNodeList
@handle_errors
def _decodeAnswerNodeList(body):
(n,) = unpack('!L', body[:4])
node_list = []
for i in xrange(n):
r = unpack('!H4sH16sH', body[4+i*26:30+i*26])
node_type, ip_address, port, uuid, state = r
ip_address = inet_ntoa(ip_address)
node_type = _checkNodeType(node_type)
state = _checkNodeState(state)
node_list.append((node_type, ip_address, port, uuid, state))
return (node_list,)
decode_table[ANSWER_NODE_LIST] = _decodeAnswerNodeList
@handle_errors
def _decodeSetNodeState(body):
(uuid, state, modify) = unpack('!16sHB', body)
state = _checkNodeState(state)
return (uuid, state, modify)
decode_table[SET_NODE_STATE] = _decodeSetNodeState
@handle_errors
def _decodeAnswerNodeState(body):
(uuid, state) = unpack('!16sH', body)
state = _checkNodeState(state)
return (uuid, state)
decode_table[ANSWER_NODE_STATE] = _decodeAnswerNodeState
@handle_errors
def _decodeAddPendingNodes(body):
(n, ) = unpack('!H', body[:2])
uuid_list = [unpack('!16s', body[2+i*16:18+i*16])[0] for i in xrange(n)]
return (uuid_list, )
decode_table[ADD_PENDING_NODES] = _decodeAddPendingNodes
@handle_errors
def _decodeAnswerNewNodes(body):
(n, ) = unpack('!H', body[:2])
uuid_list = [unpack('!16s', body[2+i*16:18+i*16])[0] for i in xrange(n)]
return (uuid_list, )
decode_table[ANSWER_NEW_NODES] = _decodeAnswerNewNodes
def _decodeAskNodeInformation(body):
pass # No payload
decode_table[ASK_NODE_INFORMATION] = _decodeAskNodeInformation
decode_table[ANSWER_NODE_INFORMATION] = _decodeNotifyNodeInformation
def _decodeAskClusterState(body):
pass
decode_table[ASK_CLUSTER_STATE] = _decodeAskClusterState
@handle_errors
def _decodeAnswerClusterState(body):
(state, ) = unpack('!H', body)
state = _checkClusterState(state)
return (state, )
decode_table[ANSWER_CLUSTER_STATE] = _decodeAnswerClusterState
@handle_errors
def _decodeSetClusterState(body):
(state, ) = unpack('!H', body[:2])
(name, _) = _readString(body, 'name', offset=2)
state = _checkClusterState(state)
return (name, state)
decode_table[SET_CLUSTER_STATE] = _decodeSetClusterState
@handle_errors
def _decodeNotifyClusterInformation(body):
(state, ) = unpack('!H', body)
state = _checkClusterState(state)
return (state, )
decode_table[NOTIFY_CLUSTER_INFORMATION] = _decodeNotifyClusterInformation
# Packet encoding
def _error(error_code, error_message):
body = pack('!HL', error_code, len(error_message)) + error_message
return Packet(ERROR, body)
def noError(message):
return _error(NO_ERROR_CODE, message)
def protocolError(error_message):
return _error(PROTOCOL_ERROR_CODE, 'protocol error: ' + error_message)
def internalError(error_message):
return _error(INTERNAL_ERROR_CODE, 'internal error: ' + error_message)
def notReady(error_message):
return _error(NOT_READY_CODE, 'not ready: ' + error_message)
def brokenNodeDisallowedError(error_message):
return _error(BROKEN_NODE_DISALLOWED_CODE,
'broken node disallowed error: ' + error_message)
def oidNotFound(error_message):
return _error(OID_NOT_FOUND_CODE, 'oid not found: ' + error_message)
def tidNotFound(error_message):
return _error(TID_NOT_FOUND_CODE, 'tid not found: ' + error_message)
def ping():
return Packet(PING)
def pong():
return Packet(PONG)
def requestNodeIdentification(node_type, uuid, ip_address, port, name):
body = pack('!LLH16s4sHL', PROTOCOL_VERSION[0], PROTOCOL_VERSION[1],
node_type, uuid, inet_aton(ip_address), port, len(name)) + name
return Packet(REQUEST_NODE_IDENTIFICATION, body)
def acceptNodeIdentification(node_type, uuid, ip_address,
port, num_partitions, num_replicas, your_uuid):
body = pack('!H16s4sHLL16s', node_type, uuid,
inet_aton(ip_address), port,
num_partitions, num_replicas, your_uuid)
return Packet(ACCEPT_NODE_IDENTIFICATION, body)
def askPrimaryMaster():
return Packet(ASK_PRIMARY_MASTER)
def answerPrimaryMaster(primary_uuid, known_master_list):
body = [primary_uuid, pack('!L', len(known_master_list))]
for master in known_master_list:
body.append(pack('!4sH16s', inet_aton(master[0]), master[1], master[2]))
body = ''.join(body)
return Packet(ANSWER_PRIMARY_MASTER, body)
def announcePrimaryMaster():
return Packet(ANNOUNCE_PRIMARY_MASTER)
def reelectPrimaryMaster():
return Packet(REELECT_PRIMARY_MASTER)
def notifyNodeInformation(node_list):
body = [pack('!L', len(node_list))]
for node_type, ip_address, port, uuid, state in node_list:
body.append(pack('!H4sH16sH', node_type, inet_aton(ip_address), port,
uuid, state))
body = ''.join(body)
return Packet(NOTIFY_NODE_INFORMATION, body)
def askLastIDs():
return Packet(ASK_LAST_IDS)
def answerLastIDs(loid, ltid, lptid):
return Packet(ANSWER_LAST_IDS, loid + ltid + lptid)
def askPartitionTable(offset_list):
body = [pack('!L', len(offset_list))]
for offset in offset_list:
body.append(pack('!L', offset))
body = ''.join(body)
return Packet(ASK_PARTITION_TABLE, body)
def answerPartitionTable(ptid, row_list):
body = [pack('!8sL', ptid, len(row_list))]
for offset, cell_list in row_list:
body.append(pack('!LL', offset, len(cell_list)))
for uuid, state in cell_list:
body.append(pack('!16sH', uuid, state))
body = ''.join(body)
return Packet(ANSWER_PARTITION_TABLE, body)
def sendPartitionTable(ptid, row_list):
body = [pack('!8sL', ptid, len(row_list))]
for offset, cell_list in row_list:
body.append(pack('!LL', offset, len(cell_list)))
for uuid, state in cell_list:
body.append(pack('!16sH', uuid, state))
body = ''.join(body)
return Packet(SEND_PARTITION_TABLE, body)
def notifyPartitionChanges(ptid, cell_list):
body = [pack('!8sL', ptid, len(cell_list))]
for offset, uuid, state in cell_list:
body.append(pack('!L16sH', offset, uuid, state))
body = ''.join(body)
return Packet(NOTIFY_PARTITION_CHANGES, body)
def startOperation():
return Packet(START_OPERATION)
def stopOperation():
return Packet(STOP_OPERATION)
def askUnfinishedTransactions():
return Packet(ASK_UNFINISHED_TRANSACTIONS)
def answerUnfinishedTransactions(tid_list):
body = [pack('!L', len(tid_list))]
body.extend(tid_list)
body = ''.join(body)
return Packet(ANSWER_UNFINISHED_TRANSACTIONS, body)
def askObjectPresent(oid, tid):
return Packet(ASK_OBJECT_PRESENT, oid + tid)
def answerObjectPresent(oid, tid):
return Packet(ANSWER_OBJECT_PRESENT, oid + tid)
def deleteTransaction(tid):
return Packet(DELETE_TRANSACTION, tid)
def commitTransaction(tid):
return Packet(COMMIT_TRANSACTION, tid)
def askNewTID():
return Packet(ASK_NEW_TID)
def answerNewTID(tid):
return Packet(ANSWER_NEW_TID, tid)
def askNewOIDs(num_oids):
return Packet(ASK_NEW_OIDS, pack('!H', num_oids))
def answerNewOIDs(oid_list):
body = [pack('!H', len(oid_list))]
body.extend(oid_list)
body = ''.join(body)
return Packet(ANSWER_NEW_OIDS, body)
def finishTransaction(oid_list, tid):
body = [pack('!8sL', tid, len(oid_list))]
body.extend(oid_list)
body = ''.join(body)
return Packet(FINISH_TRANSACTION, body)
def notifyTransactionFinished(tid):
return Packet(NOTIFY_TRANSACTION_FINISHED, tid)
def lockInformation(tid):
return Packet(LOCK_INFORMATION, tid)
def notifyInformationLocked(tid):
return Packet(NOTIFY_INFORMATION_LOCKED, tid)
def invalidateObjects(oid_list, tid):
body = [pack('!8sL', tid, len(oid_list))]
body.extend(oid_list)
body = ''.join(body)
return Packet(INVALIDATE_OBJECTS, body)
def unlockInformation(tid):
return Packet(UNLOCK_INFORMATION, tid)
def abortTransaction(tid):
return Packet(ABORT_TRANSACTION, tid)
def askStoreTransaction(tid, user, desc, ext, oid_list):
user_len = len(user)
desc_len = len(desc)
ext_len = len(ext)
body = [pack('!8sLHHH', tid, len(oid_list), len(user), len(desc), len(ext))]
body.append(user)
body.append(desc)
body.append(ext)
body.extend(oid_list)
body = ''.join(body)
return Packet(ASK_STORE_TRANSACTION, body)
def answerStoreTransaction(tid):
return Packet(ANSWER_STORE_TRANSACTION, tid)
def askStoreObject(oid, serial, compression, checksum, data, tid):
body = pack('!8s8s8sBLL', oid, serial, tid, compression,
checksum, len(data)) + data
return Packet(ASK_STORE_OBJECT, body)
def answerStoreObject(conflicting, oid, serial):
body = pack('!B8s8s', conflicting, oid, serial)
return Packet(ANSWER_STORE_OBJECT, body)
def askObject(oid, serial, tid):
return Packet(ASK_OBJECT, pack('!8s8s8s', oid, serial, tid))
def answerObject(oid, serial_start, serial_end, compression,
checksum, data):
body = pack('!8s8s8sBLL', oid, serial_start, serial_end,
compression, checksum, len(data)) + data
return Packet(ANSWER_OBJECT, body)
def askTIDs(first, last, partition):
return Packet(ASK_TIDS, pack('!QQL', first, last, partition))
def answerTIDs(tid_list):
body = [pack('!L', len(tid_list))]
body.extend(tid_list)
body = ''.join(body)
return Packet(ANSWER_TIDS, body)
def askTransactionInformation(tid):
return Packet(ASK_TRANSACTION_INFORMATION, pack('!8s', tid))
def answerTransactionInformation(tid, user, desc, ext, oid_list):
body = [pack('!8sHHHL', tid, len(user), len(desc), len(ext), len(oid_list))]
body.append(user)
body.append(desc)
body.append(ext)
body.extend(oid_list)
body = ''.join(body)
return Packet(ANSWER_TRANSACTION_INFORMATION, body)
def askObjectHistory(oid, first, last):
return Packet(ASK_OBJECT_HISTORY, pack('!8sQQ', oid, first, last))
def answerObjectHistory(oid, history_list):
body = [pack('!8sL', oid, len(history_list))]
# history_list is a list of tuple (serial, size)
for history_tuple in history_list:
body.append(pack('!8sL', history_tuple[0], history_tuple[1]))
body = ''.join(body)
return Packet(ANSWER_OBJECT_HISTORY, body)
def askOIDs(first, last, partition):
return Packet(ASK_OIDS, pack('!QQL', first, last, partition))
def answerOIDs(oid_list):
body = [pack('!L', len(oid_list))]
body.extend(oid_list)
body = ''.join(body)
return Packet(ANSWER_OIDS, body)
def askPartitionList(min_offset, max_offset, uuid):
body = [pack('!LL16s', min_offset, max_offset, uuid)]
body = ''.join(body)
return Packet(ASK_PARTITION_LIST, body)
def answerPartitionList(ptid, row_list):
body = [pack('!8sL', ptid, len(row_list))]
for offset, cell_list in row_list:
body.append(pack('!LL', offset, len(cell_list)))
for uuid, state in cell_list:
body.append(pack('!16sH', uuid, state))
body = ''.join(body)
return Packet(ANSWER_PARTITION_LIST, body)
def askNodeList(node_type):
body = [pack('!H', node_type)]
body = ''.join(body)
return Packet(ASK_NODE_LIST, body)
def answerNodeList(node_list):
body = [pack('!L', len(node_list))]
for node_type, ip_address, port, uuid, state in node_list:
body.append(pack('!H4sH16sH', node_type, inet_aton(ip_address), port,
uuid, state))
body = ''.join(body)
return Packet(ANSWER_NODE_LIST, body)
def setNodeState(uuid, state, modify_partition_table):
body = [pack('!16sHB', uuid, state, modify_partition_table)]
body = ''.join(body)
return Packet(SET_NODE_STATE, body)
def answerNodeState(uuid, state):
body = [pack('!16sH', uuid, state)]
body = ''.join(body)
return Packet(ANSWER_NODE_STATE, body)
def addPendingNodes(uuid_list=()):
# an empty list means all current pending nodes
uuid_list = [pack('!16s', uuid) for uuid in uuid_list]
body = pack('!H', len(uuid_list)) + ''.join(uuid_list)
return Packet(ADD_PENDING_NODES, body)
def answerNewNodes(uuid_list):
# an empty list means no new nodes
uuid_list = [pack('!16s', uuid) for uuid in uuid_list]
body = pack('!H', len(uuid_list)) + ''.join(uuid_list)
return Packet(ANSWER_NEW_NODES, body)
def askNodeInformation():
return Packet(ASK_NODE_INFORMATION)
def answerNodeInformation(node_list):
# XXX: copy-paste from notifyNodeInformation
body = [pack('!L', len(node_list))]
for node_type, ip_address, port, uuid, state in node_list:
body.append(pack('!H4sH16sH', node_type, inet_aton(ip_address), port,
uuid, state))
body = ''.join(body)
return Packet(ANSWER_NODE_INFORMATION, body)
def askClusterState():
return Packet(ASK_CLUSTER_STATE)
def answerClusterState(state):
body = pack('!H', state)
return Packet(ANSWER_CLUSTER_STATE, body)
def setClusterState(name, state):
body = [pack('!HL', state, len(name)), name]
body = ''.join(body)
return Packet(SET_CLUSTER_STATE, body)
def notifyClusterInformation(state):
body = pack('!H', state)
return Packet(NOTIFY_CLUSTER_INFORMATION, body)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/pt.py 0000664 0000000 0000000 00000023336 11227104631 0021226 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.protocol import UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, \
DISCARDED_STATE, RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
BROKEN_STATE, VALID_CELL_STATE_LIST, HIDDEN_STATE, PENDING_STATE
from neo.util import dump, u64
from neo.locking import RLock
class Cell(object):
"""This class represents a cell in a partition table."""
def __init__(self, node, state = UP_TO_DATE_STATE):
self.node = node
assert state in VALID_CELL_STATE_LIST
self.state = state
def getState(self):
return self.state
def setState(self, state):
assert state in VALID_CELL_STATE_LIST
self.state = state
def getNode(self):
return self.node
def getNodeState(self):
"""This is a short hand."""
return self.node.getState()
def getUUID(self):
return self.node.getUUID()
def getServer(self):
return self.node.getServer()
class PartitionTable(object):
"""This class manages a partition table."""
def __init__(self, num_partitions, num_replicas):
self.id = protocol.INVALID_PTID
self.np = num_partitions
self.nr = num_replicas
self.num_filled_rows = 0
self.partition_list = [[] for x in xrange(num_partitions)]
self.count_dict = {}
def getID(self):
return self.id
def getPartitions(self):
return self.np
def getReplicas(self):
return self.nr
def clear(self):
"""Forget an existing partition table."""
self.num_filled_rows = 0
self.partition_list = [[] for x in xrange(self.np)]
self.count_dict.clear()
def hasOffset(self, offset):
try:
return len(self.partition_list[offset]) > 0
except IndexError:
return False
def getNodeList(self):
"""Return all used nodes."""
node_list = []
for node, count in self.count_dict.iteritems():
if count > 0:
node_list.append(node)
return node_list
def getCellList(self, offset, readable=False, writable=False):
# allow all cell states
state_set = set(VALID_CELL_STATE_LIST)
if readable or writable:
# except non readables
state_set.remove(DISCARDED_STATE)
if readable:
# except non writables
state_set.remove(OUT_OF_DATE_STATE)
allowed_states = tuple(state_set)
try:
return [cell for cell in self.partition_list[offset] \
if cell is not None and cell.getState() in allowed_states]
except (TypeError, KeyError):
return []
def getCellListForID(self, id, readable=False, writable=False):
return self.getCellList(u64(id) % self.np, readable, writable)
def setCell(self, offset, node, state):
assert state in VALID_CELL_STATE_LIST
if state == DISCARDED_STATE:
return self.removeCell(offset, node)
if node.getState() in (BROKEN_STATE, DOWN_STATE):
return
row = self.partition_list[offset]
if len(row) == 0:
# Create a new row.
row = [Cell(node, state),]
if state != FEEDING_STATE:
self.count_dict[node] = self.count_dict.get(node, 0) + 1
self.partition_list[offset] = row
self.num_filled_rows += 1
else:
# XXX this can be slow, but it is necessary to remove a duplicate,
# if any.
for cell in row:
if cell.getNode() == node:
row.remove(cell)
if cell.getState() != FEEDING_STATE:
self.count_dict[node] = self.count_dict.get(node, 0) - 1
break
row.append(Cell(node, state))
if state != FEEDING_STATE:
self.count_dict[node] = self.count_dict.get(node, 0) + 1
def removeCell(self, offset, node):
row = self.partition_list[offset]
if row is not None:
for cell in row:
if cell.getNode() == node:
row.remove(cell)
if cell.getState() != FEEDING_STATE:
self.count_dict[node] = self.count_dict.get(node, 0) - 1
break
def filled(self):
return self.num_filled_rows == self.np
def log(self):
"""Help debugging partition table management.
Output sample:
DEBUG:root:pt: node 0: ad7ffe8ceef4468a0c776f3035c7a543, R
DEBUG:root:pt: node 1: a68a01e8bf93e287bd505201c1405bc2, R
DEBUG:root:pt: node 2: 67ae354b4ed240a0594d042cf5c01b28, R
DEBUG:root:pt: node 3: df57d7298678996705cd0092d84580f4, R
DEBUG:root:pt: 00000000: .UU.|U..U|.UU.|U..U|.UU.|U..U|.UU.|U..U|.UU.
DEBUG:root:pt: 00000009: U..U|.UU.|U..U|.UU.|U..U|.UU.|U..U|.UU.|U..U
Here, there are 4 nodes in RUNNING_STATE.
The first partition has 2 replicas in UP_TO_DATE_STATE, on nodes 1 and
2 (nodes 0 and 3 are displayed as unused for that partition by
displaying a dot).
The 8-digits number on the left represents the number of the first
partition on the line (here, line length is 9 to keep the docstring
width under 80 column).
"""
node_state_dict = { RUNNING_STATE: 'R',
TEMPORARILY_DOWN_STATE: 'T',
DOWN_STATE: 'D',
BROKEN_STATE: 'B',
HIDDEN_STATE: 'H',
PENDING_STATE: 'P'}
cell_state_dict = { UP_TO_DATE_STATE: 'U',
OUT_OF_DATE_STATE: 'O',
FEEDING_STATE: 'F',
DISCARDED_STATE: 'D'}
node_list = self.count_dict.keys()
node_list.sort()
node_dict = {}
for i, node in enumerate(node_list):
node_dict[node] = i
for node, i in node_dict.iteritems():
logging.debug('pt: node %d: %s, %s', i, dump(node.getUUID()),
node_state_dict[node.getState()])
line = []
max_line_len = 20 # XXX: hardcoded number of partitions per line
for offset, row in enumerate(self.partition_list):
if len(line) == max_line_len:
logging.debug('pt: %08d: %s', offset - max_line_len,
'|'.join(line))
line = []
if row is None:
line.append('X' * len(node_list))
else:
cell = []
cell_dict = dict([(node_dict[x.getNode()], x) for x in row])
for node in xrange(len(node_list)):
if node in cell_dict:
cell.append(cell_state_dict[cell_dict[node].getState()])
else:
cell.append('.')
line.append(''.join(cell))
if len(line):
logging.debug('pt: %08d: %s', offset - len(line) + 1,
'|'.join(line))
def operational(self):
if not self.filled():
return False
# FIXME it is better to optimize this code, as this could be extremely
# slow. The possible fix is to have a handler to notify a change on
# a node state, and record which rows are ready.
for row in self.partition_list:
for cell in row:
if cell.getState() in (UP_TO_DATE_STATE, FEEDING_STATE) \
and cell.getNodeState() == RUNNING_STATE:
break
else:
return False
return True
def getRow(self, offset):
row = self.partition_list[offset]
if row is None:
return []
return [(cell.getUUID(), cell.getState()) for cell in row]
def thread_safe(method):
def wrapper(self, *args, **kwargs):
self.lock()
try:
return method(self, *args, **kwargs)
finally:
self.unlock()
return wrapper
class MTPartitionTable(PartitionTable):
""" Thread-safe aware version of the partition table, override only methods
used in the client """
def __init__(self, *args, **kwargs):
self._lock = RLock()
PartitionTable.__init__(self, *args, **kwargs)
def lock(self):
self._lock.acquire()
def unlock(self):
self._lock.release()
@thread_safe
def getCellListForID(self, *args, **kwargs):
return PartitionTable.getCellListForID(self, *args, **kwargs)
@thread_safe
def setCell(self, *args, **kwargs):
return PartitionTable.setCell(self, *args, **kwargs)
@thread_safe
def clear(self, *args, **kwargs):
return PartitionTable.clear(self, *args, **kwargs)
@thread_safe
def operational(self, *args, **kwargs):
return PartitionTable.operational(self, *args, **kwargs)
@thread_safe
def getNodeList(self, *args, **kwargs):
return PartitionTable.getNodeList(self, *args, **kwargs)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/ 0000775 0000000 0000000 00000000000 11227104631 0021666 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/__init__.py 0000664 0000000 0000000 00000000000 11227104631 0023765 0 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/app.py 0000664 0000000 0000000 00000026500 11227104631 0023023 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
import sys
from time import time
from struct import unpack
from collections import deque
from neo.config import ConfigurationManager
from neo import protocol
from neo.protocol import TEMPORARILY_DOWN_STATE, INVALID_UUID, INVALID_PTID, \
partition_cell_states, HIDDEN_STATE
from neo.node import NodeManager, MasterNode, StorageNode
from neo.event import EventManager
from neo.storage.mysqldb import MySQLDatabaseManager
from neo.connection import ListeningConnection, ClientConnection
from neo.exception import OperationFailure, PrimaryFailure
from neo.storage import handlers
from neo.storage.replicator import Replicator
from neo.connector import getConnectorHandler
from neo.pt import PartitionTable
from neo.util import dump
from neo.bootstrap import BootstrapManager
class Application(object):
"""The storage node application."""
def __init__(self, file, section, reset = False):
config = ConfigurationManager(file, section)
self.name = config.getName()
logging.debug('the name is %s', self.name)
self.connector_handler = getConnectorHandler(config.getConnector())
self.server = config.getServer()
logging.debug('IP address is %s, port is %d', *(self.server))
self.master_node_list = config.getMasterNodeList()
logging.debug('master nodes are %s', self.master_node_list)
# Internal attributes.
self.em = EventManager()
self.nm = NodeManager()
self.dm = MySQLDatabaseManager(database = config.getDatabase(),
user = config.getUser(),
password = config.getPassword())
# The partition table is initialized after getting the number of
# partitions.
self.pt = None
self.replicator = None
self.listening_conn = None
self.master_conn = None
# ready is True when operational and got all informations
self.ready = False
self.has_node_information = False
self.has_partition_table = False
self.dm.setup(reset)
self.loadConfiguration()
def loadConfiguration(self):
"""Load persistent configuration data from the database.
If data is not present, generate it."""
dm = self.dm
self.uuid = dm.getUUID()
if self.uuid is None:
self.uuid = INVALID_UUID
num_partitions = dm.getNumPartitions()
num_replicas = dm.getNumReplicas()
if num_partitions is not None and num_replicas is not None:
if num_partitions <= 0:
raise RuntimeError, 'partitions must be more than zero'
# create a partition table
self.pt = PartitionTable(num_partitions, num_replicas)
name = dm.getName()
if name is None:
dm.setName(self.name)
elif name != self.name:
raise RuntimeError('name does not match with the database')
self.ptid = dm.getPTID() # return ptid or INVALID_PTID
if self.ptid == INVALID_PTID:
dm.setPTID(self.ptid)
logging.info("loaded configuration from db : uuid = %s, ptid = %s, name = %s, np = %s, nr = %s" \
%(dump(self.uuid), dump(self.ptid), name, num_partitions, num_replicas))
def loadPartitionTable(self):
"""Load a partition table from the database."""
nm = self.nm
pt = self.pt
pt.clear()
for offset, uuid, state in self.dm.getPartitionTable():
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != self.uuid:
# If this node is not self, assume that it is temporarily
# down at the moment. This state will change once every
# node starts to connect to a primary master node.
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
state = partition_cell_states.get(state)
pt.setCell(offset, node, state)
def run(self):
"""Make sure that the status is sane and start a loop."""
if len(self.name) == 0:
raise RuntimeError, 'cluster name must be non-empty'
for server in self.master_node_list:
self.nm.add(MasterNode(server = server))
# Make a listening port
handler = handlers.IdentificationHandler(self)
self.listening_conn = ListeningConnection(self.em, handler,
addr=self.server, connector_handler=self.connector_handler)
# Connect to a primary master node, verify data, and
# start the operation. This cycle will be executed permentnly,
# until the user explicitly requests a shutdown.
while 1:
# look for the primary master
self.connectToPrimaryMaster()
self.operational = False
try:
while 1:
try:
# check my state
node = self.nm.getNodeByUUID(self.uuid)
if node is not None and node.getState() == HIDDEN_STATE:
self.wait()
self.verifyData()
self.initialize()
self.doOperation()
except OperationFailure:
logging.error('operation stopped')
# XXX still we can receive answer packet here
# this must be handle in order not to fail
self.operational = False
except PrimaryFailure, msg:
logging.error('primary master is down : %s' % msg)
def connectToPrimaryMaster(self):
"""Find a primary master node, and connect to it.
If a primary master node is not elected or ready, repeat
the attempt of a connection periodically.
Note that I do not accept any connection from non-master nodes
at this stage."""
pt = self.pt
# First of all, make sure that I have no connection.
for conn in self.em.getConnectionList():
if not isinstance(conn, ListeningConnection):
conn.close()
# search, find, connect and identify to the primary master
bootstrap = BootstrapManager(self, self.name,
protocol.STORAGE_NODE_TYPE, self.uuid, self.server)
data = bootstrap.getPrimaryConnection(self.connector_handler)
(node, conn, uuid, num_partitions, num_replicas) = data
self.master_node = node
self.master_conn = conn
self.uuid = uuid
self.dm.setUUID(uuid)
# Reload a partition table from the database. This is necessary
# when a previous primary master died while sending a partition
# table, because the table might be incomplete.
if pt is not None:
self.loadPartitionTable()
self.ptid = self.dm.getPTID()
if num_partitions != pt.getPartitions():
raise RuntimeError('the number of partitions is inconsistent')
if pt is None or pt.getReplicas() != num_replicas:
# changing number of replicas is not an issue
self.dm.setNumPartitions(num_partitions)
self.dm.setNumReplicas(num_replicas)
self.pt = PartitionTable(num_partitions, num_replicas)
self.loadPartitionTable()
self.ptid = self.dm.getPTID()
def verifyData(self):
"""Verify data under the control by a primary master node.
Connections from client nodes may not be accepted at this stage."""
logging.info('verifying data')
handler = handlers.VerificationHandler(self)
self.master_conn.setHandler(handler)
em = self.em
while not self.operational:
em.poll(1)
def initialize(self):
""" Retreive partition table and node informations from the primary """
logging.debug('initializing...')
handler = handlers.InitializationHandler(self)
self.master_conn.setHandler(handler)
# ask node list and partition table
self.has_node_information = False
self.has_partition_table = False
self.pt.clear()
self.master_conn.ask(protocol.askNodeInformation())
self.master_conn.ask(protocol.askPartitionTable(()))
while not self.has_node_information or not self.has_partition_table:
self.em.poll(1)
self.ready = True
# TODO: notify the master that I switch to operation state, so other
# nodes can now connect to me
def doOperation(self):
"""Handle everything, including replications and transactions."""
logging.info('doing operation')
em = self.em
handler = handlers.MasterOperationHandler(self)
self.master_conn.setHandler(handler)
# Forget all unfinished data.
self.dm.dropUnfinishedData()
# This is a mapping between transaction IDs and information on
# UUIDs of client nodes which issued transactions and objects
# which were stored.
self.transaction_dict = {}
# This is a mapping between object IDs and transaction IDs. Used
# for locking objects against store operations.
self.store_lock_dict = {}
# This is a mapping between object IDs and transactions IDs. Used
# for locking objects against load operations.
self.load_lock_dict = {}
# This is a queue of events used to delay operations due to locks.
self.event_queue = deque()
# The replicator.
self.replicator = Replicator(self)
while 1:
em.poll(1)
if self.replicator.pending():
self.replicator.act()
def wait(self):
# change handler
logging.info("waiting in hidden state")
handler = handlers.HiddenHandler(self)
for conn in self.em.getConnectionList():
conn.setHandler(handler)
node = self.nm.getNodeByUUID(self.uuid)
while 1:
self.em.poll(1)
if node.getState() != HIDDEN_STATE:
break
def queueEvent(self, callable, *args, **kwargs):
self.event_queue.append((callable, args, kwargs))
def executeQueuedEvents(self):
l = len(self.event_queue)
p = self.event_queue.popleft
for i in xrange(l):
_callable, args, kwargs = p()
_callable(*args, **kwargs)
def shutdown(self):
"""Close all connections and exit"""
for c in self.em.getConnectionList():
if not c.isListeningConnection():
c.close()
sys.exit("Application has been asked to shut down")
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/database.py 0000664 0000000 0000000 00000021301 11227104631 0024001 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class DatabaseManager(object):
"""This class only describes an interface for database managers."""
def __init__(self, **kwargs):
"""Initialize the object."""
pass
def close(self):
""" close the database connection """
raise NotImplementedError('this method must be overridden')
def setup(self, reset = 0):
"""Set up a database. If reset is true, existing data must be
discarded."""
raise NotImplementedError('this method must be overridden')
def getUUID(self):
"""Load an UUID from a database. If not present, return None."""
raise NotImplementedError('this method must be overridden')
def setUUID(self, uuid):
"""Store an UUID into a database."""
raise NotImplementedError('this method must be overridden')
def getNumPartitions(self):
"""Load the number of partitions from a database. If not present,
return None."""
raise NotImplementedError('this method must be overridden')
def setNumPartitions(self, num_partitions):
"""Store the number of partitions into a database."""
raise NotImplementedError('this method must be overridden')
def getNumReplicas(self):
"""Load the number of replicas from a database. If not present,
return None."""
raise NotImplementedError('this method must be overridden')
def setNumReplicas(self, num_partitions):
"""Store the number of replicas into a database."""
raise NotImplementedError('this method must be overridden')
def getName(self):
"""Load a name from a database. If not present, return None."""
raise NotImplementedError('this method must be overridden')
def setName(self, name):
"""Store a name into a database."""
raise NotImplementedError('this method must be overridden')
def getPTID(self):
"""Load a Partition Table ID from a database. If not present,
return None."""
raise NotImplementedError('this method must be overridden')
def setPTID(self, ptid):
"""Store a Partition Table ID into a database."""
raise NotImplementedError('this method must be overridden')
def getPartitionTable(self):
"""Return a whole partition table as a tuple of rows. Each row
is again a tuple of an offset (row ID), an UUID of a storage
node, and a cell state."""
raise NotImplementedError('this method must be overridden')
def getLastOID(self, all = True):
"""Return the last OID in a database. If all is true,
unfinished transactions must be taken account into. If there
is no OID in the database, return None."""
raise NotImplementedError('this method must be overridden')
def getLastTID(self, all = True):
"""Return the last TID in a database. If all is true,
unfinished transactions must be taken account into. If there
is no TID in the database, return None."""
raise NotImplementedError('this method must be overridden')
def getUnfinishedTIDList(self):
"""Return a list of unfinished transaction's IDs."""
raise NotImplementedError('this method must be overridden')
def objectPresent(self, oid, tid, all = True):
"""Return true iff an object specified by a given pair of an
object ID and a transaction ID is present in a database.
Otherwise, return false. If all is true, the object must be
searched from unfinished transactions as well."""
raise NotImplementedError('this method must be overridden')
def getObject(self, oid, tid = None, before_tid = None):
"""Return a tuple of a serial, next serial, a compression
specification, a checksum, and object data, if a given object
ID is present. Otherwise, return None. If tid is None and
before_tid is None, the latest revision is taken. If tid is
specified, the given revision is taken. If tid is not specified,
but before_tid is specified, the latest revision before the
given revision is taken. The next serial is a serial right after
before_tid, if specified. Otherwise, it is None."""
raise NotImplementedError('this method must be overridden')
def changePartitionTable(self, ptid, cell_list):
"""Change a part of a partition table. The list of cells is
a tuple of tuples, each of which consists of an offset (row ID),
an UUID of a storage node, and a cell state. The Partition
Table ID must be stored as well."""
raise NotImplementedError('this method must be overridden')
def setPartitionTable(self, ptid, cell_list):
"""Set a whole partition table. The semantics is the same as
changePartitionTable, except that existing data must be
thrown away."""
raise NotImplementedError('this method must be overridden')
def dropUnfinishedData(self):
"""Drop any unfinished data from a database."""
raise NotImplementedError('this method must be overridden')
def storeTransaction(self, tid, object_list, transaction, temporary = True):
"""Store a transaction temporarily, if temporary is true. Note
that this transaction is not finished yet. The list of objects
contains tuples, each of which consists of an object ID,
a compression specification, a checksum and object data.
The transaction is either None or a tuple of the list of OIDs,
user information, a description and extension information."""
raise NotImplementedError('this method must be overridden')
def finishTransaction(self, tid):
"""Finish a transaction specified by a given ID, by moving
temporarily data to a finished area."""
raise NotImplementedError('this method must be overridden')
def deleteTransaction(self, tid, all = False):
"""Delete a transaction specified by a given ID from a temporarily
area. If all is true, it must be deleted even from a finished
area."""
raise NotImplementedError('this method must be overridden')
def getTransaction(self, tid, all = False):
"""Return a tuple of the list of OIDs, user information,
a description, and extension information, for a given transaction
ID. If there is no such transaction ID in a database, return None.
If all is true, the transaction must be searched from a temporary
area as well."""
raise NotImplementedError('this method must be overridden')
def getOIDList(self, offset, length, num_partitions, partition_list):
"""Return a list of OIDs in descending order from an offset,
at most the specified length. The list of partitions are passed
to filter out non-applicable TIDs."""
raise NotImplementedError('this method must be overridden')
def getObjectHistory(self, oid, offset = 0, length = 1):
"""Return a list of serials and sizes for a given object ID.
The length specifies the maximum size of such a list. The first serial
must be the last serial, and the list must be sorted in descending
order. If there is no such object ID in a database, return None."""
raise NotImplementedError('this method must be overridden')
def getTIDList(self, offset, length, num_partitions, partition_list):
"""Return a list of TIDs in descending order from an offset,
at most the specified length. The list of partitions are passed
to filter out non-applicable TIDs."""
raise NotImplementedError('this method must be overridden')
def getTIDListPresent(self, tid_list):
"""Return a list of TIDs which are present in a database among
the given list."""
raise NotImplementedError('this method must be overridden')
def getSerialListPresent(self, oid, serial_list):
"""Return a list of serials which are present in a database among
the given list."""
raise NotImplementedError('this method must be overridden')
__del__ = close
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/ 0000775 0000000 0000000 00000000000 11227104631 0023466 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/__init__.py 0000664 0000000 0000000 00000024014 11227104631 0025600 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.handler import EventHandler
from neo import protocol
from neo.protocol import Packet, UnexpectedPacketError, \
INVALID_UUID, RUNNING_STATE, BROKEN_STATE, \
MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, \
DOWN_STATE, TEMPORARILY_DOWN_STATE, HIDDEN_STATE
from neo.util import dump
from neo.node import MasterNode, StorageNode, ClientNode
from neo.exception import PrimaryFailure, OperationFailure
class BaseStorageHandler(EventHandler):
"""This class implements a generic part of the event handlers."""
def dealWithClientFailure(self, uuid):
pass
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
raise NotImplementedError('this method must be overridden')
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port,
num_partitions, num_replicas, your_uuid):
raise NotImplementedError('this method must be overridden')
def handleAskLastIDs(self, conn, packet):
raise NotImplementedError('this method must be overridden')
def handleAskPartitionTable(self, conn, packet, offset_list):
raise NotImplementedError('this method must be overridden')
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
raise NotImplementedError('this method must be overridden')
def handleStopOperation(self, conn, packet):
raise NotImplementedError('this method must be overridden')
def handleAskTransactionInformation(self, conn, packet, tid):
raise NotImplementedError('this method must be overridden')
def handleLockInformation(self, conn, packet, tid):
raise NotImplementedError('this method must be overridden')
def handleUnlockInformation(self, conn, packet, tid):
raise NotImplementedError('this method must be overridden')
def handleNotifyClusterInformation(self, conn, packet, state):
logging.error('ignoring notify cluster information in %s' % self.__class__.__name__)
def handleAbortTransaction(self, conn, packet, tid):
logging.info('ignoring abort transaction')
pass
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
logging.info('ignoring answer unfinished transactions')
pass
def handleAskOIDs(self, conn, packet, first, last, partition):
logging.info('ignoring ask oids')
pass
class BaseMasterHandler(BaseStorageHandler):
def timeoutExpired(self, conn):
raise PrimaryFailure('times out')
def connectionClosed(self, conn):
raise PrimaryFailure('dead')
def peerBroken(self, conn):
raise PrimaryFailure('broken')
def handleReelectPrimaryMaster(self, conn, packet):
raise PrimaryFailure('re-election occurs')
def handleNotifyNodeInformation(self, conn, packet, node_list):
"""Store information on nodes, only if this is sent by a primary
master node."""
# XXX it might be better to implement this callback in each handler.
uuid = conn.getUUID()
app = self.app
for node_type, ip_address, port, uuid, state in node_list:
addr = (ip_address, port)
# Try to retrieve it from nm
n = None
if uuid != INVALID_UUID:
n = app.nm.getNodeByUUID(uuid)
if n is None:
n = app.nm.getNodeByServer(addr)
if n is not None and uuid != INVALID_UUID:
# node only exists by address, remove it
app.nm.remove(n)
n = None
elif n.getServer() != addr:
# same uuid but different address, update it
n.setServer(addr)
if state == protocol.DOWN_STATE and n is not None:
# this node is consider as down, remove it
self.app.nm.remove(n)
continue
if node_type == MASTER_NODE_TYPE:
if n is None:
n = MasterNode(server = addr)
app.nm.add(n)
n.setState(state)
if uuid != INVALID_UUID:
if n.getUUID() is None:
n.setUUID(uuid)
elif node_type == STORAGE_NODE_TYPE:
if uuid == INVALID_UUID:
# No interest.
continue
if uuid == self.app.uuid:
# This is me, do what the master tell me
logging.info("I was told I'm %s" %(state))
if state in (DOWN_STATE, TEMPORARILY_DOWN_STATE, BROKEN_STATE):
conn.close()
self.app.shutdown()
elif state == HIDDEN_STATE:
n = app.nm.getNodeByUUID(uuid)
if n is not None:
n.setState(state)
raise OperationFailure
if n is None:
n = StorageNode(server = addr, uuid = uuid)
app.nm.add(n)
n.setState(state)
elif node_type == CLIENT_NODE_TYPE:
if uuid == INVALID_UUID:
# No interest.
continue
if state == RUNNING_STATE:
if n is None:
n = ClientNode(uuid = uuid)
app.nm.add(n)
else:
self.dealWithClientFailure(uuid)
if n is not None:
logging.debug('removing client node %s', dump(uuid))
app.nm.remove(n)
if n is not None:
logging.info("added %s %s" %(dump(n.getUUID()), n.getServer()))
class BaseClientAndStorageOperationHandler(BaseStorageHandler):
""" Accept requests common to client and storage nodes """
def handleAskTIDs(self, conn, packet, first, last, partition):
# This method is complicated, because I must return TIDs only
# about usable partitions assigned to me.
if first >= last:
raise protocol.ProtocolError('invalid offsets')
app = self.app
if partition == protocol.INVALID_PARTITION:
# Collect all usable partitions for me.
getCellList = app.pt.getCellList
partition_list = []
for offset in xrange(app.pt.getPartitions()):
for cell in getCellList(offset, readable=True):
if cell.getUUID() == app.uuid:
partition_list.append(offset)
break
else:
partition_list = [partition]
tid_list = app.dm.getTIDList(first, last - first,
app.pt.getPartitions(), partition_list)
conn.answer(protocol.answerTIDs(tid_list), packet)
def handleAskObjectHistory(self, conn, packet, oid, first, last):
if first >= last:
raise protocol.ProtocolError( 'invalid offsets')
app = self.app
history_list = app.dm.getObjectHistory(oid, first, last - first)
if history_list is None:
history_list = []
p = protocol.answerObjectHistory(oid, history_list)
conn.answer(p, packet)
def handleAskTransactionInformation(self, conn, packet, tid):
app = self.app
t = app.dm.getTransaction(tid)
if t is None:
p = protocol.tidNotFound('%s does not exist' % dump(tid))
else:
p = protocol.answerTransactionInformation(tid, t[1], t[2], t[3], t[0])
conn.answer(p, packet)
def handleAskObject(self, conn, packet, oid, serial, tid):
app = self.app
if oid in app.load_lock_dict:
# Delay the response.
app.queueEvent(self.handleAskObject, conn, packet, oid,
serial, tid)
return
if serial == protocol.INVALID_SERIAL:
serial = None
if tid == protocol.INVALID_TID:
tid = None
o = app.dm.getObject(oid, serial, tid)
if o is not None:
serial, next_serial, compression, checksum, data = o
if next_serial is None:
next_serial = protocol.INVALID_SERIAL
logging.debug('oid = %s, serial = %s, next_serial = %s',
dump(oid), dump(serial), dump(next_serial))
p = protocol.answerObject(oid, serial, next_serial,
compression, checksum, data)
else:
logging.debug('oid = %s not found', dump(oid))
p = protocol.oidNotFound('%s does not exist' % dump(oid))
conn.answer(p, packet)
# import all handlers in the current namespace
from neo.storage.handlers.identification import IdentificationHandler
from neo.storage.handlers.initialization import InitializationHandler
from neo.storage.handlers.verification import VerificationHandler
from neo.storage.handlers.replication import ReplicationHandler
from neo.storage.handlers.storage import StorageOperationHandler
from neo.storage.handlers.master import MasterOperationHandler
from neo.storage.handlers.client import ClientOperationHandler
from neo.storage.handlers.hidden import HiddenHandler
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/client.py 0000664 0000000 0000000 00000012621 11227104631 0025320 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.storage.handlers import BaseClientAndStorageOperationHandler
from neo.protocol import INVALID_SERIAL, INVALID_TID, INVALID_PARTITION, \
TEMPORARILY_DOWN_STATE, DISCARDED_STATE, OUT_OF_DATE_STATE
from neo.util import dump
class TransactionInformation(object):
"""This class represents information on a transaction."""
def __init__(self, uuid):
self._uuid = uuid
self._object_dict = {}
self._transaction = None
def getUUID(self):
return self._uuid
def addObject(self, oid, compression, checksum, data):
self._object_dict[oid] = (oid, compression, checksum, data)
def addTransaction(self, oid_list, user, desc, ext):
self._transaction = (oid_list, user, desc, ext)
def getObjectList(self):
return self._object_dict.values()
def getTransaction(self):
return self._transaction
class ClientOperationHandler(BaseClientAndStorageOperationHandler):
def dealWithClientFailure(self, uuid):
app = self.app
for tid, t in app.transaction_dict.items():
if t.getUUID() == uuid:
for o in t.getObjectList():
oid = o[0]
try:
del app.store_lock_dict[oid]
del app.load_lock_dict[oid]
except KeyError:
pass
del app.transaction_dict[tid]
# Now it may be possible to execute some events.
app.executeQueuedEvents()
def timeoutExpired(self, conn):
self.dealWithClientFailure(conn.getUUID())
BaseClientAndStorageOperationHandler.timeoutExpired(self, conn)
def connectionClosed(self, conn):
self.dealWithClientFailure(conn.getUUID())
BaseClientAndStorageOperationHandler.connectionClosed(self, conn)
def peerBroken(self, conn):
self.dealWithClientFailure(conn.getUUID())
BaseClientAndStorageOperationHandler.peerBroken(self, conn)
def connectionCompleted(self, conn):
BaseClientAndStorageOperationHandler.connectionCompleted(self, conn)
def handleAbortTransaction(self, conn, packet, tid):
app = self.app
try:
t = app.transaction_dict[tid]
object_list = t.getObjectList()
for o in object_list:
oid = o[0]
try:
del app.load_lock_dict[oid]
except KeyError:
pass
del app.store_lock_dict[oid]
del app.transaction_dict[tid]
# Now it may be possible to execute some events.
app.executeQueuedEvents()
except KeyError:
pass
def handleAskStoreTransaction(self, conn, packet, tid, user, desc,
ext, oid_list):
uuid = conn.getUUID()
app = self.app
t = app.transaction_dict.setdefault(tid, TransactionInformation(uuid))
t.addTransaction(oid_list, user, desc, ext)
conn.answer(protocol.answerStoreTransaction(tid), packet)
def handleAskStoreObject(self, conn, packet, oid, serial,
compression, checksum, data, tid):
uuid = conn.getUUID()
# First, check for the locking state.
app = self.app
locking_tid = app.store_lock_dict.get(oid)
if locking_tid is not None:
if locking_tid < tid:
# Delay the response.
app.queueEvent(self.handleAskStoreObject, conn, packet,
oid, serial, compression, checksum,
data, tid)
else:
# If a newer transaction already locks this object,
# do not try to resolve a conflict, so return immediately.
logging.info('unresolvable conflict in %s', dump(oid))
p = protocol.answerStoreObject(1, oid, locking_tid)
conn.answer(p, packet)
return
# Next, check if this is generated from the latest revision.
history_list = app.dm.getObjectHistory(oid)
if history_list:
last_serial = history_list[0][0]
if last_serial != serial:
logging.info('resolvable conflict in %s', dump(oid))
p = protocol.answerStoreObject(1, oid, last_serial)
conn.answer(p, packet)
return
# Now store the object.
t = app.transaction_dict.setdefault(tid, TransactionInformation(uuid))
t.addObject(oid, compression, checksum, data)
p = protocol.answerStoreObject(0, oid, serial)
conn.answer(p, packet)
app.store_lock_dict[oid] = tid
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/hidden.py 0000664 0000000 0000000 00000014641 11227104631 0025301 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.storage.handlers import BaseMasterHandler
from neo.protocol import Packet, \
INVALID_UUID, RUNNING_STATE, BROKEN_STATE, \
MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, \
DOWN_STATE, TEMPORARILY_DOWN_STATE, HIDDEN_STATE, \
DISCARDED_STATE, OUT_OF_DATE_STATE, UnexpectedPacketError
from neo.node import StorageNode
from neo.connection import ClientConnection
from neo import decorators
# FIXME: before move handlers, this one was inheriting from EventHandler
# instead of BaseStorageHandler
class HiddenHandler(BaseMasterHandler):
"""This class implements a generic part of the event handlers."""
def __init__(self, app):
self.app = app
BaseStorageHandler.__init__(self, app)
def handleNotifyNodeInformation(self, conn, packet, node_list):
"""Store information on nodes, only if this is sent by a primary
master node."""
uuid = conn.getUUID()
if uuid is None:
raise UnexpectedPacketError
app = self.app
node = app.nm.getNodeByUUID(uuid)
if node.getNodeType() != MASTER_NODE_TYPE:
return
for node_type, ip_address, port, uuid, state in node_list:
if node_type == STORAGE_NODE_TYPE:
if uuid == INVALID_UUID:
# No interest.
continue
if uuid == self.app.uuid:
# This is me, do what the master tell me
if state in (DOWN_STATE, TEMPORARILY_DOWN_STATE, BROKEN_STATE):
conn.close()
self.app.shutdown()
elif state == HIDDEN_STATE:
# I know I'm hidden
continue
else:
# I must be working again
n = app.nm.getNodeByUUID(uuid)
n.setState(state)
else:
# Do not care of other node
pass
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
pass
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port,
num_partitions, num_replicas, your_uuid):
pass
def handleAnswerPrimaryMaster(self, conn, packet, primary_uuid,
known_master_list):
pass
def handleAskLastIDs(self, conn, packet):
pass
def handleAskPartitionTable(self, conn, packet, offset_list):
pass
def handleSendPartitionTable(self, conn, packet, ptid, row_list):
pass
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
"""This is very similar to Send Partition Table, except that
the information is only about changes from the previous."""
app = self.app
nm = app.nm
pt = app.pt
if app.ptid >= ptid:
# Ignore this packet.
logging.debug('ignoring older partition changes')
return
# First, change the table on memory.
app.ptid = ptid
for offset, uuid, state in cell_list:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
if uuid == app.uuid and app.replicator is not None:
# If this is for myself, this can affect replications.
if state == DISCARDED_STATE:
app.replicator.removePartition(offset)
elif state == OUT_OF_DATE_STATE:
app.replicator.addPartition(offset)
# Then, the database.
app.dm.changePartitionTable(ptid, cell_list)
app.pt.log()
@decorators.client_connection_required
def handleStartOperation(self, conn, packet):
self.app.operational = True
def handleStopOperation(self, conn, packet):
pass
def handleAskUnfinishedTransactions(self, conn, packet):
pass
def handleAskTransactionInformation(self, conn, packet, tid):
pass
def handleAskObjectPresent(self, conn, packet, oid, tid):
pass
def handleDeleteTransaction(self, conn, packet, tid):
pass
def handleCommitTransaction(self, conn, packet, tid):
pass
def handleLockInformation(self, conn, packet, tid):
pass
def handleUnlockInformation(self, conn, packet, tid):
pass
def handleAskObject(self, conn, packet, oid, serial, tid):
pass
def handleAskTIDs(self, conn, packet, first, last, partition):
pass
def handleAskObjectHistory(self, conn, packet, oid, first, last):
pass
def handleAskStoreTransaction(self, conn, packet, tid, user, desc,
ext, oid_list):
pass
def handleAskStoreObject(self, conn, packet, oid, serial,
compression, checksum, data, tid):
pass
def handleAbortTransaction(self, conn, packet, tid):
logging.debug('ignoring abort transaction')
pass
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
logging.debug('ignoring answer last ids')
pass
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
logging.debug('ignoring answer unfinished transactions')
pass
def handleAskOIDs(self, conn, packet, first, last, partition):
logging.debug('ignoring ask oids')
pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/identification.py 0000664 0000000 0000000 00000006113 11227104631 0027032 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.storage.handlers import BaseStorageHandler
from neo.protocol import BROKEN_STATE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE
from neo import protocol
from neo.util import dump
from neo.node import ClientNode
class IdentificationHandler(BaseStorageHandler):
""" Handler used for incoming connections during operation state """
def connectionClosed(self, conn):
logging.warning('lost a node in IdentificationEventHandler')
def timeoutExpired(self, conn):
logging.warning('lost a node in IdentificationEventHandler')
def peerBroken(self, conn):
logging.warning('lost a node in IdentificationEventHandler')
def handleRequestNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port, name):
self.checkClusterName(name)
# reject any incoming connections if not ready
if not self.app.ready:
raise protocol.NotReadyError
app = self.app
node = app.nm.getNodeByUUID(uuid)
# choose the handler according to the node type
if node_type == protocol.CLIENT_NODE_TYPE:
from neo.storage.handlers.client import ClientOperationHandler
handler = ClientOperationHandler
if node is None:
node = ClientNode()
app.nm.add(node)
elif node_type == protocol.STORAGE_NODE_TYPE:
from neo.storage.handlers.storage import StorageOperationHandler
handler = StorageOperationHandler
else:
raise protocol.protocolError('reject non-client-or-storage node')
if node is None:
logging.error('reject an unknown node %s', dump(uuid))
raise protocol.NotReadyError
# If this node is broken, reject it.
if node.getState() == BROKEN_STATE:
raise protocol.BrokenNodeDisallowedError
# apply the handler and set up the connection
handler = handler(self.app)
conn.setHandler(handler)
conn.setUUID(uuid)
node.setUUID(uuid)
args = (STORAGE_NODE_TYPE, app.uuid, app.server[0], app.server[1],
app.pt.getPartitions(), app.pt.getReplicas(), uuid)
# accept the identification and trigger an event
conn.answer(protocol.acceptNodeIdentification(*args), packet)
handler.connectionCompleted(conn)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/initialization.py 0000664 0000000 0000000 00000006354 11227104631 0027077 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.storage.handlers import BaseMasterHandler
from neo.protocol import TEMPORARILY_DOWN_STATE
from neo import protocol
from neo.node import StorageNode
class InitializationHandler(BaseMasterHandler):
def handleAnswerNodeInformation(self, conn, packet, node_list):
assert not node_list
self.app.has_node_information = True
def handleNotifyNodeInformation(self, conn, packet, node_list):
# FIXME: This message could be replaced by a SendNodeInformation to be
# consistent with SendPartitionTable.
BaseMasterHandler.handleNotifyNodeInformation(self, conn, packet, node_list)
def handleSendPartitionTable(self, conn, packet, ptid, row_list):
"""A primary master node sends this packet to synchronize a partition
table. Note that the message can be split into multiple packets."""
app = self.app
nm = app.nm
pt = app.pt
if app.ptid != ptid:
app.ptid = ptid
pt.clear()
for offset, row in row_list:
for uuid, state in row:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
if pt.filled():
# If the table is filled, I assume that the table is ready
# to use. Thus install it into the database for persistency.
cell_list = []
for offset in xrange(app.pt.getPartitions()):
for cell in pt.getCellList(offset):
cell_list.append((offset, cell.getUUID(),
cell.getState()))
app.dm.setPartitionTable(ptid, cell_list)
def handleAnswerPartitionTable(self, conn, packet, ptid, row_list):
assert not row_list
self.app.has_partition_table = True
logging.debug('Got the partition table :')
self.app.pt.log()
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
# XXX: Currently it safe to ignore those packets because the master is
# single threaded, it send the partition table without any changes at
# the same time. Latter it should be needed to put in queue any changes
# and apply them when the initial partition is filled.
logging.debug('ignoring notifyPartitionChanges during initialization')
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/master.py 0000664 0000000 0000000 00000007440 11227104631 0025340 0 ustar 00root root 0000000 0000000
#
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.storage.handlers import BaseMasterHandler
from neo.protocol import INVALID_SERIAL, INVALID_TID, INVALID_PARTITION, \
TEMPORARILY_DOWN_STATE, DISCARDED_STATE, OUT_OF_DATE_STATE
from neo.util import dump
from neo.node import StorageNode
from neo.exception import PrimaryFailure, OperationFailure
class MasterOperationHandler(BaseMasterHandler):
""" This handler is used for the primary master """
def handleStopOperation(self, conn, packet):
raise OperationFailure('operation stopped')
def handleAnswerLastIDs(self, conn, packet, loid, ltid, lptid):
self.app.replicator.setCriticalTID(packet, ltid)
def handleAnswerUnfinishedTransactions(self, conn, packet, tid_list):
self.app.replicator.setUnfinishedTIDList(tid_list)
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
"""This is very similar to Send Partition Table, except that
the information is only about changes from the previous."""
app = self.app
nm = app.nm
pt = app.pt
if app.ptid >= ptid:
# Ignore this packet.
logging.debug('ignoring older partition changes')
return
# First, change the table on memory.
app.ptid = ptid
for offset, uuid, state in cell_list:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
if uuid == app.uuid:
# If this is for myself, this can affect replications.
if state == DISCARDED_STATE:
app.replicator.removePartition(offset)
elif state == OUT_OF_DATE_STATE:
app.replicator.addPartition(offset)
# Then, the database.
app.dm.changePartitionTable(ptid, cell_list)
logging.debug('Partition table updated:')
self.app.pt.log()
def handleLockInformation(self, conn, packet, tid):
app = self.app
try:
t = app.transaction_dict[tid]
object_list = t.getObjectList()
for o in object_list:
app.load_lock_dict[o[0]] = tid
app.dm.storeTransaction(tid, object_list, t.getTransaction())
except KeyError:
pass
conn.answer(protocol.notifyInformationLocked(tid), packet)
def handleUnlockInformation(self, conn, packet, tid):
app = self.app
try:
t = app.transaction_dict[tid]
object_list = t.getObjectList()
for o in object_list:
oid = o[0]
del app.load_lock_dict[oid]
del app.store_lock_dict[oid]
app.dm.finishTransaction(tid)
del app.transaction_dict[tid]
# Now it may be possible to execute some events.
app.executeQueuedEvents()
except KeyError:
pass
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/replication.py 0000664 0000000 0000000 00000013415 11227104631 0026355 0 ustar 00root root 0000000 0000000
#
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.storage.handlers import BaseStorageHandler
from neo import protocol
class ReplicationHandler(BaseStorageHandler):
"""This class handles events for replications."""
def connectionCompleted(self, conn):
# Nothing to do.
pass
def connectionFailed(self, conn):
logging.error('replication is stopped due to connection failure')
self.app.replicator.reset()
def timeoutExpired(self, conn):
logging.error('replication is stopped due to timeout')
self.app.replicator.reset()
def connectionClosed(self, conn):
logging.error('replication is stopped due to close')
self.app.replicator.reset()
def peerBroken(self, conn):
logging.error('replication is stopped due to breakage')
self.app.replicator.reset()
def handleAcceptNodeIdentification(self, conn, packet, node_type,
uuid, ip_address, port,
num_partitions, num_replicas, your_uuid):
# Nothing to do.
pass
def handleAnswerTIDs(self, conn, packet, tid_list):
app = self.app
if app.replicator.current_connection is not conn:
return
if tid_list:
# If I have pending TIDs, check which TIDs I don't have, and
# request the data.
present_tid_list = app.dm.getTIDListPresent(tid_list)
tid_set = set(tid_list) - set(present_tid_list)
for tid in tid_set:
conn.ask(protocol.askTransactionInformation(tid), timeout=300)
# And, ask more TIDs.
app.replicator.tid_offset += 1000
offset = app.replicator.tid_offset
p = protocol.askTIDs(offset, offset + 1000,
app.replicator.current_partition.getRID())
conn.ask(p, timeout=300)
else:
# If no more TID, a replication of transactions is finished.
# So start to replicate objects now.
p = protocol.askOIDs(0, 1000,
app.replicator.current_partition.getRID())
conn.ask(p, timeout=300)
app.replicator.oid_offset = 0
def handleAnswerTransactionInformation(self, conn, packet, tid,
user, desc, ext, oid_list):
app = self.app
if app.replicator.current_connection is not conn:
return
# Directly store the transaction.
app.dm.storeTransaction(tid, (), (oid_list, user, desc, ext), False)
def handleAnswerOIDs(self, conn, packet, oid_list):
app = self.app
if app.replicator.current_connection is not conn:
return
if oid_list:
# Pick one up, and ask the history.
oid = oid_list.pop()
conn.ask(protocol.askObjectHistory(oid, 0, 1000), timeout=300)
app.replicator.serial_offset = 0
app.replicator.oid_list = oid_list
else:
# Nothing remains, so the replication for this partition is
# finished.
app.replicator.replication_done = True
def handleAnswerObjectHistory(self, conn, packet, oid, history_list):
app = self.app
if app.replicator.current_connection is not conn:
return
if history_list:
# Check if I have objects, request those which I don't have.
serial_list = [t[0] for t in history_list]
present_serial_list = app.dm.getSerialListPresent(oid, serial_list)
serial_set = set(serial_list) - set(present_serial_list)
for serial in serial_set:
conn.ask(protocol.askObject(oid, serial, protocol.INVALID_TID), timeout=300)
# And, ask more serials.
app.replicator.serial_offset += 1000
offset = app.replicator.serial_offset
p = protocol.askObjectHistory(oid, offset, offset + 1000)
conn.ask(p, timeout=300)
else:
# This OID is finished. So advance to next.
oid_list = app.replicator.oid_list
if oid_list:
# If I have more pending OIDs, pick one up.
oid = oid_list.pop()
conn.ask(protocol.askObjectHistory(oid, 0, 1000), timeout=300)
app.replicator.serial_offset = 0
else:
# Otherwise, acquire more OIDs.
app.replicator.oid_offset += 1000
offset = app.replicator.oid_offset
p = protocol.askOIDs(offset, offset + 1000,
app.replicator.current_partition.getRID())
conn.ask(p, timeout=300)
def handleAnswerObject(self, conn, packet, oid, serial_start,
serial_end, compression, checksum, data):
app = self.app
if app.replicator.current_connection is not conn:
return
# Directly store the transaction.
obj = (oid, compression, checksum, data)
app.dm.storeTransaction(serial_start, [obj], None, False)
del obj
del data
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/storage.py 0000664 0000000 0000000 00000004406 11227104631 0025510 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo import protocol
from neo.storage.handlers import BaseClientAndStorageOperationHandler
class StorageOperationHandler(BaseClientAndStorageOperationHandler):
def connectionCompleted(self, conn):
BaseClientAndStorageOperationHandler.connectionCompleted(self, conn)
def handleAskLastIDs(self, conn, packet):
app = self.app
oid = app.dm.getLastOID() or protocol.INVALID_OID
tid = app.dm.getLastTID() or protocol.INVALID_TID
p = protocol.answerLastIDs(oid, tid, app.ptid)
conn.answer(p, packet)
def handleAskOIDs(self, conn, packet, first, last, partition):
# This method is complicated, because I must return OIDs only
# about usable partitions assigned to me.
if first >= last:
raise protocol.ProtocolError('invalid offsets')
app = self.app
if partition == protocol.INVALID_PARTITION:
# Collect all usable partitions for me.
getCellList = app.pt.getCellList
partition_list = []
for offset in xrange(app.pt.getPartitions()):
for cell in getCellList(offset, readable=True):
if cell.getUUID() == app.uuid:
partition_list.append(offset)
break
else:
partition_list = [partition]
oid_list = app.dm.getOIDList(first, last - first,
app.pt.getPartitions(), partition_list)
conn.answer(protocol.answerOIDs(oid_list), packet)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/handlers/verification.py 0000664 0000000 0000000 00000010630 11227104631 0026522 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from neo.storage.handlers import BaseMasterHandler
from neo.protocol import INVALID_OID, INVALID_TID, TEMPORARILY_DOWN_STATE
from neo import protocol
from neo.util import dump
from neo.node import StorageNode
from neo.exception import OperationFailure
class VerificationHandler(BaseMasterHandler):
"""This class deals with events for a verification phase."""
def handleAskLastIDs(self, conn, packet):
app = self.app
oid = app.dm.getLastOID() or INVALID_OID
tid = app.dm.getLastTID() or INVALID_TID
p = protocol.answerLastIDs(oid, tid, app.ptid)
conn.answer(p, packet)
def handleAskPartitionTable(self, conn, packet, offset_list):
app, pt = self.app, self.app.pt
if not offset_list:
# all is requested
# FIXME: in one packet fow now, but later this will be replaced by
# Ask/Send/Answer pattern or a split at packet level.
offset_list = range(0, pt.getPartitions())
row_list = []
try:
for offset in offset_list:
row = []
try:
for cell in app.pt.getCellList(offset):
row.append((cell.getUUID(), cell.getState()))
except TypeError:
pass
row_list.append((offset, row))
except IndexError:
raise protocol.ProtocolError('invalid partition table offset')
p = protocol.answerPartitionTable(app.ptid, row_list)
conn.answer(p, packet)
def handleNotifyPartitionChanges(self, conn, packet, ptid, cell_list):
"""This is very similar to Send Partition Table, except that
the information is only about changes from the previous."""
app = self.app
nm = app.nm
pt = app.pt
if app.ptid >= ptid:
# Ignore this packet.
logging.debug('ignoring older partition changes')
return
# First, change the table on memory.
app.ptid = ptid
for offset, uuid, state in cell_list:
node = nm.getNodeByUUID(uuid)
if node is None:
node = StorageNode(uuid = uuid)
if uuid != app.uuid:
node.setState(TEMPORARILY_DOWN_STATE)
nm.add(node)
pt.setCell(offset, node, state)
# Then, the database.
app.dm.changePartitionTable(ptid, cell_list)
def handleStartOperation(self, conn, packet):
self.app.operational = True
def handleStopOperation(self, conn, packet):
raise OperationFailure('operation stopped')
def handleAskUnfinishedTransactions(self, conn, packet):
tid_list = self.app.dm.getUnfinishedTIDList()
p = protocol.answerUnfinishedTransactions(tid_list)
conn.answer(p, packet)
def handleAskTransactionInformation(self, conn, packet, tid):
app = self.app
t = app.dm.getTransaction(tid, all=True)
if t is None:
p = protocol.tidNotFound('%s does not exist' % dump(tid))
else:
p = protocol.answerTransactionInformation(tid, t[1], t[2], t[3], t[0])
conn.answer(p, packet)
def handleAskObjectPresent(self, conn, packet, oid, tid):
if self.app.dm.objectPresent(oid, tid):
p = protocol.answerObjectPresent(oid, tid)
else:
p = protocol.oidNotFound(
'%s:%s do not exist' % (dump(oid), dump(tid)))
conn.answer(p, packet)
def handleDeleteTransaction(self, conn, packet, tid):
self.app.dm.deleteTransaction(tid, all = True)
def handleCommitTransaction(self, conn, packet, tid):
self.app.dm.finishTransaction(tid)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/mysqldb.py 0000664 0000000 0000000 00000045345 11227104631 0023726 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import MySQLdb
from MySQLdb import OperationalError
from MySQLdb.constants.CR import SERVER_GONE_ERROR, SERVER_LOST
import logging
from array import array
import string
from struct import pack, unpack
from neo.storage.database import DatabaseManager
from neo.exception import DatabaseFailure
from neo.protocol import DISCARDED_STATE, INVALID_PTID
LOG_QUERIES = False
def p64(n):
return pack('!Q', n)
def u64(s):
return unpack('!Q', s)[0]
class MySQLDatabaseManager(DatabaseManager):
"""This class manages a database on MySQL."""
def __init__(self, **kwargs):
self.db = kwargs['database']
self.user = kwargs['user']
self.passwd = kwargs.get('password')
self.conn = None
self.connect()
super(MySQLDatabaseManager, self).__init__(**kwargs)
def close(self):
self.conn.close()
def connect(self):
kwd = {'db' : self.db, 'user' : self.user}
if self.passwd is not None:
kwd['passwd'] = self.passwd
logging.info('connecting to MySQL on the database %s with user %s',
self.db, self.user)
self.conn = MySQLdb.connect(**kwd)
self.conn.autocommit(False)
self.under_transaction = False
def begin(self):
if self.under_transaction:
try:
self.commit()
except:
# Ignore any error for this implicit commit.
pass
self.query("""BEGIN""")
self.under_transaction = True
def commit(self):
self.conn.commit()
self.under_transaction = False
def rollback(self):
self.conn.rollback()
self.under_transaction = False
def query(self, query):
"""Query data from a database."""
conn = self.conn
try:
if LOG_QUERIES:
printable_char_list = []
for c in query.split('\n', 1)[0][:70]:
if c not in string.printable or c in '\t\x0b\x0c\r':
c = '\\x%02x' % ord(c)
printable_char_list.append(c)
query_part = ''.join(printable_char_list)
logging.debug('querying %s...', query_part)
conn.query(query)
r = conn.store_result()
if r is not None:
new_r = []
for row in r.fetch_row(r.num_rows()):
new_row = []
for d in row:
if isinstance(d, array):
d = d.tostring()
new_row.append(d)
new_r.append(tuple(new_row))
r = tuple(new_r)
except OperationalError, m:
if m[0] in (SERVER_GONE_ERROR, SERVER_LOST):
logging.info('the MySQL server is gone; reconnecting')
self.connect()
return self.query(query)
raise DatabaseFailure('MySQL error %d: %s' % (m[0], m[1]))
return r
def escape(self, s):
"""Escape special characters in a string."""
return self.conn.escape_string(s)
def setup(self, reset = 0):
q = self.query
if reset:
q("""DROP TABLE IF EXISTS config, pt, trans, obj, ttrans, tobj""")
# The table "config" stores configuration parameters which affect the
# persistent data.
q("""CREATE TABLE IF NOT EXISTS config (
name VARBINARY(16) NOT NULL PRIMARY KEY,
value VARBINARY(255) NOT NULL
) ENGINE = InnoDB""")
# The table "pt" stores a partition table.
q("""CREATE TABLE IF NOT EXISTS pt (
rid INT UNSIGNED NOT NULL,
uuid BINARY(16) NOT NULL,
state TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (rid, uuid)
) ENGINE = InnoDB""")
# The table "trans" stores information on committed transactions.
q("""CREATE TABLE IF NOT EXISTS trans (
tid BIGINT UNSIGNED NOT NULL PRIMARY KEY,
oids MEDIUMBLOB NOT NULL,
user BLOB NOT NULL,
description BLOB NOT NULL,
ext BLOB NOT NULL
) ENGINE = InnoDB""")
# The table "obj" stores committed object data.
q("""CREATE TABLE IF NOT EXISTS obj (
oid BIGINT UNSIGNED NOT NULL,
serial BIGINT UNSIGNED NOT NULL,
compression TINYINT UNSIGNED NOT NULL,
checksum INT UNSIGNED NOT NULL,
value MEDIUMBLOB NOT NULL,
PRIMARY KEY (oid, serial)
) ENGINE = InnoDB""")
# The table "ttrans" stores information on uncommitted transactions.
q("""CREATE TABLE IF NOT EXISTS ttrans (
tid BIGINT UNSIGNED NOT NULL,
oids MEDIUMBLOB NOT NULL,
user BLOB NOT NULL,
description BLOB NOT NULL,
ext BLOB NOT NULL
) ENGINE = InnoDB""")
# The table "tobj" stores uncommitted object data.
q("""CREATE TABLE IF NOT EXISTS tobj (
oid BIGINT UNSIGNED NOT NULL,
serial BIGINT UNSIGNED NOT NULL,
compression TINYINT UNSIGNED NOT NULL,
checksum INT UNSIGNED NOT NULL,
value MEDIUMBLOB NOT NULL
) ENGINE = InnoDB""")
def getConfiguration(self, key):
q = self.query
e = self.escape
key = e(str(key))
r = q("""SELECT value FROM config WHERE name = '%s'""" % key)
try:
return r[0][0]
except IndexError:
return None
def setConfiguration(self, key, value):
q = self.query
e = self.escape
key = e(str(key))
value = e(str(value))
q("""REPLACE INTO config VALUES ('%s', '%s')""" % (key, value))
def getUUID(self):
return self.getConfiguration('uuid')
def setUUID(self, uuid):
self.begin()
try:
self.setConfiguration('uuid', uuid)
except:
self.rollback()
raise
self.commit()
def getNumPartitions(self):
n = self.getConfiguration('partitions')
if n is not None:
return int(n)
def setNumPartitions(self, num_partitions):
self.begin()
try:
self.setConfiguration('partitions', num_partitions)
except:
self.rollback()
raise
self.commit()
def getNumReplicas(self):
n = self.getConfiguration('replicas')
if n is not None:
return int(n)
def setNumReplicas(self, num_replicas):
self.begin()
try:
self.setConfiguration('replicas', num_replicas)
except:
self.rollback()
raise
self.commit()
def getName(self):
return self.getConfiguration('name')
def setName(self, name):
self.begin()
try:
self.setConfiguration('name', name)
except:
self.rollback()
raise
self.commit()
def getPTID(self):
ptid = self.getConfiguration('ptid')
if ptid is None:
return INVALID_PTID
return ptid
def setPTID(self, ptid):
self.begin()
try:
self.setConfiguration('ptid', ptid)
except:
self.rollback()
raise
self.commit()
def getPartitionTable(self):
q = self.query
return q("""SELECT rid, uuid, state FROM pt""")
def getLastOID(self, all = True):
q = self.query
self.begin()
loid = q("""SELECT MAX(oid) FROM obj""")[0][0]
if all:
tmp_loid = q("""SELECT MAX(oid) FROM tobj""")[0][0]
if loid is None or (tmp_loid is not None and loid < tmp_loid):
loid = tmp_loid
self.commit()
if loid is not None:
loid = p64(loid)
return loid
def getLastTID(self, all = True):
# XXX this does not consider serials in obj.
# I am not sure if this is really harmful. For safety,
# check for tobj only at the moment. The reason why obj is
# not tested is that it is too slow to get the max serial
# from obj when it has a huge number of objects, because
# serial is the second part of the primary key, so the index
# is not used in this case. If doing it, it is better to
# make another index for serial, but I doubt the cost increase
# is worth.
q = self.query
self.begin()
ltid = q("""SELECT MAX(tid) FROM trans""")[0][0]
if all:
tmp_ltid = q("""SELECT MAX(tid) FROM ttrans""")[0][0]
if ltid is None or (tmp_ltid is not None and ltid < tmp_ltid):
ltid = tmp_ltid
tmp_serial = q("""SELECT MAX(serial) FROM tobj""")[0][0]
if ltid is None or (tmp_serial is not None and ltid < tmp_serial):
ltid = tmp_serial
self.commit()
if ltid is not None:
ltid = p64(ltid)
return ltid
def getUnfinishedTIDList(self):
q = self.query
tid_set = set()
self.begin()
r = q("""SELECT tid FROM ttrans""")
tid_set.update((p64(t[0]) for t in r))
r = q("""SELECT serial FROM tobj""")
self.commit()
tid_set.update((p64(t[0]) for t in r))
return list(tid_set)
def objectPresent(self, oid, tid, all = True):
q = self.query
oid = u64(oid)
tid = u64(tid)
self.begin()
r = q("""SELECT oid FROM obj WHERE oid = %d AND serial = %d""" \
% (oid, tid))
if not r and all:
r = q("""SELECT oid FROM tobj WHERE oid = %d AND serial = %d""" \
% (oid, tid))
self.commit()
if r:
return True
return False
def getObject(self, oid, tid = None, before_tid = None):
q = self.query
oid = u64(oid)
if tid is not None:
tid = u64(tid)
r = q("""SELECT serial, compression, checksum, value FROM obj
WHERE oid = %d AND serial = %d""" \
% (oid, tid))
try:
serial, compression, checksum, data = r[0]
next_serial = None
except IndexError:
return None
elif before_tid is not None:
before_tid = u64(before_tid)
r = q("""SELECT serial, compression, checksum, value FROM obj
WHERE oid = %d AND serial < %d
ORDER BY serial DESC LIMIT 1""" \
% (oid, before_tid))
try:
serial, compression, checksum, data = r[0]
r = q("""SELECT serial FROM obj
WHERE oid = %d AND serial >= %d
ORDER BY serial LIMIT 1""" \
% (oid, before_tid))
try:
next_serial = r[0][0]
except IndexError:
next_serial = None
except IndexError:
return None
else:
# XXX I want to express "HAVING serial = MAX(serial)", but
# MySQL does not use an index for a HAVING clause!
r = q("""SELECT serial, compression, checksum, value FROM obj
WHERE oid = %d ORDER BY serial DESC LIMIT 1""" \
% oid)
try:
serial, compression, checksum, data = r[0]
next_serial = None
except IndexError:
return None
if serial is not None:
serial = p64(serial)
if next_serial is not None:
next_serial = p64(next_serial)
return serial, next_serial, compression, checksum, data
def doSetPartitionTable(self, ptid, cell_list, reset):
q = self.query
e = self.escape
self.begin()
try:
if reset:
q("""TRUNCATE pt""")
for offset, uuid, state in cell_list:
uuid = e(uuid)
if state == DISCARDED_STATE:
q("""DELETE FROM pt WHERE rid = %d AND uuid = '%s'""" \
% (offset, uuid))
else:
q("""INSERT INTO pt VALUES (%d, '%s', %d)
ON DUPLICATE KEY UPDATE state = %d""" \
% (offset, uuid, state, state))
ptid = e(ptid)
q("""UPDATE config SET value = '%s' WHERE name = 'ptid'""" % ptid)
except:
self.rollback()
raise
self.commit()
def changePartitionTable(self, ptid, cell_list):
self.doSetPartitionTable(ptid, cell_list, False)
def setPartitionTable(self, ptid, cell_list):
self.doSetPartitionTable(ptid, cell_list, True)
def dropUnfinishedData(self):
q = self.query
self.begin()
try:
q("""TRUNCATE tobj""")
q("""TRUNCATE ttrans""")
except:
self.rollback()
raise
self.commit()
def storeTransaction(self, tid, object_list, transaction, temporary = True):
q = self.query
e = self.escape
tid = u64(tid)
if temporary:
obj_table = 'tobj'
trans_table = 'ttrans'
else:
obj_table = 'obj'
trans_table = 'trans'
self.begin()
try:
# XXX it might be more efficient to insert multiple objects
# at a time, but it is potentially dangerous, because
# a packet to MySQL can exceed the maximum packet size.
# However, I do not think this would be a big problem, because
# tobj has no index, so inserting one by one should not be
# significantly different from inserting many at a time.
for oid, compression, checksum, data in object_list:
oid = u64(oid)
data = e(data)
q("""REPLACE INTO %s VALUES (%d, %d, %d, %d, '%s')""" \
% (obj_table, oid, tid, compression, checksum, data))
if transaction is not None:
oid_list, user, desc, ext = transaction
oids = e(''.join(oid_list))
user = e(user)
desc = e(desc)
ext = e(ext)
q("""REPLACE INTO %s VALUES (%d, '%s', '%s', '%s', '%s')""" \
% (trans_table, tid, oids, user, desc, ext))
except:
self.rollback()
raise
self.commit()
def finishTransaction(self, tid):
q = self.query
tid = u64(tid)
self.begin()
try:
q("""INSERT INTO obj SELECT * FROM tobj WHERE tobj.serial = %d""" \
% tid)
q("""DELETE FROM tobj WHERE serial = %d""" % tid)
q("""INSERT INTO trans SELECT * FROM ttrans WHERE ttrans.tid = %d""" \
% tid)
q("""DELETE FROM ttrans WHERE tid = %d""" % tid)
except:
self.rollback()
raise
self.commit()
def deleteTransaction(self, tid, all = False):
q = self.query
tid = u64(tid)
self.begin()
try:
q("""DELETE FROM tobj WHERE serial = %d""" % tid)
q("""DELETE FROM ttrans WHERE tid = %d""" % tid)
if all:
# Note that this can be very slow.
q("""DELETE FROM obj WHERE serial = %d""" % tid)
q("""DELETE FROM trans WHERE tid = %d""" % tid)
except:
self.rollback()
raise
self.commit()
def getTransaction(self, tid, all = False):
q = self.query
tid = u64(tid)
self.begin()
r = q("""SELECT oids, user, description, ext FROM trans
WHERE tid = %d""" \
% tid)
if not r and all:
r = q("""SELECT oids, user, description, ext FROM ttrans
WHERE tid = %d""" \
% tid)
self.commit()
if r:
oids, user, desc, ext = r[0]
if (len(oids) % 8) != 0 or len(oids) == 0:
raise DatabaseFailure('invalid oids for tid %x' % tid)
oid_list = []
for i in xrange(0, len(oids), 8):
oid_list.append(oids[i:i+8])
return oid_list, user, desc, ext
return None
def getOIDList(self, offset, length, num_partitions, partition_list):
q = self.query
r = q("""SELECT DISTINCT oid FROM obj WHERE MOD(oid,%d) in (%s)
ORDER BY oid DESC LIMIT %d,%d""" \
% (num_partitions,
','.join([str(p) for p in partition_list]),
offset, length))
return [p64(t[0]) for t in r]
def getObjectHistory(self, oid, offset = 0, length = 1):
q = self.query
oid = u64(oid)
r = q("""SELECT serial, LENGTH(value) FROM obj WHERE oid = %d
ORDER BY serial DESC LIMIT %d,%d""" \
% (oid, offset, length))
if r:
return [(p64(serial), length) for serial, length in r]
return None
def getTIDList(self, offset, length, num_partitions, partition_list):
q = self.query
r = q("""SELECT tid FROM trans WHERE MOD(tid,%d) in (%s)
ORDER BY tid DESC LIMIT %d,%d""" \
% (num_partitions,
','.join([str(p) for p in partition_list]),
offset, length))
return [p64(t[0]) for t in r]
def getTIDListPresent(self, tid_list):
q = self.query
r = q("""SELECT tid FROM trans WHERE tid in (%s)""" \
% ','.join([str(u64(tid)) for tid in tid_list]))
return [p64(t[0]) for t in r]
def getSerialListPresent(self, oid, serial_list):
q = self.query
oid = u64(oid)
r = q("""SELECT serial FROM obj WHERE oid = %d AND serial in (%s)""" \
% (oid, ','.join([str(u64(serial)) for serial in serial_list])))
return [p64(t[0]) for t in r]
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/replicator.py 0000664 0000000 0000000 00000023224 11227104631 0024407 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2006-2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import logging
from random import choice
from neo.storage import handlers
from neo import protocol
from neo.protocol import STORAGE_NODE_TYPE, UP_TO_DATE_STATE, \
OUT_OF_DATE_STATE, INVALID_TID, RUNNING_STATE
from neo.connection import ClientConnection
from neo.util import dump
class Partition(object):
"""This class abstracts the state of a partition."""
def __init__(self, rid):
self.rid = rid
self.tid = None
def getRID(self):
return self.rid
def getCriticalTID(self):
return self.tid
def setCriticalTID(self, tid):
self.tid = tid
def safe(self, pending_tid_list):
if self.tid is None:
return False
for tid in pending_tid_list:
if self.tid >= tid:
return False
return True
class Replicator(object):
"""This class handles replications of objects and transactions.
Assumptions:
- Client nodes recognize partition changes reasonably quickly.
- When an out of date partition is added, next transaction ID
is given after the change is notified and serialized.
Procedures:
- Get the last TID right after a partition is added. This TID
is called a "critical TID", because this and TIDs before this
may not be present in this storage node yet. After a critical
TID, all transactions must exist in this storage node.
- Check if a primary master node still has pending transactions
before and at a critical TID. If so, I must wait for them to be
committed or aborted.
- In order to copy data, first get the list of TIDs. This is done
part by part, because the list can be very huge. When getting
a part of the list, I verify if they are in my database, and
ask data only for non-existing TIDs. This is performed until
the check reaches a critical TID.
- Next, get the list of OIDs. And, for each OID, ask the history,
namely, a list of serials. This is also done part by part, and
I ask only non-existing data. """
def __init__(self, app):
self.app = app
self.new_partition_dict = self._getOutdatedPartitionList()
self.partition_dict = {}
self.current_partition = None
self.current_connection = None
self.critical_tid_dict = {}
self.waiting_for_unfinished_tids = False
self.unfinished_tid_list = None
self.replication_done = True
self.primary_master_connection = app.master_conn
def reset(self):
"""Reset attributes to restart replicating."""
self.current_partition = None
self.current_connection = None
self.waiting_for_unfinished_tids = False
self.unfinished_tid_list = None
self.replication_done = True
def _getOutdatedPartitionList(self):
app = self.app
partition_dict = {}
for offset in xrange(app.pt.getPartitions()):
for uuid, state in app.pt.getRow(offset):
if uuid == app.uuid and state == OUT_OF_DATE_STATE:
partition_dict[offset] = Partition(offset)
return partition_dict
def pending(self):
"""Return whether there is any pending partition."""
return len(self.partition_dict) or len(self.new_partition_dict)
def setCriticalTID(self, packet, tid):
"""This is a callback from OperationEventHandler."""
msg_id = packet.getId()
try:
partition_list = self.critical_tid_dict[msg_id]
logging.debug('setting critical TID %s to %s',
dump(tid),
', '.join([str(p.getRID()) for p in partition_list]))
for partition in self.critical_tid_dict[msg_id]:
partition.setCriticalTID(tid)
del self.critical_tid_dict[msg_id]
except KeyError:
logging.debug("setCriticalTID raised KeyError for msg_id %s" %(msg_id,))
def _askCriticalTID(self):
conn = self.primary_master_connection
msg_id = conn.ask(protocol.askLastIDs())
self.critical_tid_dict[msg_id] = self.new_partition_dict.values()
self.partition_dict.update(self.new_partition_dict)
self.new_partition_dict = {}
def setUnfinishedTIDList(self, tid_list):
"""This is a callback from OperationEventHandler."""
logging.debug('setting unfinished TIDs %s',
','.join([dump(tid) for tid in tid_list]))
self.waiting_for_unfinished_tids = False
self.unfinished_tid_list = tid_list
def _askUnfinishedTIDs(self):
conn = self.primary_master_connection
conn.ask(protocol.askUnfinishedTransactions())
self.waiting_for_unfinished_tids = True
def _startReplication(self):
# Choose a storage node for the source.
app = self.app
try:
cell_list = app.pt.getCellList(self.current_partition.getRID(),
readable=True)
node_list = [cell.getNode() for cell in cell_list
if cell.getNodeState() == RUNNING_STATE]
node = choice(node_list)
except IndexError:
# Not operational.
logging.error('not operational', exc_info = 1)
self.current_partition = None
return
addr = node.getServer()
if addr is None:
logging.error("no address known for the selected node %s" %(dump(node.getUUID())))
return
if self.current_connection is not None:
if self.current_connection.getAddress() == addr:
# I can reuse the same connection.
pass
else:
self.current_connection.close()
self.current_connection = None
if self.current_connection is None:
handler = handlers.ReplicationHandler(app)
self.current_connection = ClientConnection(app.em, handler,
addr = addr,
connector_handler = app.connector_handler)
p = protocol.requestNodeIdentification(STORAGE_NODE_TYPE, app.uuid,
app.server[0], app.server[1], app.name)
self.current_connection.ask(p)
self.tid_offset = 0
p = protocol.askTIDs(0, 1000, self.current_partition.getRID())
self.current_connection.ask(p, timeout=300)
self.replication_done = False
def _finishReplication(self):
app = self.app
try:
self.partition_dict.pop(self.current_partition.getRID())
# Notify to a primary master node that my cell is now up-to-date.
conn = self.primary_master_connection
p = protocol.notifyPartitionChanges( app.ptid,
[(self.current_partition.getRID(), app.uuid, UP_TO_DATE_STATE)])
conn.notify(p)
except KeyError:
pass
self.current_partition = None
def act(self):
# If the new partition list is not empty, I must ask a critical
# TID to a primary master node.
if self.new_partition_dict:
self._askCriticalTID()
if self.current_partition is None:
# I need to choose something.
if self.waiting_for_unfinished_tids:
# Still waiting.
logging.debug('waiting for unfinished tids')
return
elif self.unfinished_tid_list is not None:
# Try to select something.
for partition in self.partition_dict.values():
if partition.safe(self.unfinished_tid_list):
self.current_partition = partition
self.unfinished_tid_list = None
break
else:
# Not yet.
logging.debug('not ready yet')
self.unfinished_tid_list = None
return
self._startReplication()
else:
# Ask pending transactions.
logging.debug('asking unfinished tids')
self._askUnfinishedTIDs()
else:
if self.replication_done:
logging.info('replication is done for %s' %(self.current_partition.getRID(),))
self._finishReplication()
def removePartition(self, rid):
"""This is a callback from OperationEventHandler."""
try:
self.partition_dict.pop(rid)
except KeyError:
pass
try:
self.new_partition_dict.pop(rid)
except KeyError:
pass
def addPartition(self, rid):
"""This is a callback from OperationEventHandler."""
if not self.partition_dict.has_key(rid) \
and not self.new_partition_dict.has_key(rid):
self.new_partition_dict[rid] = Partition(rid)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/ 0000775 0000000 0000000 00000000000 11227104631 0023030 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/__init__.py 0000664 0000000 0000000 00000001571 11227104631 0025145 0 ustar 00root root 0000000 0000000 from neo.storage.tests.testStorageApp import StorageAppTests
from neo.storage.tests.testStorageMySQLdb import StorageMySQSLdbTests
from neo.storage.tests.testInitializationHandler import StorageInitializationHandlerTests
from neo.storage.tests.testVerificationHandler import StorageVerificationHandlerTests
from neo.storage.tests.testBootstrapHandler import StorageBootstrapHandlerTests
from neo.storage.tests.testStorageHandler import StorageStorageHandlerTests
from neo.storage.tests.testClientHandler import StorageClientHandlerTests
from neo.storage.tests.testMasterHandler import StorageMasterHandlerTests
__all__ = [
'StorageAppTests',
'StorageBootstrapTests',
'StorageMySQSLdbTests',
'StorageInitializationHandlerTests',
'StorageClientHandlerTests',
'StorageMasterHandlerTests',
'StorageStorageHandlerTests',
'StorageVerificationHandlerTests',
]
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testBootstrapHandler.py 0000664 0000000 0000000 00000042277 11227104631 0027571 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from neo.tests.base import NeoTestBase
from neo.master.app import MasterNode
from neo.pt import PartitionTable
from neo.storage.app import Application, StorageNode
from neo.storage.handlers import BootstrapHandler
from neo.storage.handlers import VerificationHandler
from neo import protocol
from neo.protocol import STORAGE_NODE_TYPE, MASTER_NODE_TYPE
from neo.protocol import BROKEN_STATE, RUNNING_STATE, Packet, INVALID_UUID
from neo.protocol import ACCEPT_NODE_IDENTIFICATION, REQUEST_NODE_IDENTIFICATION
from neo.protocol import ERROR, BROKEN_NODE_DISALLOWED_CODE, ASK_PRIMARY_MASTER
from neo.protocol import ANSWER_PRIMARY_MASTER
class StorageBootstrapHandlerTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile()
self.app = Application(config, "master1")
for server in self.app.master_node_list:
self.app.nm.add(MasterNode(server=server))
self.trying_master_node = self.app.nm.getMasterNodeList()[0]
self.bootstrap = BootstrapHandler(self.app)
# define some variable to simulate client and storage node
self.master_port = 10010
self.storage_port = 10020
self.num_partitions = 1009
self.num_replicas = 2
def tearDown(self):
NeoTestBase.tearDown(self)
# Common methods
def getLastUUID(self):
return self.uuid
# Tests
def test_01_connectionCompleted(self):
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
# request identification
self.app.trying_master_node = self.trying_master_node
self.bootstrap.connectionCompleted(conn)
self.checkAskPrimaryMaster(conn)
def test_02_connectionFailed(self):
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port)})
# the primary is dead
self.app.trying_master_node = self.trying_master_node
self.app.primary_master_node = self.app.trying_master_node
self.bootstrap.connectionFailed(conn)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
# a master is dead
self.app.trying_master_node = self.trying_master_node
master_node = MasterNode()
self.app.primary_master_node = master_node
self.bootstrap.connectionFailed(conn)
self.assertEquals(self.app.primary_master_node, master_node)
self.assertEquals(self.app.trying_master_node, None)
def test_03_connectionAccepted(self):
# no packet sent
uuid = self.getNewUUID()
em = Mock({ 'register': None})
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.master_port),
"getHandler" : self.bootstrap,
"getEventManager": em
})
connector = Mock({ })
addr = ("127.0.0.1", self.master_port)
self.bootstrap.connectionAccepted(conn, connector, addr)
self.assertEquals(len(connector.mockGetNamedCalls('getEventManager')), 0)
self.checkNoPacketSent(conn)
def test_04_timeoutExpired(self):
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
# pmn connection has expired
self.app.trying_master_node = self.trying_master_node
self.app.trying_master_node = self.trying_master_node
self.bootstrap.timeoutExpired(conn)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
# another master connection as expired
self.app.trying_master_node = self.trying_master_node
master_node = MasterNode()
self.app.primary_master_node = master_node
self.bootstrap.connectionFailed(conn)
self.assertEquals(self.app.primary_master_node, master_node)
self.assertEquals(self.app.trying_master_node, None)
self.checkNoPacketSent(conn)
def test_05_connectionClosed(self):
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
# pmn connection is closed
self.app.trying_master_node = self.trying_master_node
self.app.trying_master_node = self.trying_master_node
self.bootstrap.connectionClosed(conn)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
# another master node connection is closed
self.app.trying_master_node = self.trying_master_node
master_node = MasterNode()
self.app.primary_master_node = master_node
self.bootstrap.connectionClosed(conn)
self.assertEquals(self.app.primary_master_node, master_node)
self.assertEquals(self.app.trying_master_node, None)
self.checkNoPacketSent(conn)
def test_06_peerBroken(self):
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
# the primary is broken
self.app.trying_master_node = self.trying_master_node
self.app.trying_master_node = self.trying_master_node
self.bootstrap.peerBroken(conn)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
# another master is broken
self.app.trying_master_node = self.trying_master_node
master_node = MasterNode()
self.app.primary_master_node = master_node
self.bootstrap.peerBroken(conn)
self.assertEquals(self.app.primary_master_node, master_node)
self.assertEquals(self.app.trying_master_node, None)
self.checkNoPacketSent(conn)
def test_07_handleNotReady(self):
# the primary is not ready
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
self.app.trying_master_node = self.trying_master_node
self.app.trying_master_node = self.trying_master_node
self.bootstrap.handleNotReady(conn, None, None)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
self.checkClosed(conn)
# another master is not ready
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
self.app.trying_master_node = self.trying_master_node
master_node = MasterNode()
self.app.primary_master_node = master_node
self.bootstrap.handleNotReady(conn, None, None)
self.assertEquals(self.app.primary_master_node, master_node)
self.assertEquals(self.app.trying_master_node, None)
self.checkClosed(conn)
self.checkNoPacketSent(conn)
def test_09_handleAcceptNodeIdentification2(self):
# not a master node -> rejected
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.storage_port), })
self.app.trying_master_node = self.trying_master_node
packet = Packet(msg_type=ACCEPT_NODE_IDENTIFICATION)
# non-master node to be removed
server = ('127.0.0.1', self.storage_port)
self.app.nm.add((StorageNode(server=server)))
self.assertTrue(server in self.app.nm.server_dict)
self.bootstrap.handleAcceptNodeIdentification(
conn=conn,
uuid=self.getNewUUID(),
packet=packet,
port=self.storage_port,
node_type=STORAGE_NODE_TYPE,
ip_address='127.0.0.1',
num_partitions=self.num_partitions,
num_replicas=self.app.num_replicas,
your_uuid=self.getNewUUID())
self.assertTrue(server not in self.app.nm.server_dict)
self.checkClosed(conn)
def test_09_handleAcceptNodeIdentification3(self):
# bad address -> rejected
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
self.app.trying_master_node = self.trying_master_node
packet = Packet(msg_type=ACCEPT_NODE_IDENTIFICATION)
self.bootstrap.handleAcceptNodeIdentification(
conn=conn,
uuid=self.getNewUUID(),
packet=packet,
port=self.storage_port,
node_type=MASTER_NODE_TYPE,
ip_address='127.0.0.1',
num_partitions=self.num_partitions,
num_replicas=self.app.num_replicas,
your_uuid=self.getNewUUID())
server = ('127.0.0.1', self.master_port)
self.assertTrue(server not in self.app.nm.server_dict)
self.checkClosed(conn)
def test_09_handleAcceptNodeIdentification4(self):
# bad number of replicas/partitions
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
self.app.trying_master_node = self.trying_master_node
packet = Packet(msg_type=ACCEPT_NODE_IDENTIFICATION)
uuid = self.getNewUUID()
args = {
'conn':conn,
'uuid':uuid,
'packet':packet,
'port':self.master_port,
'node_type':MASTER_NODE_TYPE,
'ip_address':'127.0.0.1',
'your_uuid': self.getNewUUID()
}
self.app.num_partitions = 1
self.app.num_replicas = 1
# partition number as changed -> error
self.assertRaises(
RuntimeError,
self.bootstrap.handleAcceptNodeIdentification,
num_partitions=self.app.num_partitions + 2,
num_replicas=self.app.num_replicas,
**args)
self.checkNoUUIDSet(conn)
self.checkNoPacketSent(conn)
def test_09_handleAcceptNodeIdentification5(self):
# no errors
uuid, your_uuid = self.getNewUUID(), self.getNewUUID()
self.assertNotEquals(uuid, your_uuid)
self.app.num_partitions = None # will create a partition table
self.app.num_replicas = None
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
self.app.trying_master_node = self.trying_master_node
self.assertNotEquals(self.app.trying_master_node.getUUID(), uuid)
self.assertEqual(self.app.dm.getNumPartitions(), self.num_partitions)
packet = Packet(msg_type=ACCEPT_NODE_IDENTIFICATION)
self.bootstrap.handleAcceptNodeIdentification(
conn=conn,
uuid=uuid,
packet=packet,
port=self.master_port,
node_type=MASTER_NODE_TYPE,
ip_address='127.0.0.1',
num_partitions=self.num_partitions,
num_replicas=self.num_replicas,
your_uuid=your_uuid)
# check PT
self.assertEquals(self.app.num_partitions, self.num_partitions)
self.assertEquals(self.app.num_replicas, self.num_replicas)
self.assertEqual(self.app.num_partitions, self.app.dm.getNumPartitions())
self.assertTrue(isinstance(self.app.pt, PartitionTable))
self.assertEquals(self.app.ptid, self.app.dm.getPTID())
self.checkAskPrimaryMaster(conn)
# uuid
self.checkUUIDSet(conn, uuid)
self.assertEquals(self.app.trying_master_node.getUUID(), uuid)
self.assertEquals(self.app.uuid, self.app.dm.getUUID())
self.assertEquals(self.app.uuid, your_uuid)
def test_10_handleAnswerPrimaryMaster02(self):
# register new master nodes
existing_master = ('127.0.0.1', self.master_port, self.getNewUUID(), )
new_master = ('192.168.0.1', 10001, self.getNewUUID(), )
known_masters = (existing_master, new_master, )
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
packet = Packet(msg_type=ANSWER_PRIMARY_MASTER)
self.assertTrue(existing_master[:2] in self.app.nm.server_dict)
self.assertTrue(new_master[:2] not in self.app.nm.server_dict)
self.bootstrap.handleAnswerPrimaryMaster(
conn=conn,
packet=packet,
primary_uuid=self.getNewUUID(),
known_master_list=known_masters
)
# check server list
self.assertTrue(existing_master[:2] in self.app.nm.server_dict)
self.assertTrue(new_master[:2] in self.app.nm.server_dict)
# check new master
n = self.app.nm.getNodeByServer(new_master[:2])
self.assertTrue(isinstance(n, MasterNode))
self.assertEquals(n.getUUID(), new_master[2])
self.assertEquals(len(conn.mockGetNamedCalls('setHandler')), 0)
self.checkRequestNodeIdentification(conn)
def test_10_handleAnswerPrimaryMaster03(self):
# invalid primary master uuid -> close connection
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
packet = Packet(msg_type=ANSWER_PRIMARY_MASTER)
pmn = self.app.nm.getNodeByServer(('127.0.0.1', self.master_port))
self.app.primary_master_node = pmn
self.app.trying_master_node = pmn
self.bootstrap.handleAnswerPrimaryMaster(
conn=conn,
packet=packet,
primary_uuid=INVALID_UUID,
known_master_list=()
)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
self.assertEquals(len(conn.mockGetNamedCalls('setHandler')), 0)
self.checkNoPacketSent(conn)
self.checkClosed(conn)
def test_10_handleAnswerPrimaryMaster04(self):
# trying_master_node is not pmn -> close connection
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
packet = Packet(msg_type=ANSWER_PRIMARY_MASTER)
pmn = self.app.nm.getNodeByServer(('127.0.0.1', self.master_port))
pmn.setUUID(self.getNewUUID())
self.app.primary_master_node = None
self.app.trying_master_node = None
self.assertNotEquals(pmn.getUUID(), None)
self.bootstrap.handleAnswerPrimaryMaster(
conn=conn,
packet=packet,
primary_uuid=pmn.getUUID(),
known_master_list=()
)
self.assertEquals(self.app.primary_master_node, pmn)
self.assertEquals(len(conn.mockGetNamedCalls('setHandler')), 0)
self.assertEquals(self.app.trying_master_node, None)
self.checkNoPacketSent(conn)
self.checkClosed(conn)
def test_10_handleAnswerPrimaryMaster05(self):
# trying_master_node is pmn -> set verification handler
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
packet = Packet(msg_type=ANSWER_PRIMARY_MASTER)
pmn = self.app.nm.getNodeByServer(('127.0.0.1', self.master_port))
pmn.setUUID(self.getNewUUID())
self.app.primary_master_node = None
self.app.trying_master_node = pmn
self.assertNotEquals(pmn.getUUID(), None)
self.bootstrap.handleAnswerPrimaryMaster(
conn=conn,
packet=packet,
primary_uuid=pmn.getUUID(),
known_master_list=()
)
self.assertEquals(self.app.primary_master_node, pmn)
self.assertEquals(self.app.trying_master_node, pmn)
self.checkRequestNodeIdentification(conn)
def test_10_handleAnswerPrimaryMaster06(self):
# primary_uuid not known -> nothing happen
conn = Mock({"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port), })
packet = Packet(msg_type=ANSWER_PRIMARY_MASTER)
self.app.primary_master_node = None
self.app.trying_master_node = None
new_uuid = self.getNewUUID()
self.bootstrap.handleAnswerPrimaryMaster(
conn=conn,
packet=packet,
primary_uuid=new_uuid,
known_master_list=()
)
self.assertEquals(self.app.primary_master_node, None)
self.assertEquals(self.app.trying_master_node, None)
self.assertEquals(len(conn.mockGetNamedCalls('setHandler')), 0)
self.checkRequestNodeIdentification(conn)
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testClientHandler.py 0000664 0000000 0000000 00000032526 11227104631 0027026 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from struct import pack, unpack
from mock import Mock
from collections import deque
from neo.tests.base import NeoTestBase
from neo.master.app import MasterNode
from neo.storage.app import Application, StorageNode
from neo.storage.handlers.client import TransactionInformation
from neo.storage.handlers import ClientOperationHandler
from neo.exception import PrimaryFailure, OperationFailure
from neo.pt import PartitionTable
from neo import protocol
from neo.protocol import *
class StorageClientHandlerTests(NeoTestBase):
def checkHandleUnexpectedPacket(self, _call, _msg_type, _listening=True, **kwargs):
conn = Mock({
"getAddress" : ("127.0.0.1", self.master_port),
"isServerConnection": _listening,
})
packet = Packet(msg_type=_msg_type)
# hook
self.operation.peerBroken = lambda c: c.peerBrokendCalled()
self.checkUnexpectedPacketRaised(_call, conn=conn, packet=packet, **kwargs)
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.app.num_partitions = 1
self.app.num_replicas = 1
self.app.transaction_dict = {}
self.app.store_lock_dict = {}
self.app.load_lock_dict = {}
self.app.event_queue = deque()
for server in self.app.master_node_list:
master = MasterNode(server = server)
self.app.nm.add(master)
# handler
self.operation = ClientOperationHandler(self.app)
# set pmn
self.master_uuid = self.getNewUUID()
pmn = self.app.nm.getMasterNodeList()[0]
pmn.setUUID(self.master_uuid)
self.app.primary_master_node = pmn
self.master_port = 10010
def tearDown(self):
NeoTestBase.tearDown(self)
def test_01_TransactionInformation(self):
uuid = self.getNewUUID()
transaction = TransactionInformation(uuid)
# uuid
self.assertEquals(transaction._uuid, uuid)
self.assertEquals(transaction.getUUID(), uuid)
# objects
self.assertEquals(transaction._object_dict, {})
object = (self.getNewUUID(), 1, 2, 3, )
transaction.addObject(*object)
objects = transaction.getObjectList()
self.assertEquals(len(objects), 1)
self.assertEquals(objects[0], object)
# transactions
self.assertEquals(transaction._transaction, None)
t = ((1, 2, 3), 'user', 'desc', '')
transaction.addTransaction(*t)
self.assertEquals(transaction.getTransaction(), t)
def test_05_dealWithClientFailure(self):
# check if client's transaction are cleaned
uuid = self.getNewUUID()
from neo.node import ClientNode
client = ClientNode(('127.0.0.1', 10010))
client.setUUID(uuid)
self.app.nm.add(client)
self.app.store_lock_dict[0] = object()
transaction = Mock({
'getUUID': uuid,
'getObjectList': ((0, ), ),
})
self.app.transaction_dict[0] = transaction
self.assertTrue(1 not in self.app.store_lock_dict)
self.assertTrue(1 not in self.app.transaction_dict)
self.operation.dealWithClientFailure(uuid)
# objects and transaction removed
self.assertTrue(0 not in self.app.store_lock_dict)
self.assertTrue(0 not in self.app.transaction_dict)
def test_18_handleAskTransactionInformation1(self):
# transaction does not exists
conn = Mock({ })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.operation.handleAskTransactionInformation(conn, packet, INVALID_TID)
self.checkErrorPacket(conn)
def test_18_handleAskTransactionInformation2(self):
# answer
conn = Mock({ })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
dm = Mock({ "getTransaction": (INVALID_TID, 'user', 'desc', '', ), })
self.app.dm = dm
self.operation.handleAskTransactionInformation(conn, packet, INVALID_TID)
self.checkAnswerTransactionInformation(conn)
def test_24_handleAskObject1(self):
# delayed response
conn = Mock({})
self.app.dm = Mock()
packet = Packet(msg_type=ASK_OBJECT)
self.app.load_lock_dict[INVALID_OID] = object()
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
self.assertEquals(len(self.app.event_queue), 1)
self.checkNoPacketSent(conn)
self.assertEquals(len(self.app.dm.mockGetNamedCalls('getObject')), 0)
def test_24_handleAskObject2(self):
# invalid serial / tid / packet not found
self.app.dm = Mock({'getObject': None})
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT)
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
calls = self.app.dm.mockGetNamedCalls('getObject')
self.assertEquals(len(self.app.event_queue), 0)
self.assertEquals(len(calls), 1)
calls[0].checkArgs(INVALID_OID, None, None)
self.checkErrorPacket(conn)
def test_24_handleAskObject3(self):
# object found => answer
self.app.dm = Mock({'getObject': ('', '', 0, 0, '', )})
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT)
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
self.assertEquals(len(self.app.event_queue), 0)
self.checkAnswerObject(conn)
def test_25_handleAskTIDs1(self):
# invalid offsets => error
app = self.app
app.pt = Mock()
app.dm = Mock()
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.checkProtocolErrorRaised(self.operation.handleAskTIDs, conn, packet, 1, 1, None)
self.assertEquals(len(app.pt.mockGetNamedCalls('getCellList')), 0)
self.assertEquals(len(app.dm.mockGetNamedCalls('getTIDList')), 0)
def test_25_handleAskTIDs2(self):
# well case => answer
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.app.num_partitions = 1
self.app.dm = Mock({'getTIDList': (INVALID_TID, )})
self.operation.handleAskTIDs(conn, packet, 1, 2, 1)
calls = self.app.dm.mockGetNamedCalls('getTIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [1, ])
self.checkAnswerTids(conn)
def test_25_handleAskTIDs3(self):
# invalid partition => answer usable partitions
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.app.num_partitions = 1
cell = Mock({'getUUID':self.app.uuid})
self.app.dm = Mock({'getTIDList': (INVALID_TID, )})
self.app.pt = Mock({'getCellList': (cell, )})
self.operation.handleAskTIDs(conn, packet, 1, 2, INVALID_PARTITION)
self.assertEquals(len(self.app.pt.mockGetNamedCalls('getCellList')), 1)
calls = self.app.dm.mockGetNamedCalls('getTIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [0, ])
self.checkAnswerTids(conn)
def test_26_handleAskObjectHistory1(self):
# invalid offsets => error
app = self.app
app.dm = Mock()
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT_HISTORY)
self.checkProtocolErrorRaised(self.operation.handleAskObjectHistory, conn, packet, 1, 1, None)
self.assertEquals(len(app.dm.mockGetNamedCalls('getObjectHistory')), 0)
def test_26_handleAskObjectHistory2(self):
# first case: empty history
packet = Packet(msg_type=ASK_OBJECT_HISTORY)
conn = Mock({})
self.app.dm = Mock({'getObjectHistory': None})
self.operation.handleAskObjectHistory(conn, packet, INVALID_OID, 1, 2)
self.checkAnswerObjectHistory(conn)
# second case: not empty history
conn = Mock({})
self.app.dm = Mock({'getObjectHistory': [('', 0, ), ]})
self.operation.handleAskObjectHistory(conn, packet, INVALID_OID, 1, 2)
self.checkAnswerObjectHistory(conn)
def test_27_handleAskStoreTransaction2(self):
# add transaction entry
packet = Packet(msg_type=ASK_STORE_TRANSACTION)
conn = Mock({'getUUID': self.getNewUUID()})
self.operation.handleAskStoreTransaction(conn, packet,
INVALID_TID, '', '', '', ())
t = self.app.transaction_dict.get(INVALID_TID, None)
self.assertNotEquals(t, None)
self.assertTrue(isinstance(t, TransactionInformation))
self.assertEquals(t.getTransaction(), ((), '', '', ''))
self.checkAnswerStoreTransaction(conn)
def test_28_handleAskStoreObject2(self):
# locked => delayed response
packet = Packet(msg_type=ASK_STORE_OBJECT)
conn = Mock({'getUUID': self.app.uuid})
oid = '\x02' * 8
tid1, tid2 = self.getTwoIDs()
self.app.store_lock_dict[oid] = tid1
self.assertTrue(oid in self.app.store_lock_dict)
t_before = self.app.transaction_dict.items()[:]
self.operation.handleAskStoreObject(conn, packet, oid,
INVALID_SERIAL, 0, 0, '', tid2)
self.assertEquals(len(self.app.event_queue), 1)
t_after = self.app.transaction_dict.items()[:]
self.assertEquals(t_before, t_after)
self.checkNoPacketSent(conn)
self.assertTrue(oid in self.app.store_lock_dict)
def test_28_handleAskStoreObject3(self):
# locked => unresolvable conflict => answer
packet = Packet(msg_type=ASK_STORE_OBJECT)
conn = Mock({'getUUID': self.app.uuid})
tid1, tid2 = self.getTwoIDs()
self.app.store_lock_dict[INVALID_OID] = tid2
self.operation.handleAskStoreObject(conn, packet, INVALID_OID,
INVALID_SERIAL, 0, 0, '', tid1)
self.checkAnswerStoreObject(conn)
self.assertEquals(self.app.store_lock_dict[INVALID_OID], tid2)
# conflicting
packet = conn.mockGetNamedCalls('answer')[0].getParam(0)
self.assertTrue(unpack('!B8s8s', packet._body)[0])
def test_28_handleAskStoreObject4(self):
# resolvable conflict => answer
packet = Packet(msg_type=ASK_STORE_OBJECT)
conn = Mock({'getUUID': self.app.uuid})
self.app.dm = Mock({'getObjectHistory':((self.getNewUUID(), ), )})
self.assertEquals(self.app.store_lock_dict.get(INVALID_OID, None), None)
self.operation.handleAskStoreObject(conn, packet, INVALID_OID,
INVALID_SERIAL, 0, 0, '', INVALID_TID)
self.checkAnswerStoreObject(conn)
self.assertEquals(self.app.store_lock_dict.get(INVALID_OID, None), None)
# conflicting
packet = conn.mockGetNamedCalls('answer')[0].getParam(0)
self.assertTrue(unpack('!B8s8s', packet._body)[0])
def test_28_handleAskStoreObject5(self):
# no conflict => answer
packet = Packet(msg_type=ASK_STORE_OBJECT)
conn = Mock({'getUUID': self.app.uuid})
self.operation.handleAskStoreObject(conn, packet, INVALID_OID,
INVALID_SERIAL, 0, 0, '', INVALID_TID)
t = self.app.transaction_dict.get(INVALID_TID, None)
self.assertNotEquals(t, None)
self.assertEquals(len(t.getObjectList()), 1)
object = t.getObjectList()[0]
self.assertEquals(object, (INVALID_OID, 0, 0, ''))
# no conflict
packet = self.checkAnswerStoreObject(conn)
self.assertFalse(unpack('!B8s8s', packet._body)[0])
def test_29_handleAbortTransaction(self):
# remove transaction
packet = Packet(msg_type=ABORT_TRANSACTION)
conn = Mock({'getUUID': self.app.uuid})
transaction = Mock({ 'getObjectList': ((0, ), ), })
self.called = False
def called():
self.called = True
self.app.executeQueuedEvents = called
self.app.load_lock_dict[0] = object()
self.app.store_lock_dict[0] = object()
self.app.transaction_dict[INVALID_TID] = transaction
self.operation.handleAbortTransaction(conn, packet, INVALID_TID)
self.assertTrue(self.called)
self.assertEquals(len(self.app.load_lock_dict), 0)
self.assertEquals(len(self.app.store_lock_dict), 0)
self.assertEquals(len(self.app.store_lock_dict), 0)
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testInitializationHandler.py 0000664 0000000 0000000 00000013612 11227104631 0030572 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from neo.tests.base import NeoTestBase
from neo import protocol
from neo.node import MasterNode
from neo.pt import PartitionTable
from neo.storage.app import Application, StorageNode
from neo.storage.handlers import InitializationHandler
from neo.protocol import STORAGE_NODE_TYPE, MASTER_NODE_TYPE, CLIENT_NODE_TYPE
from neo.protocol import BROKEN_STATE, RUNNING_STATE, Packet, INVALID_UUID, \
UP_TO_DATE_STATE, INVALID_OID, INVALID_TID, PROTOCOL_ERROR_CODE
from neo.protocol import ACCEPT_NODE_IDENTIFICATION, REQUEST_NODE_IDENTIFICATION, \
NOTIFY_PARTITION_CHANGES, STOP_OPERATION, ASK_LAST_IDS, ASK_PARTITION_TABLE, \
ANSWER_OBJECT_PRESENT, ASK_OBJECT_PRESENT, OID_NOT_FOUND_CODE, LOCK_INFORMATION, \
UNLOCK_INFORMATION, TID_NOT_FOUND_CODE, ASK_TRANSACTION_INFORMATION, \
COMMIT_TRANSACTION, ASK_UNFINISHED_TRANSACTIONS, SEND_PARTITION_TABLE
from neo.protocol import ERROR, BROKEN_NODE_DISALLOWED_CODE, ASK_PRIMARY_MASTER
from neo.protocol import ANSWER_PRIMARY_MASTER
from neo.exception import PrimaryFailure, OperationFailure
from neo.storage.mysqldb import MySQLDatabaseManager, p64, u64
class StorageInitializationHandlerTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.verification = InitializationHandler(self.app)
# define some variable to simulate client and storage node
self.master_port = 10010
self.storage_port = 10020
self.client_port = 11011
self.num_partitions = 1009
self.num_replicas = 2
self.app.num_partitions = 1009
self.app.num_replicas = 2
self.app.operational = False
self.app.load_lock_dict = {}
self.app.pt = PartitionTable(self.app.num_partitions, self.app.num_replicas)
def tearDown(self):
NeoTestBase.tearDown(self)
# Common methods
def getLastUUID(self):
return self.uuid
def test_02_timeoutExpired(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.timeoutExpired, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_03_connectionClosed(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.connectionClosed, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_04_peerBroken(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.peerBroken, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_09_handleSendPartitionTable(self):
packet = Packet(msg_type=SEND_PARTITION_TABLE)
uuid = self.getNewUUID()
# send a table
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.app.pt = PartitionTable(3, 2)
node_1 = self.getNewUUID()
node_2 = self.getNewUUID()
node_3 = self.getNewUUID()
# SN already known one of the node
self.app.nm.add(StorageNode(uuid=node_1))
self.app.ptid = 1
self.app.num_partitions = 3
self.app.num_replicas =2
self.assertEqual(self.app.dm.getPartitionTable(), ())
row_list = [(0, ((node_1, UP_TO_DATE_STATE), (node_2, UP_TO_DATE_STATE))),
(1, ((node_3, UP_TO_DATE_STATE), (node_1, UP_TO_DATE_STATE))),
(2, ((node_2, UP_TO_DATE_STATE), (node_3, UP_TO_DATE_STATE)))]
self.assertFalse(self.app.pt.filled())
# send part of the table, won't be filled
self.verification.handleSendPartitionTable(conn, packet, "1", row_list[:1])
self.assertFalse(self.app.pt.filled())
self.assertEqual(self.app.ptid, "1")
self.assertEqual(self.app.dm.getPartitionTable(), ())
# send remaining of the table
self.verification.handleSendPartitionTable(conn, packet, "1", row_list[1:])
self.assertTrue(self.app.pt.filled())
self.assertEqual(self.app.ptid, "1")
self.assertNotEqual(self.app.dm.getPartitionTable(), ())
# send a complete new table
self.verification.handleSendPartitionTable(conn, packet, "2", row_list)
self.assertTrue(self.app.pt.filled())
self.assertEqual(self.app.ptid, "2")
self.assertNotEqual(self.app.dm.getPartitionTable(), ())
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testMasterHandler.py 0000664 0000000 0000000 00000023324 11227104631 0027037 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from struct import pack, unpack
from mock import Mock
from collections import deque
from neo.tests.base import NeoTestBase
from neo.master.app import MasterNode
from neo.storage.app import Application, StorageNode
from neo.storage.handlers import MasterOperationHandler
from neo.exception import PrimaryFailure, OperationFailure
from neo.pt import PartitionTable
from neo import protocol
from neo.protocol import *
class StorageMasterHandlerTests(NeoTestBase):
def checkHandleUnexpectedPacket(self, _call, _msg_type, _listening=True, **kwargs):
conn = Mock({
"getAddress" : ("127.0.0.1", self.master_port),
"isServerConnection": _listening,
})
packet = Packet(msg_type=_msg_type)
# hook
self.operation.peerBroken = lambda c: c.peerBrokendCalled()
self.checkUnexpectedPacketRaised(_call, conn=conn, packet=packet, **kwargs)
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.app.num_partitions = 1
self.app.num_replicas = 1
self.app.transaction_dict = {}
self.app.store_lock_dict = {}
self.app.load_lock_dict = {}
self.app.event_queue = deque()
for server in self.app.master_node_list:
master = MasterNode(server = server)
self.app.nm.add(master)
# handler
self.operation = MasterOperationHandler(self.app)
# set pmn
self.master_uuid = self.getNewUUID()
pmn = self.app.nm.getMasterNodeList()[0]
pmn.setUUID(self.master_uuid)
self.app.primary_master_node = pmn
self.master_port = 10010
def tearDown(self):
NeoTestBase.tearDown(self)
def test_06_timeoutExpired(self):
# client connection
conn = Mock({
"getUUID": self.master_uuid,
"getAddress" : ("127.0.0.1", self.master_port),
})
self.assertRaises(PrimaryFailure, self.operation.timeoutExpired, conn)
self.checkNoPacketSent(conn)
def test_07_connectionClosed2(self):
# primary has closed the connection
conn = Mock({
"getUUID": self.master_uuid,
"getAddress" : ("127.0.0.1", self.master_port),
})
self.assertRaises(PrimaryFailure, self.operation.connectionClosed, conn)
self.checkNoPacketSent(conn)
def test_08_peerBroken(self):
# client connection
conn = Mock({
"getUUID": self.master_uuid,
"getAddress" : ("127.0.0.1", self.master_port),
})
self.assertRaises(PrimaryFailure, self.operation.peerBroken, conn)
self.checkNoPacketSent(conn)
def test_14_handleNotifyPartitionChanges1(self):
# old partition change -> do nothing
app = self.app
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
app.replicator = Mock({})
packet = Packet(msg_type=NOTIFY_PARTITION_CHANGES)
self.app.ptid = 1
count = len(self.app.nm.getNodeList())
self.operation.handleNotifyPartitionChanges(conn, packet, 0, ())
self.assertEquals(self.app.ptid, 1)
self.assertEquals(len(self.app.nm.getNodeList()), count)
calls = self.app.replicator.mockGetNamedCalls('removePartition')
self.assertEquals(len(calls), 0)
calls = self.app.replicator.mockGetNamedCalls('addPartition')
self.assertEquals(len(calls), 0)
def test_14_handleNotifyPartitionChanges2(self):
# cases :
uuid = self.getNewUUID()
cells = (
(0, uuid, UP_TO_DATE_STATE),
(1, self.app.uuid, DISCARDED_STATE),
(2, self.app.uuid, OUT_OF_DATE_STATE),
)
# context
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
packet = Packet(msg_type=NOTIFY_PARTITION_CHANGES)
app = self.app
ptid1, ptid2 = self.getTwoIDs()
self.assertNotEquals(ptid1, ptid2)
app.ptid = ptid1
app.pt = PartitionTable(3, 1)
app.pt = Mock({ })
app.dm = Mock({ })
app.replicator = Mock({})
count = len(app.nm.getNodeList())
self.operation.handleNotifyPartitionChanges(conn, packet, ptid2, cells)
# ptid set
self.assertEquals(app.ptid, ptid2)
# two nodes added
self.assertEquals(len(app.nm.getNodeList()), count + 2)
# uuid != app.uuid -> TEMPORARILY_DOWN_STATE
self.assertEquals(app.nm.getNodeByUUID(uuid).getState(), TEMPORARILY_DOWN_STATE)
# pt calls
calls = self.app.pt.mockGetNamedCalls('setCell')
self.assertEquals(len(calls), 3)
calls[0].checkArgs(0, app.nm.getNodeByUUID(uuid), UP_TO_DATE_STATE)
calls[1].checkArgs(1, app.nm.getNodeByUUID(app.uuid), DISCARDED_STATE)
calls[2].checkArgs(2, app.nm.getNodeByUUID(app.uuid), OUT_OF_DATE_STATE)
# replicator calls
calls = self.app.replicator.mockGetNamedCalls('removePartition')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1)
calls = self.app.replicator.mockGetNamedCalls('addPartition')
# dm call
calls = self.app.dm.mockGetNamedCalls('changePartitionTable')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(ptid2, cells)
def test_16_handleStopOperation1(self):
# OperationFailure
conn = Mock({ 'isServerConnection': False })
packet = Packet(msg_type=STOP_OPERATION)
self.assertRaises(OperationFailure, self.operation.handleStopOperation, conn, packet)
def test_22_handleLockInformation2(self):
# load transaction informations
conn = Mock({ 'isServerConnection': False, })
self.app.dm = Mock({ })
packet = Packet(msg_type=LOCK_INFORMATION)
transaction = Mock({ 'getObjectList': ((0, ), ), })
self.app.transaction_dict[INVALID_TID] = transaction
self.operation.handleLockInformation(conn, packet, INVALID_TID)
self.assertEquals(self.app.load_lock_dict[0], INVALID_TID)
calls = self.app.dm.mockGetNamedCalls('storeTransaction')
self.assertEquals(len(calls), 1)
self.checkNotifyInformationLocked(conn, answered_packet=packet)
# transaction not in transaction_dict -> KeyError
transaction = Mock({ 'getObjectList': ((0, ), ), })
conn = Mock({ 'isServerConnection': False, })
self.operation.handleLockInformation(conn, packet, '\x01' * 8)
self.checkNotifyInformationLocked(conn, answered_packet=packet)
def test_23_handleUnlockInformation2(self):
# delete transaction informations
conn = Mock({ 'isServerConnection': False, })
self.app.dm = Mock({ })
packet = Packet(msg_type=LOCK_INFORMATION)
transaction = Mock({ 'getObjectList': ((0, ), ), })
self.app.transaction_dict[INVALID_TID] = transaction
self.app.load_lock_dict[0] = transaction
self.app.store_lock_dict[0] = transaction
self.operation.handleUnlockInformation(conn, packet, INVALID_TID)
self.assertEquals(len(self.app.load_lock_dict), 0)
self.assertEquals(len(self.app.store_lock_dict), 0)
self.assertEquals(len(self.app.store_lock_dict), 0)
calls = self.app.dm.mockGetNamedCalls('finishTransaction')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(INVALID_TID)
# transaction not in transaction_dict -> KeyError
transaction = Mock({ 'getObjectList': ((0, ), ), })
conn = Mock({ 'isServerConnection': False, })
self.operation.handleLockInformation(conn, packet, '\x01' * 8)
self.checkNotifyInformationLocked(conn, answered_packet=packet)
def test_30_handleAnswerLastIDs(self):
# set critical TID on replicator
conn = Mock()
packet = Packet(msg_type=ANSWER_LAST_IDS)
self.app.replicator = Mock()
self.operation.handleAnswerLastIDs(
conn=conn,
packet=packet,
loid=INVALID_OID,
ltid=INVALID_TID,
lptid=INVALID_TID,
)
calls = self.app.replicator.mockGetNamedCalls('setCriticalTID')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(packet, INVALID_TID)
def test_31_handleAnswerUnfinishedTransactions(self):
# set unfinished TID on replicator
conn = Mock()
packet = Packet(msg_type=ANSWER_UNFINISHED_TRANSACTIONS)
self.app.replicator = Mock()
self.operation.handleAnswerUnfinishedTransactions(
conn=conn,
packet=packet,
tid_list=(INVALID_TID, ),
)
calls = self.app.replicator.mockGetNamedCalls('setUnfinishedTIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs((INVALID_TID, ))
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testStorageApp.py 0000664 0000000 0000000 00000015353 11227104631 0026356 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, logging, os
from mock import Mock
from neo.tests.base import NeoTestBase
from neo.storage.app import Application
from neo.protocol import INVALID_PTID, INVALID_OID, INVALID_TID, \
INVALID_UUID, Packet, NOTIFY_NODE_INFORMATION, UP_TO_DATE_STATE
from neo.node import MasterNode, ClientNode, StorageNode
from neo.storage.mysqldb import p64, u64, MySQLDatabaseManager
from collections import deque
from neo.pt import PartitionTable
class StorageAppTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.WARNING)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.app.event_queue = deque()
def tearDown(self):
NeoTestBase.tearDown(self)
def getNewUUID(self):
uuid = INVALID_UUID
while uuid == INVALID_UUID:
uuid = os.urandom(16)
self.uuid = uuid
return uuid
def test_01_loadPartitionTable(self):
self.assertEqual(len(self.app.dm.getPartitionTable()), 0)
self.assertEqual(self.app.pt, None)
num_partitions = 3
num_replicas = 2
self.app.pt = PartitionTable(num_partitions, num_replicas)
self.assertEqual(self.app.pt.getNodeList(), [])
self.assertFalse(self.app.pt.filled())
for x in xrange(num_partitions):
self.assertFalse(self.app.pt.hasOffset(x))
# load an empty table
self.app.loadPartitionTable()
self.assertEqual(self.app.pt.getNodeList(), [])
self.assertFalse(self.app.pt.filled())
for x in xrange(num_partitions):
self.assertFalse(self.app.pt.hasOffset(x))
# add some node, will be remove when loading table
master_uuid = self.getNewUUID()
master = MasterNode(uuid=master_uuid)
storage_uuid = self.getNewUUID()
storage = StorageNode(uuid=storage_uuid)
client_uuid = self.getNewUUID()
client = ClientNode(uuid=client_uuid)
self.app.pt.setCell(0, master, UP_TO_DATE_STATE)
self.app.pt.setCell(0, storage, UP_TO_DATE_STATE)
self.assertEqual(len(self.app.pt.getNodeList()), 2)
self.assertFalse(self.app.pt.filled())
for x in xrange(num_partitions):
if x == 0:
self.assertTrue(self.app.pt.hasOffset(x))
else:
self.assertFalse(self.app.pt.hasOffset(x))
# load an empty table, everything removed
self.assertEqual(len(self.app.dm.getPartitionTable()), 0)
self.app.loadPartitionTable()
self.assertEqual(self.app.pt.getNodeList(), [])
self.assertFalse(self.app.pt.filled())
for x in xrange(num_partitions):
self.assertFalse(self.app.pt.hasOffset(x))
# add some node
self.app.pt.setCell(0, master, UP_TO_DATE_STATE)
self.app.pt.setCell(0, storage, UP_TO_DATE_STATE)
self.assertEqual(len(self.app.pt.getNodeList()), 2)
self.assertFalse(self.app.pt.filled())
for x in xrange(num_partitions):
if x == 0:
self.assertTrue(self.app.pt.hasOffset(x))
else:
self.assertFalse(self.app.pt.hasOffset(x))
# fill partition table
self.app.dm.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(0, client_uuid, UP_TO_DATE_STATE))
self.app.dm.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(1, client_uuid, UP_TO_DATE_STATE))
self.app.dm.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(1, storage_uuid, UP_TO_DATE_STATE))
self.app.dm.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(2, storage_uuid, UP_TO_DATE_STATE))
self.app.dm.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(2, master_uuid, UP_TO_DATE_STATE))
self.assertEqual(len(self.app.dm.getPartitionTable()), 5)
self.app.loadPartitionTable()
self.assertTrue(self.app.pt.filled())
for x in xrange(num_partitions):
self.assertTrue(self.app.pt.hasOffset(x))
# check each row
cell_list = self.app.pt.getCellList(0)
self.assertEqual(len(cell_list), 1)
self.assertEqual(cell_list[0].getUUID(), client_uuid)
cell_list = self.app.pt.getCellList(1)
self.assertEqual(len(cell_list), 2)
self.failUnless(cell_list[0].getUUID() in (client_uuid, storage_uuid))
self.failUnless(cell_list[1].getUUID() in (client_uuid, storage_uuid))
cell_list = self.app.pt.getCellList(2)
self.assertEqual(len(cell_list), 2)
self.failUnless(cell_list[0].getUUID() in (master_uuid, storage_uuid))
self.failUnless(cell_list[1].getUUID() in (master_uuid, storage_uuid))
def test_02_queueEvent(self):
self.assertEqual(len(self.app.event_queue), 0)
event = Mock({"getId": 1325136})
self.app.queueEvent(event, "test", key="value")
self.assertEqual(len(self.app.event_queue), 1)
event, args, kw = self.app.event_queue[0]
self.assertEqual(event.getId(), 1325136)
self.assertEqual(len(args), 1)
self.assertEqual(args[0], "test")
self.assertEqual(kw, {"key" : "value"})
def test_03_executeQueuedEvents(self):
self.assertEqual(len(self.app.event_queue), 0)
event = Mock({"getId": 1325136})
self.app.queueEvent(event, "test", key="value")
self.app.executeQueuedEvents()
self.assertEquals(len(event.mockGetNamedCalls("__call__")), 1)
call = event.mockGetNamedCalls("__call__")[0]
params = call.getParam(0)
self.assertEqual(params, "test")
params = call.kwparams
self.assertEqual(params, {'key': 'value'})
def test_04_getPartition(self):
self.app.num_partitions = 3
p = self.app.getPartition(p64(1))
self.assertEqual(p, 1)
p = self.app.getPartition(p64(2))
self.assertEqual(p, 2)
p = self.app.getPartition(p64(3))
self.assertEqual(p, 0)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testStorageHandler.py 0000664 0000000 0000000 00000022136 11227104631 0027210 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from struct import pack, unpack
from mock import Mock
from collections import deque
from neo.tests.base import NeoTestBase
from neo.master.app import MasterNode
from neo.storage.app import Application, StorageNode
from neo.storage.handlers import StorageOperationHandler
from neo import protocol
from neo.protocol import *
class StorageStorageHandlerTests(NeoTestBase):
def checkHandleUnexpectedPacket(self, _call, _msg_type, _listening=True, **kwargs):
conn = Mock({
"getAddress" : ("127.0.0.1", self.master_port),
"isServerConnection": _listening,
})
packet = Packet(msg_type=_msg_type)
# hook
self.operation.peerBroken = lambda c: c.peerBrokendCalled()
self.checkUnexpectedPacketRaised(_call, conn=conn, packet=packet, **kwargs)
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.app.num_partitions = 1
self.app.num_replicas = 1
self.app.transaction_dict = {}
self.app.store_lock_dict = {}
self.app.load_lock_dict = {}
self.app.event_queue = deque()
for server in self.app.master_node_list:
master = MasterNode(server = server)
self.app.nm.add(master)
# handler
self.operation = StorageOperationHandler(self.app)
# set pmn
self.master_uuid = self.getNewUUID()
pmn = self.app.nm.getMasterNodeList()[0]
pmn.setUUID(self.master_uuid)
self.app.primary_master_node = pmn
self.master_port = 10010
def tearDown(self):
NeoTestBase.tearDown(self)
def test_18_handleAskTransactionInformation1(self):
# transaction does not exists
conn = Mock({ })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.operation.handleAskTransactionInformation(conn, packet, INVALID_TID)
self.checkErrorPacket(conn)
def test_18_handleAskTransactionInformation2(self):
# answer
conn = Mock({ })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
dm = Mock({ "getTransaction": (INVALID_TID, 'user', 'desc', '', ), })
self.app.dm = dm
self.operation.handleAskTransactionInformation(conn, packet, INVALID_TID)
self.checkAnswerTransactionInformation(conn)
def test_24_handleAskObject1(self):
# delayed response
conn = Mock({})
self.app.dm = Mock()
packet = Packet(msg_type=ASK_OBJECT)
self.app.load_lock_dict[INVALID_OID] = object()
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
self.assertEquals(len(self.app.event_queue), 1)
self.checkNoPacketSent(conn)
self.assertEquals(len(self.app.dm.mockGetNamedCalls('getObject')), 0)
def test_24_handleAskObject2(self):
# invalid serial / tid / packet not found
self.app.dm = Mock({'getObject': None})
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT)
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
calls = self.app.dm.mockGetNamedCalls('getObject')
self.assertEquals(len(self.app.event_queue), 0)
self.assertEquals(len(calls), 1)
calls[0].checkArgs(INVALID_OID, None, None)
self.checkErrorPacket(conn)
def test_24_handleAskObject3(self):
# object found => answer
self.app.dm = Mock({'getObject': ('', '', 0, 0, '', )})
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT)
self.assertEquals(len(self.app.event_queue), 0)
self.operation.handleAskObject(conn, packet,
oid=INVALID_OID,
serial=INVALID_SERIAL,
tid=INVALID_TID)
self.assertEquals(len(self.app.event_queue), 0)
self.checkAnswerObject(conn)
def test_25_handleAskTIDs1(self):
# invalid offsets => error
app = self.app
app.pt = Mock()
app.dm = Mock()
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.checkProtocolErrorRaised(self.operation.handleAskTIDs, conn, packet, 1, 1, None)
self.assertEquals(len(app.pt.mockGetNamedCalls('getCellList')), 0)
self.assertEquals(len(app.dm.mockGetNamedCalls('getTIDList')), 0)
def test_25_handleAskTIDs2(self):
# well case => answer
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.app.num_partitions = 1
self.app.dm = Mock({'getTIDList': (INVALID_TID, )})
self.operation.handleAskTIDs(conn, packet, 1, 2, 1)
calls = self.app.dm.mockGetNamedCalls('getTIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [1, ])
self.checkAnswerTids(conn)
def test_25_handleAskTIDs3(self):
# invalid partition => answer usable partitions
conn = Mock({})
packet = Packet(msg_type=ASK_TIDS)
self.app.num_partitions = 1
cell = Mock({'getUUID':self.app.uuid})
self.app.dm = Mock({'getTIDList': (INVALID_TID, )})
self.app.pt = Mock({'getCellList': (cell, )})
self.operation.handleAskTIDs(conn, packet, 1, 2, INVALID_PARTITION)
self.assertEquals(len(self.app.pt.mockGetNamedCalls('getCellList')), 1)
calls = self.app.dm.mockGetNamedCalls('getTIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [0, ])
self.checkAnswerTids(conn)
def test_26_handleAskObjectHistory1(self):
# invalid offsets => error
app = self.app
app.dm = Mock()
conn = Mock({})
packet = Packet(msg_type=ASK_OBJECT_HISTORY)
self.checkProtocolErrorRaised(self.operation.handleAskObjectHistory, conn, packet, 1, 1, None)
self.assertEquals(len(app.dm.mockGetNamedCalls('getObjectHistory')), 0)
def test_26_handleAskObjectHistory2(self):
# first case: empty history
packet = Packet(msg_type=ASK_OBJECT_HISTORY)
conn = Mock({})
self.app.dm = Mock({'getObjectHistory': None})
self.operation.handleAskObjectHistory(conn, packet, INVALID_OID, 1, 2)
self.checkAnswerObjectHistory(conn)
# second case: not empty history
conn = Mock({})
self.app.dm = Mock({'getObjectHistory': [('', 0, ), ]})
self.operation.handleAskObjectHistory(conn, packet, INVALID_OID, 1, 2)
self.checkAnswerObjectHistory(conn)
def test_25_handleAskOIDs1(self):
# invalid offsets => error
app = self.app
app.pt = Mock()
app.dm = Mock()
conn = Mock({})
packet = Packet(msg_type=ASK_OIDS)
self.checkProtocolErrorRaised(self.operation.handleAskOIDs, conn, packet, 1, 1, None)
self.assertEquals(len(app.pt.mockGetNamedCalls('getCellList')), 0)
self.assertEquals(len(app.dm.mockGetNamedCalls('getOIDList')), 0)
def test_25_handleAskOIDs2(self):
# well case > answer OIDs
conn = Mock({})
packet = Packet(msg_type=ASK_OIDS)
self.app.num_partitions = 1
self.app.dm = Mock({'getOIDList': (INVALID_OID, )})
self.operation.handleAskOIDs(conn, packet, 1, 2, 1)
calls = self.app.dm.mockGetNamedCalls('getOIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [1, ])
self.checkAnswerOids(conn)
def test_25_handleAskOIDs3(self):
# invalid partition => answer usable partitions
conn = Mock({})
packet = Packet(msg_type=ASK_OIDS)
self.app.num_partitions = 1
cell = Mock({'getUUID':self.app.uuid})
self.app.dm = Mock({'getOIDList': (INVALID_OID, )})
self.app.pt = Mock({'getCellList': (cell, )})
self.operation.handleAskOIDs(conn, packet, 1, 2, INVALID_PARTITION)
self.assertEquals(len(self.app.pt.mockGetNamedCalls('getCellList')), 1)
calls = self.app.dm.mockGetNamedCalls('getOIDList')
self.assertEquals(len(calls), 1)
calls[0].checkArgs(1, 1, 1, [0, ])
self.checkAnswerOids(conn)
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testStorageMySQLdb.py 0000664 0000000 0000000 00000076311 11227104631 0027112 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
import MySQLdb
from mock import Mock
from neo.protocol import *
from neo.tests.base import NeoTestBase
from neo.exception import DatabaseFailure
from neo.storage.mysqldb import MySQLDatabaseManager, p64, u64
NEO_SQL_DATABASE = 'test_mysqldb1'
NEO_SQL_USER = 'test'
class StorageMySQSLdbTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1, prefix=NEO_SQL_DATABASE[:-1])
# db manager
self.db = MySQLDatabaseManager(
database=NEO_SQL_DATABASE,
user=NEO_SQL_USER,
)
def tearDown(self):
self.db.close()
def checkCalledQuery(self, query=None, call=0):
self.assertTrue(len(self.db.conn.mockGetNamedCalls('query')) > call)
call = self.db.conn.mockGetNamedCalls('query')[call]
call.checkArgs('BEGIN')
def test_01_p64(self):
self.assertEquals(p64(0), '\0' * 8)
self.assertEquals(p64(1), '\0' * 7 +'\1')
def test_02_u64(self):
self.assertEquals(u64('\0' * 8), 0)
self.assertEquals(u64('\0' * 7 + '\n'), 10)
def test_03_MySQLDatabaseManagerInit(self):
db = MySQLDatabaseManager(
database=NEO_SQL_DATABASE,
user=NEO_SQL_USER,
)
# init
self.assertEquals(db.db, NEO_SQL_DATABASE)
self.assertEquals(db.user, NEO_SQL_USER)
# & connect
import MySQLdb
self.assertTrue(isinstance(db.conn, MySQLdb.connection))
self.assertEquals(db.under_transaction, False)
def test_04_begin1(self):
# implicit commit
self.db.conn = Mock({ })
self.db.under_transaction = True
self.db.begin()
self.assertEquals(len(self.db.conn.mockGetNamedCalls('commit')), 1)
self.checkCalledQuery(query='BEGIN')
self.assertEquals(self.db.under_transaction, True)
def test_05_begin2(self):
# no current transaction
self.db.conn = Mock({ })
self.db.under_transaction = True
self.db.begin()
self.checkCalledQuery(query='COMMIT')
self.assertEquals(self.db.under_transaction, True)
def test_06_commit(self):
self.db.conn = Mock()
self.db.under_transaction = True
self.db.commit()
self.assertEquals(len(self.db.conn.mockGetNamedCalls('commit')), 1)
self.assertEquals(self.db.under_transaction, False)
def test_06_rollback(self):
# rollback called and no current transaction
self.db.conn = Mock({ })
self.db.under_transaction = True
self.db.rollback()
self.assertEquals(len(self.db.conn.mockGetNamedCalls('rollback')), 1)
self.assertEquals(self.db.under_transaction, False)
def test_07_query1(self):
# fake result object
from array import array
result_object = Mock({
"num_rows": 1,
"fetch_row": ((1, 2, array('b', (1, 2, ))), ),
})
# expected formatted result
expected_result = (
(1, 2, '\x01\x02', ),
)
self.db.conn = Mock({ 'store_result': result_object })
result = self.db.query('QUERY')
self.assertEquals(result, expected_result)
calls = self.db.conn.mockGetNamedCalls('query')
self.assertEquals(len(calls), 1)
calls[0].checkArgs('QUERY')
def test_07_query2(self):
# test the OperationalError exception
# fake object, raise exception during the first call
from MySQLdb import OperationalError
from MySQLdb.constants.CR import SERVER_GONE_ERROR
class FakeConn(object):
def query(*args):
raise OperationalError(SERVER_GONE_ERROR, 'this is a test')
self.db.conn = FakeConn()
self.connect_called = False
def connect_hook():
# mock object, break raise/connect loop
self.db.conn = Mock({'num_rows': 0})
self.connect_called = True
self.db.connect = connect_hook
# make a query, exception will be raised then connect() will be
# called and the second query will use the mock object
self.db.query('QUERY')
self.assertTrue(self.connect_called)
def test_07_query3(self):
# OperationalError > raise DatabaseFailure exception
from MySQLdb import OperationalError
class FakeConn(object):
def close(self):
pass
def query(*args):
raise OperationalError(-1, 'this is a test')
self.db.conn = FakeConn()
self.assertRaises(DatabaseFailure, self.db.query, 'QUERY')
def test_08_escape(self):
self.assertEquals(self.db.escape('a"b'), 'a\\"b')
self.assertEquals(self.db.escape("a'b"), "a\\'b")
def test_09_setup(self):
# create all tables
self.db.conn = Mock()
self.db.setup()
calls = self.db.conn.mockGetNamedCalls('query')
self.assertEquals(len(calls), 6)
# create all tables but drop them first
self.db.conn = Mock()
self.db.setup(reset=True)
calls = self.db.conn.mockGetNamedCalls('query')
self.assertEquals(len(calls), 7)
def test_10_getConfiguration(self):
# check if a configuration entry is well read
self.db.setup()
result = self.db.getConfiguration('a')
# doesn't exists, None expected
self.assertEquals(result, None)
self.db.query("insert into config values ('a', 'b');")
result = self.db.getConfiguration('a')
# exists, check result
self.assertEquals(result, 'b')
def test_11_setConfiguration(self):
# check if a configuration entry is well written
self.db.setup()
self.db.setConfiguration('a', 'c')
result = self.db.getConfiguration('a')
self.assertEquals(result, 'c')
def checkConfigEntry(self, get_call, set_call, value):
# generic test for all configuration entries accessors
self.db.setup()
self.assertEquals(get_call(), None)
set_call(value)
self.assertEquals(get_call(), value)
set_call(value * 2)
self.assertEquals(get_call(), value * 2)
def test_12_UUID(self):
self.checkConfigEntry(
get_call=self.db.getUUID,
set_call=self.db.setUUID,
value='TEST_VALUE')
def test_13_NumPartitions(self):
self.checkConfigEntry(
get_call=self.db.getNumPartitions,
set_call=self.db.setNumPartitions,
value=10)
def test_14_Name(self):
self.checkConfigEntry(
get_call=self.db.getName,
set_call=self.db.setName,
value='TEST_NAME')
def test_15_PTID(self):
test = '\x01' * 8
self.db.setup()
self.assertEquals(self.db.getPTID(), INVALID_PTID)
self.db.setPTID(test)
self.assertEquals(self.db.getPTID(), test)
self.db.setPTID(test * 2)
self.assertEquals(self.db.getPTID(), test * 2)
def test_16_getPartitionTable(self):
# insert an entry and check it
self.db.setup()
rid, uuid, state = '\x00' * 8, '\x00' * 16, 0
self.db.query("insert into pt (rid, uuid, state) values ('%s', '%s', %d)" %
(u64(rid), uuid, state))
pt = self.db.getPartitionTable()
self.assertEquals(pt, ((0L, uuid, state), ))
def test_17_getLastOID(self):
# should search in obj table only
self.db.setup()
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values (3, 'A', 0, 0, '')""")
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values (1, 'A', 0, 0, '')""")
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values (2, 'A', 0, 0, '')""")
result = self.db.getLastOID()
self.assertEquals(result, '\x00' * 7 + '\x03')
# should look in temporary table too
self.db.query("""insert into tobj (oid, serial, compression,
checksum, value) values (5, 'A', 0, 0, '')""")
result = self.db.getLastOID(all=True)
self.assertEquals(result, '\x00' * 7 + '\x05')
def test_18_getLastTID(self):
# max TID is in obj table
self.db.setup()
self.db.query("""insert into trans (tid, oids, user,
description, ext) values (1, '', '', '', '')""")
self.db.query("""insert into trans (tid, oids, user,
description, ext) values (2, '', '', '', '')""")
result = self.db.getLastTID()
self.assertEquals(result, '\x00' * 7 + '\x02')
# max tid is in ttrans table
self.db.query("""insert into ttrans (tid, oids, user,
description, ext) values (3, '', '', '', '')""")
result = self.db.getLastTID()
self.assertEquals(result, '\x00' * 7 + '\x03')
# max tid is in tobj (serial)
self.db.query("""insert into tobj (oid, serial, compression,
checksum, value) values (0, 4, 0, 0, '')""")
result = self.db.getLastTID()
self.assertEquals(result, '\x00' * 7 + '\x04')
def test_19_getUnfinishedTIDList(self):
self.db.setup()
self.db.query("""insert into ttrans (tid, oids, user,
description, ext) values (3, '', '', '', '')""")
self.db.query("""insert into tobj (oid, serial, compression,
checksum, value) values (0, 4, 0, 0, '')""")
result = self.db.getUnfinishedTIDList()
expected = ['\x00' * 7 + '\x03', '\x00' * 7 + '\x04']
self.assertEquals(result, expected)
def test_20_objectPresent(self):
self.db.setup()
oid1, tid1 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x01'
oid2, tid2 = '\x00' * 7 + '\x02', '\x00' * 7 + '\x02'
# object not present
self.assertFalse(self.db.objectPresent(oid1, tid1, all=False))
self.assertFalse(self.db.objectPresent(oid1, tid1, all=True))
self.assertFalse(self.db.objectPresent(oid2, tid2, all=False))
self.assertFalse(self.db.objectPresent(oid2, tid2, all=True))
# object present in temp table
self.db.query("""insert into tobj (oid, serial, compression,
checksum, value) values (%d, %d, 0, 0, '')""" %
(u64(oid1), u64(tid1)))
self.assertFalse(self.db.objectPresent(oid1, tid1, all=False))
self.assertTrue(self.db.objectPresent(oid1, tid1, all=True))
self.assertFalse(self.db.objectPresent(oid2, tid2, all=False))
self.assertFalse(self.db.objectPresent(oid2, tid2, all=True))
# object present in both table
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values ("%s", "%s", 0, 0, '')""" %
(u64(oid2), u64(tid2)))
self.assertFalse(self.db.objectPresent(oid1, tid1, all=False))
self.assertTrue(self.db.objectPresent(oid1, tid1, all=True))
self.assertTrue(self.db.objectPresent(oid2, tid2, all=False))
self.assertTrue(self.db.objectPresent(oid2, tid2, all=True))
def test_21_getObject(self):
self.db.setup()
oid1, tid1 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x01'
oid2, tid2 = '\x00' * 7 + '\x02', '\x00' * 7 + '\x02'
# tid specified and object not present
result = self.db.getObject(oid1, tid1)
self.assertEquals(result, None)
# tid specified and object present
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values (%d, %d, 0, 0, '')""" %
(u64(oid1), u64(tid1)))
result = self.db.getObject(oid1, tid1)
self.assertEquals(result, (tid1, None, 0, 0, ''))
# before_tid specified, object not present
result = self.db.getObject(oid2, before_tid=tid2)
self.assertEquals(result, None)
# before_tid specified, object present, no next serial
result = self.db.getObject(oid1, before_tid=tid2)
self.assertEquals(result, (tid1, None, 0, 0, ''))
# before_tid specified, object present, next serial exists
self.db.query("""insert into obj (oid, serial, compression,
checksum, value) values (%d, %d, 0, 0, '')""" %
(u64(oid1), u64(tid2)))
result = self.db.getObject(oid1, before_tid=tid2)
self.assertEquals(result, (tid1, tid2, 0, 0, ''))
# no tid specified, retreive last object transaction, object unknown
result = self.db.getObject(oid2)
self.assertEquals(result, None)
# same but object found
result = self.db.getObject(oid1)
self.assertEquals(result, (tid2, None, 0, 0, ''))
def test_23_changePartitionTable(self):
# two sn, two partitions
self.db.setup()
ptid = '1'
uuid1, uuid2 = '\x00' * 15 + '\x01', '\x00' * 15 + '\x02'
cells = (
(0, uuid1, DISCARDED_STATE),
(1, uuid2, UP_TO_DATE_STATE),
)
self.db.setPTID(INVALID_PTID)
# empty table -> insert for second cell
self.db.changePartitionTable(ptid, cells)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1, uuid2, 0))
self.assertEquals(self.db.getPTID(), ptid)
# delete previous entries for a DISCARDED_STATE node
self.db.query("delete from pt")
args = (0, uuid1, DISCARDED_STATE)
self.db.query('insert into pt (rid, uuid, state) values (%d, "%s", %d)' % args)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (0, uuid1, 3))
self.assertEquals(self.db.getPTID(), ptid)
self.db.changePartitionTable(ptid, cells)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1, uuid2, 0))
self.assertEquals(self.db.getPTID(), ptid)
# raise exception (config not set), check rollback
self.db.query("drop table config") # will generate the exception
args = (0, uuid1, DISCARDED_STATE)
self.db.query('insert into pt (rid, uuid, state) values (%d, "%s", %d)' % args)
self.assertRaises(MySQLdb.ProgrammingError,
self.db.changePartitionTable, ptid, cells)
result = self.db.query('select count(*) from pt where rid=0')
self.assertEquals(len(result), 1)
def test_22_setGetPartitionTable(self):
# two sn, two partitions
self.db.setup()
ptid = '1'
uuid1, uuid2 = '\x00' * 15 + '\x01', '\x00' * 15 + '\x02'
cells = (
(0, uuid1, DISCARDED_STATE),
(1, uuid2, UP_TO_DATE_STATE),
)
self.db.setPTID(INVALID_PTID)
# not empty table, reset -> clean table first
args = (0, uuid2, UP_TO_DATE_STATE)
self.db.query('insert into pt (rid, uuid, state) values (%d, "%s", %d)' % args)
self.db.setPartitionTable(ptid, cells)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1, uuid2, 0))
self.assertEquals(self.db.getPTID(), ptid)
# delete previous entries for a DISCARDED_STATE node
self.db.query("delete from pt")
args = (0, uuid1, DISCARDED_STATE)
self.db.query('insert into pt (rid, uuid, state) values (%d, "%s", %d)' % args)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (0, uuid1, 3))
self.assertEquals(self.db.getPTID(), ptid)
self.db.setPartitionTable(ptid, cells)
result = self.db.query('select rid, uuid, state from pt')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1, uuid2, 0))
self.assertEquals(self.db.getPTID(), ptid)
# raise exception (config not set), check rollback
self.db.query("drop table config") # will generate the exception
args = (0, uuid1, DISCARDED_STATE)
self.db.query('insert into pt (rid, uuid, state) values (%d, "%s", %d)' % args)
self.assertRaises(MySQLdb.ProgrammingError,
self.db.setPartitionTable, ptid, cells)
result = self.db.query('select count(*) from pt where rid=0')
self.assertEquals(len(result), 1)
def test_23_dropUnfinishedData(self):
# delete entries from tobj and ttrans
self.db.setup()
self.db.query("""insert into tobj (oid, serial, compression,
checksum, value) values (0, 4, 0, 0, '')""")
self.db.query("""insert into ttrans (tid, oids, user,
description, ext) values (3, '', '', '', '')""")
self.db.dropUnfinishedData()
result = self.db.query('select * from tobj')
self.assertEquals(result, ())
result = self.db.query('select * from ttrans')
self.assertEquals(result, ())
def test_24_storeTransaction1(self):
# data set
tid = '\x00' * 7 + '\x01'
oid1, oid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
object_list = ( (oid1, 0, 0, ''), (oid2, 0, 0, ''),)
transaction = ((oid1, oid2), 'user', 'desc', 'ext')
# store objects in temporary table
self.db.setup()
self.db.storeTransaction(tid, object_list, transaction=None, temporary=True)
result = self.db.query('select oid, serial, compression, checksum, value from tobj')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 1, 0, 0, ''))
self.assertEquals(result[1], (2L, 1, 0, 0, ''))
result = self.db.query('select * from obj')
self.assertEquals(len(result), 0)
# and in obj table
self.db.setup(reset=True)
self.db.storeTransaction(tid, object_list, transaction=None, temporary=False)
result = self.db.query('select oid, serial, compression, checksum, value from obj')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 1, 0, 0, ''))
self.assertEquals(result[1], (2L, 1, 0, 0, ''))
result = self.db.query('select * from tobj')
self.assertEquals(len(result), 0)
# store transaction in temporary table
self.db.setup(reset=True)
self.db.storeTransaction(tid, (), transaction=transaction, temporary=True)
result = self.db.query('select tid, oids, user, description, ext from ttrans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1L, oid1 + oid2, 'user', 'desc', 'ext',) )
result = self.db.query('select * from trans')
self.assertEquals(len(result), 0)
# and in trans table
self.db.setup(reset=True)
self.db.storeTransaction(tid, (), transaction=transaction, temporary=False)
result = self.db.query('select tid, oids, user, description, ext from trans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1L, oid1 + oid2, 'user', 'desc', 'ext',))
result = self.db.query('select * from ttrans')
self.assertEquals(len(result), 0)
def test_25_finishTransaction(self):
# data set
tid1, tid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
oid1, oid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
object_list = ( (oid1, 0, 0, ''), (oid2, 0, 0, ''),)
transaction = ((oid1, oid2), 'u', 'd', 'e')
self.db.setup(reset=True)
# store two temporary transactions
self.db.storeTransaction(tid1, object_list, transaction, temporary=True)
self.db.storeTransaction(tid2, object_list, transaction, temporary=True)
result = self.db.query('select count(*) from tobj')
self.assertEquals(result[0][0], 4)
result = self.db.query('select count(*) from ttrans')
self.assertEquals(result[0][0], 2)
# finish t1
self.db.finishTransaction(tid1)
# t1 should be finished
result = self.db.query('select * from obj order by oid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 1L, 0, 0, ''))
self.assertEquals(result[1], (2L, 1L, 0, 0, ''))
result = self.db.query('select * from trans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (1L, oid1 + oid2, 'u', 'd', 'e',))
# t2 should stay in temporary tables
result = self.db.query('select * from tobj order by oid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 2L, 0, 0, ''))
self.assertEquals(result[1], (2L, 2L, 0, 0, ''))
result = self.db.query('select * from ttrans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (2L, oid1 + oid2, 'u', 'd', 'e',))
def test_26_deleteTransaction(self):
# data set
tid1, tid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
oid1, oid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
object_list = ( (oid1, 0, 0, ''), (oid2, 0, 0, ''),)
transaction = ((oid1, oid2), 'u', 'd', 'e')
self.db.setup(reset=True)
# store two transactions in both state
self.db.storeTransaction(tid1, object_list, transaction, temporary=True)
self.db.storeTransaction(tid2, object_list, transaction, temporary=True)
self.db.storeTransaction(tid1, object_list, transaction, temporary=False)
self.db.storeTransaction(tid2, object_list, transaction, temporary=False)
result = self.db.query('select count(*) from tobj')
self.assertEquals(result[0][0], 4)
result = self.db.query('select count(*) from ttrans')
self.assertEquals(result[0][0], 2)
result = self.db.query('select count(*) from obj')
self.assertEquals(result[0][0], 4)
result = self.db.query('select count(*) from trans')
self.assertEquals(result[0][0], 2)
# delete t1 (all)
self.db.deleteTransaction(tid1, all=True)
# t2 not altered
result = self.db.query('select * from tobj order by oid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 2L, 0, 0, ''))
self.assertEquals(result[1], (2L, 2L, 0, 0, ''))
result = self.db.query('select * from ttrans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (2L, oid1 + oid2, 'u', 'd', 'e',))
result = self.db.query('select * from obj order by oid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 2L, 0, 0, ''))
self.assertEquals(result[1], (2L, 2L, 0, 0, ''))
result = self.db.query('select * from trans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (2L, oid1 + oid2, 'u', 'd', 'e',))
# store t1 again
self.db.storeTransaction(tid1, object_list, transaction, temporary=True)
self.db.storeTransaction(tid1, object_list, transaction, temporary=False)
# and remove it but only from temporary tables
self.db.deleteTransaction(tid1, all=False)
# t2 not altered and t1 stay in obj/trans tables
result = self.db.query('select * from tobj order by oid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, 2L, 0, 0, ''))
self.assertEquals(result[1], (2L, 2L, 0, 0, ''))
result = self.db.query('select * from ttrans')
self.assertEquals(len(result), 1)
self.assertEquals(result[0], (2L, oid1 + oid2, 'u', 'd', 'e',))
result = self.db.query('select * from obj order by oid, serial asc')
self.assertEquals(len(result), 4)
self.assertEquals(result[0], (1L, 1L, 0, 0, ''))
self.assertEquals(result[1], (1L, 2L, 0, 0, ''))
self.assertEquals(result[2], (2L, 1L, 0, 0, ''))
self.assertEquals(result[3], (2L, 2L, 0, 0, ''))
result = self.db.query('select * from trans order by tid asc')
self.assertEquals(len(result), 2)
self.assertEquals(result[0], (1L, oid1 + oid2, 'u', 'd', 'e',))
self.assertEquals(result[1], (2L, oid1 + oid2, 'u', 'd', 'e',))
def test_27_getTransaction(self):
# data set
tid1, tid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
oid1, oid2 = '\x00' * 7 + '\x01', '\x00' * 7 + '\x02'
oids = [oid1, oid2]
transaction = ((oid1, oid2), 'u', 'd', 'e')
self.db.setup(reset=True)
# store t1 in temporary and t2 in persistent tables
self.db.storeTransaction(tid1, (), transaction, temporary=True)
self.db.storeTransaction(tid2, (), transaction, temporary=False)
# get t1 from all -> OK
t = self.db.getTransaction(tid1, all=True)
self.assertEquals(t, (oids, 'u', 'd', 'e'))
# get t1 from no tmp only -> fail
t = self.db.getTransaction(tid1, all=False)
self.assertEquals(t, None)
# get t2 from all or not -> always OK
t = self.db.getTransaction(tid2, all=True)
self.assertEquals(t, (oids, 'u', 'd', 'e'))
t = self.db.getTransaction(tid2, all=False)
self.assertEquals(t, (oids, 'u', 'd', 'e'))
# store wrong oids -> DatabaseFailure
self.db.setup(reset=True)
self.db.query("""replace into trans (tid, oids, user, description, ext)
values ('%s', '%s', 'u', 'd', 'e')""" % (u64(tid1), 'OIDs_'))
self.assertRaises(DatabaseFailure, self.db.getTransaction, tid1)
def test_28_getOIDList(self):
# there are two partitions and two objects in each of them
# o1 & o3 in p1, o2 & o4 in p2
self.db.setup(reset=True)
tid = '\x00' * 7 + '\x01'
oid1, oid2, oid3, oid4 = ['\x00' * 7 + chr(i) for i in xrange(4)]
for oid in (oid1, oid2, oid3, oid4):
self.db.query("replace into obj values (%d, %d, 0, 0, '')" %
(u64(oid), u64(tid)))
# get all oids for all partitions
result = self.db.getOIDList(0, 4, 2, (0, 1))
self.assertEquals(result, [oid4, oid3, oid2, oid1])
# get all oids but from the second with a limit a two
result = self.db.getOIDList(1, 2, 2, (0, 1))
self.assertEquals(result, [oid3, oid2])
# get all oids for the first partition
result = self.db.getOIDList(0, 2, 2, (0, ))
self.assertEquals(result, [oid3, oid1])
# get all oids for the second partition with a limit of one
result = self.db.getOIDList(0, 1, 2, (1, ))
self.assertEquals(result, [oid4])
# get all oids for the second partition with an offset of 3 > nothing
result = self.db.getOIDList(3, 2, 2, (1, ))
self.assertEquals(result, [])
# get oids for an inexsitant partition -> nothing
result = self.db.getOIDList(0, 2, 2, (3, ))
self.assertEquals(result, [])
def test_29_getObjectHistory(self):
# there is one object with 4 revisions
self.db.setup(reset=True)
tids = ['\x00' * 7 + chr(i) for i in xrange(4)]
oid = '\x00' * 8
for tid in tids:
self.db.query("replace into obj values (%d, %d, 0, 0, '')" %
(u64(oid), u64(tid)))
# unkwown object
result = self.db.getObjectHistory(oid='\x01' * 8)
self.assertEquals(result, None)
# retreive all revisions
result = self.db.getObjectHistory(oid=oid, offset=0, length=4)
expected = [(tid, 0) for tid in reversed(tids)]
self.assertEquals(result, expected)
# get from the second, limit to 2 revisions
result = self.db.getObjectHistory(oid=oid, offset=1, length=2)
expected = [(tids[2], 0), (tids[1], 0)]
self.assertEquals(result, expected)
# get from the fifth -> nothing
result = self.db.getObjectHistory(oid=oid, offset=4, length=2)
self.assertEquals(result, None)
def test_28_getTIDList(self):
# same as for getOIDList, 2 partitions and 4 transactions
self.db.setup(reset=True)
tids = ['\x00' * 7 + chr(i) for i in xrange(4)]
tid1, tid2, tid3, tid4 = tids
for tid in tids:
self.db.query("replace into trans values (%d, '', 'u', 'd', 'e')" %
(u64(tid)))
# get all tids for all partitions
result = self.db.getTIDList(0, 4, 2, (0, 1))
self.assertEquals(result, [tid4, tid3, tid2, tid1])
# get all tids but from the second with a limit a two
result = self.db.getTIDList(1, 2, 2, (0, 1))
self.assertEquals(result, [tid3, tid2])
# get all tids for the first partition
result = self.db.getTIDList(0, 2, 2, (0, ))
self.assertEquals(result, [tid3, tid1])
# get all tids for the second partition with a limit of one
result = self.db.getTIDList(0, 1, 2, (1, ))
self.assertEquals(result, [tid4])
# get all tids for the second partition with an offset of 3 > nothing
result = self.db.getTIDList(3, 2, 2, (1, ))
self.assertEquals(result, [])
# get tids for an inexsitant partition -> nothing
result = self.db.getTIDList(0, 2, 2, (3, ))
self.assertEquals(result, [])
def test_29_getTIDListPresent(self):
# 4 sample transactions
self.db.setup(reset=True)
tid = '\x00' * 7 + '\x01'
tid1, tid2, tid3, tid4 = ['\x00' * 7 + chr(i) for i in xrange(4)]
for tid in (tid1, tid2, tid3, tid4):
self.db.query("replace into trans values (%d, '', 'u', 'd', 'e')" %
(u64(tid)))
# all match
result = self.db.getTIDListPresent((tid1, tid2, tid3, tid4))
expected = [tid1, tid2, tid3, tid4]
self.assertEquals(sorted(result), expected)
# none match
result = self.db.getTIDListPresent(('\x01' * 8, '\x02' * 8))
self.assertEquals(result, [])
# some match
result = self.db.getTIDListPresent((tid1, tid3))
self.assertEquals(sorted(result), [tid1, tid3])
def test_30_getSerialListPresent(self):
# 4 sample objects
self.db.setup(reset=True)
tids = ['\x00' * 7 + chr(i) for i in xrange(4)]
tid1, tid2, tid3, tid4 = tids
oid = '\x00' * 8
for tid in tids:
self.db.query("replace into obj values (%d, %d, 0, 0, '')" %
(u64(oid), u64(tid)))
# all match
result = self.db.getSerialListPresent(oid, tids)
expected = list(tids)
self.assertEquals(sorted(result), expected)
# none match
result = self.db.getSerialListPresent(oid, ('\x01' * 8, '\x02' * 8))
self.assertEquals(result, [])
# some match
result = self.db.getSerialListPresent(oid, (tid1, tid3))
self.assertEquals(sorted(result), [tid1, tid3])
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/storage/tests/testVerificationHandler.py 0000664 0000000 0000000 00000040401 11227104631 0030221 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import logging
from mock import Mock
from neo.tests.base import NeoTestBase
from neo import protocol
from neo.node import MasterNode
from neo.pt import PartitionTable
from neo.storage.app import Application, StorageNode
from neo.storage.handlers import VerificationHandler
from neo.protocol import STORAGE_NODE_TYPE, MASTER_NODE_TYPE, CLIENT_NODE_TYPE
from neo.protocol import BROKEN_STATE, RUNNING_STATE, Packet, INVALID_UUID, \
UP_TO_DATE_STATE, INVALID_OID, INVALID_TID, PROTOCOL_ERROR_CODE
from neo.protocol import ACCEPT_NODE_IDENTIFICATION, REQUEST_NODE_IDENTIFICATION, \
NOTIFY_PARTITION_CHANGES, STOP_OPERATION, ASK_LAST_IDS, ASK_PARTITION_TABLE, \
ANSWER_OBJECT_PRESENT, ASK_OBJECT_PRESENT, OID_NOT_FOUND_CODE, LOCK_INFORMATION, \
UNLOCK_INFORMATION, TID_NOT_FOUND_CODE, ASK_TRANSACTION_INFORMATION, \
COMMIT_TRANSACTION, ASK_UNFINISHED_TRANSACTIONS, SEND_PARTITION_TABLE
from neo.protocol import ERROR, BROKEN_NODE_DISALLOWED_CODE, ASK_PRIMARY_MASTER
from neo.protocol import ANSWER_PRIMARY_MASTER
from neo.exception import PrimaryFailure, OperationFailure
from neo.storage.mysqldb import MySQLDatabaseManager, p64, u64
class StorageVerificationHandlerTests(NeoTestBase):
def setUp(self):
logging.basicConfig(level = logging.ERROR)
self.prepareDatabase(number=1)
# create an application object
config = self.getConfigFile(master_number=1)
self.app = Application(config, "storage1")
self.verification = VerificationHandler(self.app)
# define some variable to simulate client and storage node
self.master_port = 10010
self.storage_port = 10020
self.client_port = 11011
self.num_partitions = 1009
self.num_replicas = 2
self.app.num_partitions = 1009
self.app.num_replicas = 2
self.app.operational = False
self.app.load_lock_dict = {}
self.app.pt = PartitionTable(self.app.num_partitions, self.app.num_replicas)
def tearDown(self):
NeoTestBase.tearDown(self)
# Common methods
def getLastUUID(self):
return self.uuid
# Tests
def test_02_timeoutExpired(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.timeoutExpired, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_03_connectionClosed(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.connectionClosed, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_04_peerBroken(self):
# client connection
uuid = self.getNewUUID()
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.assertRaises(PrimaryFailure, self.verification.peerBroken, conn,)
# nothing happens
self.checkNoPacketSent(conn)
def test_07_handleAskLastIDs(self):
uuid = self.getNewUUID()
packet = Mock()
# return invalid if db store nothing
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.verification.handleAskLastIDs(conn, packet)
oid, tid, ptid = self.checkAnswerLastIDs(conn, decode=True)
self.assertEqual(oid, INVALID_OID)
self.assertEqual(tid, INVALID_TID)
self.assertEqual(ptid, self.app.ptid)
# return value stored in db
# insert some oid
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.app.dm.begin()
self.app.dm.query("""insert into obj (oid, serial, compression,
checksum, value) values (3, 'A', 0, 0, '')""")
self.app.dm.query("""insert into obj (oid, serial, compression,
checksum, value) values (1, 'A', 0, 0, '')""")
self.app.dm.query("""insert into obj (oid, serial, compression,
checksum, value) values (2, 'A', 0, 0, '')""")
self.app.dm.query("""insert into tobj (oid, serial, compression,
checksum, value) values (5, 'A', 0, 0, '')""")
# insert some tid
self.app.dm.query("""insert into trans (tid, oids, user,
description, ext) values (1, '', '', '', '')""")
self.app.dm.query("""insert into trans (tid, oids, user,
description, ext) values (2, '', '', '', '')""")
self.app.dm.query("""insert into ttrans (tid, oids, user,
description, ext) values (3, '', '', '', '')""")
# max tid is in tobj (serial)
self.app.dm.query("""insert into tobj (oid, serial, compression,
checksum, value) values (0, 4, 0, 0, '')""")
self.app.dm.commit()
self.verification.handleAskLastIDs(conn, packet)
self.checkAnswerLastIDs(conn)
oid, tid, ptid = self.checkAnswerLastIDs(conn, decode=True)
self.assertEqual(u64(oid), 5)
self.assertEqual(u64(tid), 4)
self.assertEqual(ptid, self.app.ptid)
def test_08_handleAskPartitionTable(self):
uuid = self.getNewUUID()
packet = Mock()
# try to get unknown offset
self.assertEqual(len(self.app.pt.getNodeList()), 0)
self.assertFalse(self.app.pt.hasOffset(1))
self.assertEqual(len(self.app.pt.getCellList(1)), 0)
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.verification.handleAskPartitionTable(conn, packet, [1,])
ptid, row_list = self.checkAnswerPartitionTable(conn, decode=True)
self.assertEqual(len(row_list), 1)
offset, rows = row_list[0]
self.assertEqual(offset, 1)
self.assertEqual(len(rows), 0)
# try to get known offset
node = StorageNode(("127.7.9.9", 1), self.getNewUUID())
self.app.pt.setCell(1, node, UP_TO_DATE_STATE)
self.assertTrue(self.app.pt.hasOffset(1))
conn = Mock({"getUUID" : uuid,
"getAddress" : ("127.0.0.1", self.client_port),
"isServerConnection" : False})
self.verification.handleAskPartitionTable(conn, packet, [1,])
ptid, row_list = self.checkAnswerPartitionTable(conn, decode=True)
self.assertEqual(len(row_list), 1)
offset, rows = row_list[0]
self.assertEqual(offset, 1)
self.assertEqual(len(rows), 1)
def test_10_handleNotifyPartitionChanges(self):
# old partition change
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
packet = Packet(msg_type=NOTIFY_PARTITION_CHANGES)
self.app.ptid = 1
self.verification.handleNotifyPartitionChanges(conn, packet, 0, ())
self.assertEquals(self.app.ptid, 1)
# new node
conn = Mock({
"isServerConnection": False,
"getAddress" : ("127.0.0.1", self.master_port),
})
packet = Packet(msg_type=NOTIFY_PARTITION_CHANGES)
cell = (0, self.getNewUUID(), UP_TO_DATE_STATE)
count = len(self.app.nm.getNodeList())
self.app.pt = PartitionTable(1, 1)
self.app.dm = Mock({ })
ptid, self.ptid = self.getTwoIDs()
# pt updated
self.verification.handleNotifyPartitionChanges(conn, packet, ptid, (cell, ))
self.assertEquals(len(self.app.nm.getNodeList()), count + 1)
# check db update
calls = self.app.dm.mockGetNamedCalls('changePartitionTable')
self.assertEquals(len(calls), 1)
self.assertEquals(calls[0].getParam(0), ptid)
self.assertEquals(calls[0].getParam(1), (cell, ))
def test_11_handleStartOperation(self):
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
self.assertFalse(self.app.operational)
packet = Packet(msg_type=STOP_OPERATION)
self.verification.handleStartOperation(conn, packet)
self.assertTrue(self.app.operational)
def test_12_handleStopOperation(self):
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
packet = Packet(msg_type=STOP_OPERATION)
self.assertRaises(OperationFailure, self.verification.handleStopOperation, conn, packet)
def test_13_handleAskUnfinishedTransactions(self):
# client connection with no data
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False})
packet = Packet(msg_type=ASK_UNFINISHED_TRANSACTIONS)
self.verification.handleAskUnfinishedTransactions(conn, packet)
(tid_list, ) = self.checkAnswerUnfinishedTransactions(conn, decode=True)
self.assertEqual(len(tid_list), 0)
# client connection with some data
self.app.dm.begin()
self.app.dm.query("""insert into tobj (oid, serial, compression,
checksum, value) values (0, 4, 0, 0, '')""")
self.app.dm.commit()
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False})
packet = Packet(msg_type=ASK_UNFINISHED_TRANSACTIONS)
self.verification.handleAskUnfinishedTransactions(conn, packet)
(tid_list, ) = self.checkAnswerUnfinishedTransactions(conn, decode=True)
self.assertEqual(len(tid_list), 1)
self.assertEqual(u64(tid_list[0]), 4)
def test_14_handleAskTransactionInformation(self):
# ask from client conn with no data
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.verification.handleAskTransactionInformation(conn, packet, p64(1))
code, message = self.checkErrorPacket(conn, decode=True)
self.assertEqual(code, TID_NOT_FOUND_CODE)
# input some tmp data and ask from client, must find both transaction
self.app.dm.begin()
self.app.dm.query("""insert into ttrans (tid, oids, user,
description, ext) values (3, '%s', 'u1', 'd1', 'e1')""" %(p64(4),))
self.app.dm.query("""insert into trans (tid, oids, user,
description, ext) values (1,'%s', 'u2', 'd2', 'e2')""" %(p64(2),))
self.app.dm.commit()
# object from trans
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.verification.handleAskTransactionInformation(conn, packet, p64(1))
tid, user, desc, ext, oid_list = self.checkAnswerTransactionInformation(conn, decode=True)
self.assertEqual(u64(tid), 1)
self.assertEqual(user, 'u2')
self.assertEqual(desc, 'd2')
self.assertEqual(ext, 'e2')
self.assertEqual(len(oid_list), 1)
self.assertEqual(u64(oid_list[0]), 2)
# object from ttrans
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.verification.handleAskTransactionInformation(conn, packet, p64(3))
tid, user, desc, ext, oid_list = self.checkAnswerTransactionInformation(conn, decode=True)
self.assertEqual(u64(tid), 3)
self.assertEqual(user, 'u1')
self.assertEqual(desc, 'd1')
self.assertEqual(ext, 'e1')
self.assertEqual(len(oid_list), 1)
self.assertEqual(u64(oid_list[0]), 4)
# input some tmp data and ask from server, must find one transaction
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': True })
# find the one in trans
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.verification.handleAskTransactionInformation(conn, packet, p64(1))
tid, user, desc, ext, oid_list = self.checkAnswerTransactionInformation(conn, decode=True)
self.assertEqual(u64(tid), 1)
self.assertEqual(user, 'u2')
self.assertEqual(desc, 'd2')
self.assertEqual(ext, 'e2')
self.assertEqual(len(oid_list), 1)
self.assertEqual(u64(oid_list[0]), 2)
# do not find the one in ttrans
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': True })
packet = Packet(msg_type=ASK_TRANSACTION_INFORMATION)
self.verification.handleAskTransactionInformation(conn, packet, p64(3))
code, message = self.checkErrorPacket(conn, decode=True)
self.assertEqual(code, TID_NOT_FOUND_CODE)
def test_15_handleAskObjectPresent(self):
# client connection with no data
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False})
packet = Packet(msg_type=ASK_OBJECT_PRESENT)
self.verification.handleAskObjectPresent(conn, packet, p64(1), p64(2))
code, message = self.checkErrorPacket(conn, decode=True)
self.assertEqual(code, OID_NOT_FOUND_CODE)
# client connection with some data
self.app.dm.begin()
self.app.dm.query("""insert into tobj (oid, serial, compression,
checksum, value) values (1, 2, 0, 0, '')""")
self.app.dm.commit()
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False})
packet = Packet(msg_type=ASK_OBJECT_PRESENT)
self.verification.handleAskObjectPresent(conn, packet, p64(1), p64(2))
oid, tid = self.checkAnswerObjectPresent(conn, decode=True)
self.assertEqual(u64(tid), 2)
self.assertEqual(u64(oid), 1)
def test_16_handleDeleteTransaction(self):
# client connection with no data
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False})
packet = Packet(msg_type=ASK_OBJECT_PRESENT)
self.verification.handleDeleteTransaction(conn, packet, p64(1))
# client connection with data
self.app.dm.begin()
self.app.dm.query("""insert into tobj (oid, serial, compression,
checksum, value) values (1, 2, 0, 0, '')""")
self.app.dm.commit()
self.verification.handleDeleteTransaction(conn, packet, p64(2))
result = self.app.dm.query('select * from tobj')
self.assertEquals(len(result), 0)
def test_17_handleCommitTransaction(self):
# commit a transaction
conn = Mock({ "getAddress" : ("127.0.0.1", self.master_port),
'isServerConnection': False })
dm = Mock()
self.app.dm = dm
packet = Packet(msg_type=COMMIT_TRANSACTION)
self.verification.handleCommitTransaction(conn, packet, p64(1))
self.assertEqual(len(dm.mockGetNamedCalls("finishTransaction")), 1)
call = dm.mockGetNamedCalls("finishTransaction")[0]
tid = call.getParam(0)
self.assertEqual(u64(tid), 1)
if __name__ == "__main__":
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/ 0000775 0000000 0000000 00000000000 11227104631 0021364 5 ustar 00root root 0000000 0000000 neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/__init__.py 0000664 0000000 0000000 00000001074 11227104631 0023477 0 ustar 00root root 0000000 0000000 from neo.tests.testConfig import ConfigurationManagerTests
from neo.tests.testConnection import ConnectionTests
from neo.tests.testEvent import EventTests
from neo.tests.testHandler import HandlerDecoratorsTests, HandlerTests
from neo.tests.testNodes import NodesTests
from neo.tests.testPT import PartitionTableTests
from neo.tests.testProtocol import ProtocolTests
__all__ = [
'ConfigurationManagerTests',
'ConnectionTests',
'EventTests',
'HandlerDecoratorsTests',
'HandlerTests',
'NodesTests',
'PartitionTableTests',
'ProtocolTests',
]
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/base.py 0000664 0000000 0000000 00000031275 11227104631 0022660 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import unittest
import tempfile
import MySQLdb
from mock import Mock
from neo import protocol
base_config = """
[DEFAULT]
master_nodes: %(master_nodes)s
replicas: %(replicas)d
partitions: %(partitions)d
name: main
user: %(user)s
connector: SocketConnector
"""
master_config = """
[master%(id)d]
server: 127.0.0.1:1001%(index)d
"""
storage_config = """
[storage%(id)d]
database: %(db)s%(id)d
server: 127.0.0.1:1002%(index)d
"""
DB_PREFIX = 'test_neo_'
DB_ADMIN = 'root'
DB_PASSWD = None
DB_USER = 'test'
class NeoTestBase(unittest.TestCase):
""" Base class for neo tests, implements common checks """
config_file = None
def prepareDatabase(self, number, admin=DB_ADMIN, password=DB_PASSWD,
user=DB_USER, prefix=DB_PREFIX):
""" create empties databases """
# SQL connection
connect_arg_dict = {'user': admin}
if password is not None:
connect_arg_dict['passwd'] = password
sql_connection = MySQLdb.Connect(**connect_arg_dict)
cursor = sql_connection.cursor()
# drop and create each database
for i in xrange(number):
database = "%s%d" % (prefix, i+1)
cursor.execute('DROP DATABASE IF EXISTS %s' % (database, ))
cursor.execute('CREATE DATABASE %s' % (database, ))
cursor.execute('GRANT ALL ON %s.* TO "%s"@"localhost" IDENTIFIED BY ""' %
(database, user))
cursor.close()
sql_connection.close()
def getConfigFile(self, master_number=2, storage_number=2, user=DB_USER,
replicas=2, partitions=1009, database=DB_PREFIX):
# if already called
if self.config_file is not None:
os.remove(self.config_file)
# global config
master_nodes = ''
for i in xrange(master_number):
master_nodes += '127.0.0.1:1001%d ' % i
config = base_config % {
'master_nodes': master_nodes,
'replicas': replicas,
'partitions': partitions,
'user':user,
}
# append masters
for i in xrange(master_number):
config += master_config % {'index':i, 'id':i+1}
# and storages
for i in xrange(storage_number):
config += storage_config % { 'index':i, 'id':i+1, 'db':database}
tmp_id, self.config_file = tempfile.mkstemp()
tmp_file = os.fdopen(tmp_id, "w+b")
tmp_file.write(config)
tmp_file.close()
return self.config_file
def tearDown(self):
if self.config_file is not None:
os.remove(self.config_file)
# XXX: according to changes with namespaced UUIDs, it would be better to
# implement getUUID() methods
def getNewUUID(self):
""" Return a valid UUID """
uuid = protocol.INVALID_UUID
while uuid == protocol.INVALID_UUID:
uuid = os.urandom(16)
self.uuid = uuid
return uuid
def getTwoIDs(self):
""" Return a tuple of two sorted UUIDs """
# generate two ptid, first is lower
ptids = self.getNewUUID(), self.getNewUUID()
return min(ptids), max(ptids)
ptid = min(ptids)
def getFakeConnection(self, uuid=None, address=('127.0.0.1', 10000)):
return Mock({
'getUUID': uuid,
'getAddress': address,
})
def checkProtocolErrorRaised(self, method, *args, **kwargs):
""" Check if the ProtocolError exception was raised """
self.assertRaises(protocol.ProtocolError, method, *args, **kwargs)
def checkUnexpectedPacketRaised(self, method, *args, **kwargs):
""" Check if the UnexpectedPacketError exception wxas raised """
self.assertRaises(protocol.UnexpectedPacketError, method, *args, **kwargs)
def checkIdenficationRequired(self, method, *args, **kwargs):
""" Check is the identification_required decorator is applied """
self.checkUnexpectedPacketRaised(method, *args, **kwargs)
def checkBrokenNodeDisallowedErrorRaised(self, method, *args, **kwargs):
""" Check if the BrokenNodeDisallowedError exception wxas raised """
self.assertRaises(protocol.BrokenNodeDisallowedError, method, *args, **kwargs)
def checkNotReadyErrorRaised(self, method, *args, **kwargs):
""" Check if the NotReadyError exception wxas raised """
self.assertRaises(protocol.NotReadyError, method, *args, **kwargs)
def checkAborted(self, conn):
""" Ensure the connection was aborted """
self.assertEquals(len(conn.mockGetNamedCalls('abort')), 1)
def checkNotAborted(self, conn):
""" Ensure the connection was not aborted """
self.assertEquals(len(conn.mockGetNamedCalls('abort')), 0)
def checkClosed(self, conn):
""" Ensure the connection was closed """
self.assertEquals(len(conn.mockGetNamedCalls('close')), 1)
def checkNotClosed(self, conn):
""" Ensure the connection was not closed """
self.assertEquals(len(conn.mockGetNamedCalls('close')), 0)
def checkNoPacketSent(self, conn):
""" check if no packet were sent """
self.assertEquals(len(conn.mockGetNamedCalls('notify')), 0)
self.assertEquals(len(conn.mockGetNamedCalls('answer')), 0)
self.assertEquals(len(conn.mockGetNamedCalls('ask')), 0)
def checkNoUUIDSet(self, conn):
""" ensure no UUID was set on the connection """
self.assertEquals(len(conn.mockGetNamedCalls('setUUID')), 0)
def checkUUIDSet(self, conn, uuid=None):
""" ensure no UUID was set on the connection """
calls = conn.mockGetNamedCalls('setUUID')
self.assertEquals(len(calls), 1)
if uuid is not None:
self.assertEquals(calls[0].getParam(0), uuid)
# in check(Ask|Answer|Notify)Packet we return the packet so it can be used
# in tests if more accurates checks are required
def checkErrorPacket(self, conn, decode=False):
""" Check if an error packet was answered """
calls = conn.mockGetNamedCalls("answer")
self.assertEquals(len(calls), 1)
packet = calls[0].getParam(0)
self.assertTrue(isinstance(packet, protocol.Packet))
self.assertEquals(packet.getType(), protocol.ERROR)
if decode:
return protocol.decode_table[packet.getType()](packet._body)
return packet
def checkAskPacket(self, conn, packet_type, decode=False):
""" Check if an ask-packet with the right type is sent """
calls = conn.mockGetNamedCalls('ask')
self.assertEquals(len(calls), 1)
packet = calls[0].getParam(0)
self.assertTrue(isinstance(packet, protocol.Packet))
self.assertEquals(packet.getType(), packet_type)
if decode:
return protocol.decode_table[packet.getType()](packet._body)
return packet
def checkAnswerPacket(self, conn, packet_type, answered_packet=None, decode=False):
""" Check if an answer-packet with the right type is sent """
calls = conn.mockGetNamedCalls('answer')
self.assertEquals(len(calls), 1)
packet = calls[0].getParam(0)
self.assertTrue(isinstance(packet, protocol.Packet))
self.assertEquals(packet.getType(), packet_type)
if answered_packet is not None:
a_packet = calls[0].getParam(1)
self.assertEquals(a_packet, answered_packet)
self.assertEquals(a_packet.getId(), answered_packet.getId())
if decode:
return protocol.decode_table[packet.getType()](packet._body)
return packet
def checkNotifyPacket(self, conn, packet_type, packet_number=0, decode=False):
""" Check if a notify-packet with the right type is sent """
calls = conn.mockGetNamedCalls('notify')
self.assertTrue(len(calls) > packet_number)
packet = calls[packet_number].getParam(0)
self.assertTrue(isinstance(packet, protocol.Packet))
self.assertEquals(packet.getType(), packet_type)
if decode:
return protocol.decode_table[packet.getType()](packet._body)
return packet
def checkNotifyNodeInformation(self, conn, **kw):
return self.checkNotifyPacket(conn, protocol.NOTIFY_NODE_INFORMATION, **kw)
def checkSendPartitionTable(self, conn, **kw):
return self.checkNotifyPacket(conn, protocol.SEND_PARTITION_TABLE, **kw)
def checkStartOperation(self, conn, **kw):
return self.checkNotifyPacket(conn, protocol.START_OPERATION, **kw)
def checkNotifyTransactionFinished(self, conn, **kw):
return self.checkNotifyPacket(conn, protocol.NOTIFY_TRANSACTION_FINISHED, **kw)
def checkNotifyInformationLocked(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.NOTIFY_INFORMATION_LOCKED, **kw)
def checkLockInformation(self, conn, **kw):
return self.checkAskPacket(conn, protocol.LOCK_INFORMATION, **kw)
def checkUnlockInformation(self, conn, **kw):
return self.checkAskPacket(conn, protocol.UNLOCK_INFORMATION, **kw)
def checkRequestNodeIdentification(self, conn, **kw):
return self.checkAskPacket(conn, protocol.REQUEST_NODE_IDENTIFICATION, **kw)
def checkAskPrimaryMaster(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_PRIMARY_MASTER)
def checkAskUnfinishedTransactions(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_UNFINISHED_TRANSACTIONS)
def checkAskTransactionInformation(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_TRANSACTION_INFORMATION, **kw)
def checkAskObjectPresent(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_OBJECT_PRESENT, **kw)
def checkAskObject(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_OBJECT, **kw)
def checkAskStoreObject(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_STORE_OBJECT, **kw)
def checkAskStoreTransaction(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_STORE_TRANSACTION, **kw)
def checkFinishTransaction(self, conn, **kw):
return self.checkAskPacket(conn, protocol.FINISH_TRANSACTION, **kw)
def checkAskNewTid(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_NEW_TID, **kw)
def checkAskLastIDs(self, conn, **kw):
return self.checkAskPacket(conn, protocol.ASK_LAST_IDS, **kw)
def checkAcceptNodeIdentification(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ACCEPT_NODE_IDENTIFICATION, **kw)
def checkAnswerPrimaryMaster(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_PRIMARY_MASTER, **kw)
def checkAnswerLastIDs(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_LAST_IDS, **kw)
def checkAnswerUnfinishedTransactions(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_UNFINISHED_TRANSACTIONS, **kw)
def checkAnswerObject(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_OBJECT, **kw)
def checkAnswerTransactionInformation(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_TRANSACTION_INFORMATION, **kw)
def checkAnswerTids(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_TIDS, **kw)
def checkAnswerObjectHistory(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_OBJECT_HISTORY, **kw)
def checkAnswerStoreTransaction(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_STORE_TRANSACTION, **kw)
def checkAnswerStoreObject(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_STORE_OBJECT, **kw)
def checkAnswerOids(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_OIDS, **kw)
def checkAnswerPartitionTable(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_PARTITION_TABLE, **kw)
def checkAnswerObjectPresent(self, conn, **kw):
return self.checkAnswerPacket(conn, protocol.ANSWER_OBJECT_PRESENT, **kw)
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/testConfig.py 0000664 0000000 0000000 00000010151 11227104631 0024041 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, os
from mock import Mock
from neo.protocol import RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, BROKEN_STATE, \
MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, INVALID_UUID
from neo.config import ConfigurationManager
from tempfile import mkstemp
class ConfigurationManagerTests(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def getNewUUID(self):
uuid = INVALID_UUID
while uuid == INVALID_UUID:
uuid = os.urandom(16)
self.uuid = uuid
return uuid
def test_01_configuration_manager(self):
# initialisation
#create a fake configuration file
config_file_text = """# Default parameters.
[DEFAULT]
# The list of master nodes.
master_nodes: 127.0.0.2:10010 127.0.0.2
# The number of replicas.
replicas: 25
# The number of partitions.
partitions: 243125
# The name of this cluster.
name: unittest
# The user name for the database.
user: neotest
# The password for the database.
password: neotest
connector : SocketTestConnector
# The first master.
[mastertest]
server: 127.0.0.1:15010
# The first storage.
[storage1]
database: neotest1
server: 127.0.0.5
# The second storage.
[storage2]
database: neotest2
server: 127.0.0.1:15021
"""
tmp_id, self.tmp_path = mkstemp()
tmp_file = os.fdopen(tmp_id, "w+b")
tmp_file.write(config_file_text)
tmp_file.close()
config = ConfigurationManager(self.tmp_path, "mastertest")
self.assertNotEqual(config.parser, None)
self.assertEqual(config.section, "mastertest")
# some values will be get from default config into class
self.assertEqual(config.getDatabase(), "test")
self.assertEqual(config.getUser(), "neotest")
self.assertEqual(config.getPassword(), "neotest")
self.assertEqual(config.getServer(), ("127.0.0.1", 15010))
self.assertEqual(config.getReplicas(), 25)
self.assertEqual(config.getPartitions(), 243125)
self.assertEqual(config.getConnector(), "SocketTestConnector")
self.assertEqual(config.getName(), "unittest")
self.assertEqual(len(config.getMasterNodeList()), 2)
node_list = config.getMasterNodeList()
self.failUnless(("127.0.0.2", 10010) in node_list)
self.failUnless(("127.0.0.2", 10100) in node_list)
# test with a storage where no port is defined, must get the default one
config = ConfigurationManager(self.tmp_path, "storage1")
self.assertNotEqual(config.parser, None)
self.assertEqual(config.section, "storage1")
# some values will be get from default config into class
self.assertEqual(config.getDatabase(), "neotest1")
self.assertEqual(config.getUser(), "neotest")
self.assertEqual(config.getPassword(), "neotest")
self.assertEqual(config.getServer(), ("127.0.0.5", 10100))
self.assertEqual(config.getReplicas(), 25)
self.assertEqual(config.getPartitions(), 243125)
self.assertEqual(config.getConnector(), "SocketTestConnector")
self.assertEqual(config.getName(), "unittest")
self.assertEqual(len(config.getMasterNodeList()), 2)
node_list = config.getMasterNodeList()
self.failUnless(("127.0.0.2", 10010) in node_list)
self.failUnless(("127.0.0.2", 10100) in node_list)
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/testConnection.py 0000664 0000000 0000000 00000145735 11227104631 0024754 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, os
from mock import Mock
from neo import protocol
from neo.protocol import RUNNING_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, BROKEN_STATE, \
MASTER_NODE_TYPE, STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, INVALID_UUID
from neo.node import Node, MasterNode, StorageNode, ClientNode, NodeManager
from time import time
from neo.connection import BaseConnection, ListeningConnection, Connection, \
ClientConnection, ServerConnection, MTClientConnection, MTServerConnection
from neo.connector import getConnectorHandler, registerConnectorHandler
from neo.handler import EventHandler
from neo.master.tests.connector import DoNothingConnector
from neo.connector import ConnectorException, ConnectorTryAgainException, \
ConnectorInProgressException, ConnectorConnectionRefusedException
from neo.protocol import Packet, ProtocolError, PROTOCOL_ERROR_CODE, ERROR,INTERNAL_ERROR_CODE, \
ANSWER_PRIMARY_MASTER
from neo import protocol
def getNewUUID():
uuid = INVALID_UUID
while uuid == INVALID_UUID:
uuid = os.urandom(16)
return uuid
class ConnectionTests(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_01_BaseConnection(self):
em = Mock() #EpollEventManager()
handler = EventHandler()
# no connector
bc = BaseConnection(em, handler)
self.assertNotEqual(bc.em, None)
self.assertEqual(bc.handler, handler)
self.assertNotEqual(bc.getEventManager(), None)
self.assertEqual(bc.getHandler(), handler)
self.assertEqual(bc.getUUID(), None)
self.assertEqual(bc.lock(), 1)
self.assertEqual(bc.unlock(), None)
self.assertEqual(bc.getAddress(), None)
self.assertEqual(bc.getConnector(), None)
self.assertRaises(NotImplementedError, bc.readable)
self.assertRaises(NotImplementedError, bc.writable)
self.assertEqual(bc.connector, None)
self.assertEqual(bc.addr, None)
self.assertEqual(bc.connector_handler, None)
# init with connector but no handler
registerConnectorHandler(DoNothingConnector)
connector = getConnectorHandler("DoNothingConnector")()
self.assertNotEqual(connector, None)
em = Mock()
bc = BaseConnection(em, handler, connector=connector)
self.assertNotEqual(bc.connector, None)
self.assertNotEqual(bc.getConnector(), None)
self.assertEqual(bc.connector_handler, DoNothingConnector)
# check it registered the connection in epoll
self.assertEquals(len(em.mockGetNamedCalls("register")), 1)
call = em.mockGetNamedCalls("register")[0]
conn = call.getParam(0)
self.assertEquals(conn, bc)
# init just with handler
em = Mock()
bc = BaseConnection(em, handler, connector_handler=DoNothingConnector)
self.assertEqual(bc.getConnector(), None)
self.assertEqual(bc.connector_handler, DoNothingConnector)
self.assertEquals(len(em.mockGetNamedCalls("register")), 0)
# add connector
connector = bc.connector_handler()
bc.setConnector(connector)
self.failUnless(isinstance(bc.getConnector(), DoNothingConnector))
self.assertNotEqual(bc.getConnector(), None)
# check it registered the connection in epoll
self.assertEquals(len(em.mockGetNamedCalls("register")), 1)
call = em.mockGetNamedCalls("register")[0]
conn = call.getParam(0)
self.assertEquals(conn, bc)
# init with address
connector = DoNothingConnector()
em = Mock()
handler = EventHandler()
bc = BaseConnection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
# check it registered the connection in epoll
self.assertEquals(len(em.mockGetNamedCalls("register")), 1)
call = em.mockGetNamedCalls("register")[0]
conn = call.getParam(0)
self.assertEquals(conn, bc)
def test_02_ListeningConnection(self):
# test init part
em = Mock()
handler = Mock()
def getNewConnection(self):
return self, "127.0.0.1"
DoNothingConnector.getNewConnection = getNewConnection
bc = ListeningConnection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
self.assertEqual(len(em.mockGetNamedCalls("register")), 1)
self.assertEqual(len(em.mockGetNamedCalls("addReader")), 1)
self.assertNotEqual(bc.getConnector(), None)
connector = bc.getConnector()
self.assertEqual(len(connector.mockGetNamedCalls("makeListeningConnection")), 1)
# test readable
bc.readable()
self.assertEqual(len(connector.mockGetNamedCalls("getNewConnection")), 1)
self.assertEqual(len(handler.mockGetNamedCalls("connectionAccepted")), 1)
# test with exception raise when getting new connection
em = Mock()
handler = Mock()
def getNewConnection(self):
raise ConnectorTryAgainException
DoNothingConnector.getNewConnection = getNewConnection
bc = ListeningConnection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
self.assertEqual(len(em.mockGetNamedCalls("register")), 1)
self.assertEqual(len(em.mockGetNamedCalls("addReader")), 1)
self.assertNotEqual(bc.getConnector(), None)
connector = bc.getConnector()
self.assertEqual(len(connector.mockGetNamedCalls("makeListeningConnection")), 1)
# test readable
bc.readable()
self.assertEqual(len(connector.mockGetNamedCalls("getNewConnection")), 1)
self.assertEqual(len(handler.mockGetNamedCalls("connectionAccepted")), 0)
def test_03_Connection(self):
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
self.assertEqual(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEqual(bc.getConnector(), None)
self.assertEqual(bc.read_buf, '')
self.assertEqual(bc.write_buf, '')
self.assertEqual(bc.cur_id, 0)
self.assertEqual(bc.event_dict, {})
self.assertEqual(bc.aborted, False)
# test uuid
self.assertEqual(bc.uuid, None)
self.assertEqual(bc.getUUID(), None)
uuid = getNewUUID()
bc.setUUID(uuid)
self.assertEqual(bc.getUUID(), uuid)
# test next id
cur_id = bc.cur_id
next_id = bc._getNextId()
self.assertEqual(next_id, cur_id)
next_id = bc._getNextId()
self.failUnless(next_id > cur_id)
# test overflow of next id
bc.cur_id = 0xffffffff
next_id = bc._getNextId()
self.assertEqual(next_id, 0xffffffff)
next_id = bc._getNextId()
self.assertEqual(next_id, 0)
# test abort
bc.abort()
self.assertEqual(bc.aborted, True)
self.assertRaises(NotImplementedError, bc.isServerConnection)
def test_04_Connection_pending(self):
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
self.assertEqual(bc.connector, None)
# no connector and no buffer
self.assertFalse(bc.pending())
# no connector but buffer
bc.write_buf += '1'
self.assertFalse(bc.pending())
# connector with no buffer
conn = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=conn, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
self.assertNotEqual(bc.connector, None)
self.assertFalse(bc.pending())
# connector and buffer
bc.write_buf += '1'
self.assertTrue(bc.pending())
def test_05_Connection_recv(self):
em = Mock()
handler = Mock()
# patch receive method to return data
def receive(self):
return "testdata"
DoNothingConnector.receive = receive
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertNotEqual(bc.getConnector(), None)
bc._recv()
self.assertEqual(bc.read_buf, "testdata")
# patch receive method to raise try again
def receive(self):
raise ConnectorTryAgainException
DoNothingConnector.receive = receive
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertNotEqual(bc.getConnector(), None)
bc._recv()
self.assertEqual(bc.read_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# patch receive method to raise ConnectorConnectionRefusedException
def receive(self):
raise ConnectorConnectionRefusedException
DoNothingConnector.receive = receive
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertNotEqual(bc.getConnector(), None)
# fake client connection instance with connecting attribute
bc.connecting = True
bc._recv()
self.assertEqual(bc.read_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 1)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 1)
# patch receive method to raise any other connector error
def receive(self):
raise ConnectorException
DoNothingConnector.receive = receive
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertNotEqual(bc.getConnector(), None)
self.assertRaises(ConnectorException, bc._recv)
self.assertEqual(bc.read_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 1)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 2)
def test_06_Connection_send(self):
# no data, nothing done
em = Mock()
handler = Mock()
# patch receive method to return data
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# send all data
def send(self, data):
return len(data)
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# send part of the data
def send(self, data):
return len(data)/2
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, "data")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# send multiple packet
def send(self, data):
return len(data)
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata" + "second" + "third"
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdatasecondthird")
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# send part of multiple packet
def send(self, data):
return len(data)/2
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata" + "second" + "third"
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdatasecondthird")
self.assertEqual(bc.write_buf, "econdthird")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# raise try again
def send(self, data):
raise ConnectorTryAgainException
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata" + "second" + "third"
self.assertNotEqual(bc.getConnector(), None)
bc._send()
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdatasecondthird")
self.assertEqual(bc.write_buf, "testdata" + "second" + "third")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# raise other error
def send(self, data):
raise ConnectorException
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata" + "second" + "third"
self.assertNotEqual(bc.getConnector(), None)
self.assertRaises(ConnectorException, bc._send)
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdatasecondthird")
# connection closed -> buffers flushed
self.assertEqual(bc.write_buf, "")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 1)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 1)
def test_07_Connection_addPacket(self):
# no connector
p = Mock({"encode" : "testdata"})
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getConnector(), None)
self.assertEqual(bc.write_buf, '')
bc._addPacket(p)
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 0)
# new packet
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
self.assertNotEqual(bc.getConnector(), None)
bc._addPacket(p)
self.assertEqual(bc.write_buf, "testdata")
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 1)
# packet witch raise protocol error
# change the max packet size and create a to big message
# be careful not to set the max packet size < error message
# this part of the test is disabled because the case where a too big
# message is send is handled in protocol.Packet.encode
# master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2135, getNewUUID()),
# ("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
# ("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
# ("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
# p = protocol.answerPrimaryMaster(getNewUUID(), master_list)
# p.setId(1)
# OLD_MAX_PACKET_SIZE = protocol.MAX_PACKET_SIZE
# protocol.MAX_PACKET_SIZE = 0x55
#
# connector = DoNothingConnector()
# bc = Connection(em, handler, connector_handler=DoNothingConnector,
# connector=connector, addr=("127.0.0.7", 93413))
# self.assertEqual(bc.write_buf, '')
# self.assertNotEqual(bc.getConnector(), None)
# import pdb
# pdb.set_trace()
# bc._addPacket(p)
# self.assertNotEqual(bc.write_buf, "testdata")
# self.assertRaises(ProtocolError, p.encode)
# self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 2)
# # check it sends error packet
# packet = Packet.parse(bc.write_buf)
# self.assertEqual(packet.getType(), ERROR)
# code, message = packet.decode()
# self.assertEqual(code, INTERNAL_ERROR_CODE)
# self.assertEqual(message, "internal error: message too big (206)")
# # reset value
# protocol.MAX_PACKET_SIZE = OLD_MAX_PACKET_SIZE
def test_08_Connection_expectMessage(self):
# no connector -> nothing is done
p = Mock()
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=None, addr=("127.0.0.7", 93413))
self.assertEqual(bc.getConnector(), None)
self.assertEqual(len(bc.event_dict), 0)
bc.expectMessage('1')
self.assertEqual(len(bc.event_dict), 0)
self.assertEquals(len(em.mockGetNamedCalls("addIdleEvent")), 0)
# with a right connector -> event created
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertNotEqual(bc.getConnector(), None)
self.assertEqual(len(bc.event_dict), 0)
bc.expectMessage('1')
self.assertEqual(len(bc.event_dict), 1)
self.assertEquals(len(em.mockGetNamedCalls("addIdleEvent")), 1)
def test_09_Connection_analyse(self):
# nothing to read, nothing is done
p = Mock()
em = Mock()
handler = Mock()
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertEqual(len(bc.event_dict), 0)
bc.analyse()
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 0)
self.assertEqual(bc.read_buf, '')
self.assertEqual(len(bc.event_dict), 0)
# give some data to analyse
master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2135, getNewUUID()),
("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
p = protocol.answerPrimaryMaster(getNewUUID(), master_list)
p.setId(1)
data = p.encode()
bc.read_buf += data
self.assertEqual(len(bc.event_dict), 0)
bc.analyse()
# check packet decoded
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 1)
call = handler.mockGetNamedCalls("packetReceived")[0]
data = call.getParam(1)
self.assertEqual(data.getType(), p.getType())
self.assertEqual(data.getId(), p.getId())
self.assertEqual(data.decode(), p.decode())
self.assertEqual(len(bc.event_dict), 0)
self.assertEqual(bc.read_buf, '')
# give multiple packet
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
# packet 1
master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2135, getNewUUID()),
("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
p1 = protocol.answerPrimaryMaster(getNewUUID(), master_list)
p1.setId(1)
data = p1.encode()
bc.read_buf += data
# packet 2
master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2135, getNewUUID()),
("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
p2 = protocol.answerPrimaryMaster( getNewUUID(), master_list)
p2.setId(2)
data = p2.encode()
bc.read_buf += data
self.assertEqual(len(bc.read_buf), len(p1.encode()) + len(p2.encode()))
self.assertEqual(len(bc.event_dict), 0)
bc.analyse()
# check two packets decoded
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 2)
# packet 1
call = handler.mockGetNamedCalls("packetReceived")[0]
data = call.getParam(1)
self.assertEqual(data.getType(), p1.getType())
self.assertEqual(data.getId(), p1.getId())
self.assertEqual(data.decode(), p1.decode())
# packet 2
call = handler.mockGetNamedCalls("packetReceived")[1]
data = call.getParam(1)
self.assertEqual(data.getType(), p2.getType())
self.assertEqual(data.getId(), p2.getId())
self.assertEqual(data.decode(), p2.decode())
self.assertEqual(len(bc.event_dict), 0)
self.assertEqual(len(bc.read_buf), 0)
# give a bad packet, won't be decoded
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
bc.read_buf += "datadatadatadata"
self.assertEqual(len(bc.read_buf), 16)
self.assertEqual(len(bc.event_dict), 0)
bc.analyse()
self.assertEqual(len(bc.read_buf), 16)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 0)
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 0)
# give an expected packet
em = Mock()
handler = Mock()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2135, getNewUUID()),
("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
p = protocol.answerPrimaryMaster(getNewUUID(), master_list)
p.setId(1)
data = p.encode()
bc.read_buf += data
self.assertEqual(len(bc.event_dict), 0)
bc.expectMessage(1)
self.assertEqual(len(bc.event_dict), 1)
bc.analyse()
# check packet decoded
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 1)
call = handler.mockGetNamedCalls("packetReceived")[0]
data = call.getParam(1)
self.assertEqual(data.getType(), p.getType())
self.assertEqual(data.getId(), p.getId())
self.assertEqual(data.decode(), p.decode())
self.assertEqual(len(bc.event_dict), 0)
self.assertEqual(bc.read_buf, '')
def test_10_Connection_writable(self):
# with pending operation after send
em = Mock()
handler = Mock()
def send(self, data):
return len(data)/2
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertNotEqual(bc.getConnector(), None)
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
bc.writable()
# test send was called
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, "data")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# pending, so nothing called
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 0)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("shutdown")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("close")), 0)
# with no longer pending operation after send
em = Mock()
handler = Mock()
def send(self, data):
return len(data)
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertNotEqual(bc.getConnector(), None)
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
bc.writable()
# test send was called
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
# nothing else pending, and aborted is false, so writer has been removed
self.assertFalse(bc.pending())
self.assertFalse(bc.aborted)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("shutdown")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("close")), 0)
# with no longer pending operation after send and aborted set to true
em = Mock()
handler = Mock()
def send(self, data):
return len(data)
DoNothingConnector.send = send
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertNotEqual(bc.getConnector(), None)
self.assertTrue(bc.pending())
bc.abort()
self.assertTrue(bc.aborted)
bc.writable()
# test send was called
self.assertEquals(len(connector.mockGetNamedCalls("send")), 1)
call = connector.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 1)
# nothing else pending, and aborted is false, so writer has been removed
self.assertFalse(bc.pending())
self.assertTrue(bc.aborted)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 1)
self.assertEquals(len(connector.mockGetNamedCalls("shutdown")), 1)
self.assertEquals(len(connector.mockGetNamedCalls("close")), 1)
def test_11_Connection_readable(self):
# With aborted set to false
em = Mock()
handler = Mock()
# patch receive method to return data
def receive(self):
master_list = (("127.0.0.1", 2135, getNewUUID()), ("127.0.0.1", 2136, getNewUUID()),
("127.0.0.1", 2235, getNewUUID()), ("127.0.0.1", 2134, getNewUUID()),
("127.0.0.1", 2335, getNewUUID()),("127.0.0.1", 2133, getNewUUID()),
("127.0.0.1", 2435, getNewUUID()),("127.0.0.1", 2132, getNewUUID()))
uuid = getNewUUID()
p = protocol.answerPrimaryMaster(uuid, master_list)
p.setId(1)
data = p.encode()
return data
DoNothingConnector.receive = receive
connector = DoNothingConnector()
bc = Connection(em, handler, connector_handler=DoNothingConnector,
connector=connector, addr=("127.0.0.7", 93413))
self.assertEqual(bc.read_buf, '')
self.assertNotEqual(bc.getConnector(), None)
self.assertFalse(bc.aborted)
bc.readable()
# check packet decoded
self.assertEqual(bc.read_buf, '')
self.assertEquals(len(em.mockGetNamedCalls("removeIdleEvent")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("packetReceived")), 1)
call = handler.mockGetNamedCalls("packetReceived")[0]
data = call.getParam(1)
self.assertEqual(data.getType(), ANSWER_PRIMARY_MASTER)
self.assertEqual(data.getId(), 1)
self.assertEqual(len(bc.event_dict), 0)
self.assertEqual(bc.read_buf, '')
# check not aborted
self.assertFalse(bc.aborted)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 0)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("shutdown")), 0)
self.assertEquals(len(connector.mockGetNamedCalls("close")), 0)
def test_12_ClientConnection_init(self):
makeClientConnection_org = DoNothingConnector.makeClientConnection
# create a good client connection
em = Mock()
handler = Mock()
connector = DoNothingConnector()
bc = ClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
# check connector created and connection initialize
self.assertFalse(bc.connecting)
self.assertFalse(bc.isServerConnection())
self.assertNotEqual(bc.getConnector(), None)
conn = bc.getConnector()
self.assertEquals(len(conn.mockGetNamedCalls("makeClientConnection")), 1)
call = conn.mockGetNamedCalls("makeClientConnection")[0]
data = call.getParam(0)
self.assertEqual(data, ("127.0.0.7", 93413))
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 0)
# raise connection in progress
def makeClientConnection(self, *args, **kw):
raise ConnectorInProgressException
em = Mock()
handler = Mock()
connector = DoNothingConnector()
DoNothingConnector.makeClientConnection = makeClientConnection
try:
bc = ClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
finally:
DoNothingConnector.makeClientConnection = makeClientConnection_org
# check connector created and connection initialize
self.assertTrue(bc.connecting)
self.assertFalse(bc.isServerConnection())
self.assertNotEqual(bc.getConnector(), None)
conn = bc.getConnector()
self.assertEquals(len(conn.mockGetNamedCalls("makeClientConnection")), 1)
call = conn.mockGetNamedCalls("makeClientConnection")[0]
data = call.getParam(0)
self.assertEqual(data, ("127.0.0.7", 93413))
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 1)
# raise another error, connection must fail
def makeClientConnection(self, *args, **kw):
raise ConnectorException
em = Mock()
handler = Mock()
connector = DoNothingConnector()
DoNothingConnector.makeClientConnection = makeClientConnection
try:
self.assertRaises(ConnectorException, ClientConnection, em, handler,
connector_handler=DoNothingConnector, addr=("127.0.0.7", 93413))
finally:
DoNothingConnector.makeClientConnection = makeClientConnection_org
# since the exception was raised, the connection is not created
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 1)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 0)
def test_13_ClientConnection_writable(self):
# with a non connecting connection, will call parent's method
em = Mock()
handler = Mock()
def makeClientConnection(self, *args, **kw):
return "OK"
def send(self, data):
return len(data)
makeClientConnection_org = DoNothingConnector.makeClientConnection
send_org = DoNothingConnector.send
DoNothingConnector.send = send
DoNothingConnector.makeClientConnection = makeClientConnection
try:
bc = ClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
finally:
DoNothingConnector.send = send_org
DoNothingConnector.makeClientConnection = makeClientConnection_org
# check connector created and connection initialize
self.assertFalse(bc.connecting)
self.assertNotEqual(bc.getConnector(), None)
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
# call
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
bc.writable()
conn = bc.getConnector()
self.assertFalse(bc.pending())
self.assertFalse(bc.aborted)
self.assertFalse(bc.connecting)
self.assertEquals(len(conn.mockGetNamedCalls("send")), 1)
call = conn.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
self.assertEqual(bc.write_buf, '')
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("shutdown")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("close")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("send")), 1)
call = conn.mockGetNamedCalls("send")[0]
data = call.getParam(0)
self.assertEquals(data, "testdata")
# with a connecting connection, must not call parent's method
# with no error, just complete connection
em = Mock()
handler = Mock()
def getError(self):
return None
DoNothingConnector.getError = getError
bc = ClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
# check connector created and connection initialize
bc.connecting = True
self.assertNotEqual(bc.getConnector(), None)
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
# call
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
bc.writable()
conn = bc.getConnector()
self.assertFalse(bc.connecting)
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
self.assertEqual(bc.write_buf, "testdata")
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 2)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 2)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 0)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("send")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("shutdown")), 0)
self.assertEquals(len(conn.mockGetNamedCalls("close")), 0)
# with a connecting connection, must not call parent's method
# with errors, close connection
em = Mock()
handler = Mock()
def getError(self):
return True
DoNothingConnector.getError = getError
bc = ClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
# check connector created and connection initialize
bc.connecting = True
self.assertNotEqual(bc.getConnector(), None)
self.assertEqual(bc.write_buf, '')
bc.write_buf = "testdata"
self.assertTrue(bc.pending())
self.assertFalse(bc.aborted)
# call
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
bc.writable()
self.assertEqual(bc.getConnector(), None)
self.assertTrue(bc.connecting)
self.assertFalse(bc.pending())
self.assertFalse(bc.aborted)
self.assertEqual(bc.write_buf, "") # buffer flushed at closure
self.assertEquals(len(handler.mockGetNamedCalls("connectionClosed")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 1)
self.assertEquals(len(em.mockGetNamedCalls("unregister")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeWriter")), 1)
self.assertEquals(len(em.mockGetNamedCalls("removeReader")), 1)
def test_14_ServerConnection(self):
em = Mock()
handler = Mock()
bc = ServerConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
self.assertEqual(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEqual(bc.getConnector(), None)
self.assertEqual(bc.read_buf, '')
self.assertEqual(bc.write_buf, '')
self.assertEqual(bc.cur_id, 0)
self.assertEqual(bc.event_dict, {})
self.assertEqual(bc.aborted, False)
# test uuid
self.assertEqual(bc.uuid, None)
self.assertEqual(bc.getUUID(), None)
uuid = getNewUUID()
bc.setUUID(uuid)
self.assertEqual(bc.getUUID(), uuid)
# test next id
cur_id = bc.cur_id
next_id = bc._getNextId()
self.assertEqual(next_id, cur_id)
next_id = bc._getNextId()
self.failUnless(next_id > cur_id)
# test overflow of next id
bc.cur_id = 0xffffffff
next_id = bc._getNextId()
self.assertEqual(next_id, 0xffffffff)
next_id = bc._getNextId()
self.assertEqual(next_id, 0)
# test abort
bc.abort()
self.assertEqual(bc.aborted, True)
self.assertTrue(bc.isServerConnection())
def test_15_MTClientConnection(self):
makeClientConnection_org = DoNothingConnector.makeClientConnection
# same as ClientConnection, except definition of some lock
# create a good client connection
em = Mock()
handler = Mock()
connector = DoNothingConnector()
bc = MTClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
# check connector created and connection initialize
self.assertFalse(bc.connecting)
self.assertFalse(bc.isServerConnection())
self.assertNotEqual(bc.getConnector(), None)
conn = bc.getConnector()
self.assertEquals(len(conn.mockGetNamedCalls("makeClientConnection")), 1)
call = conn.mockGetNamedCalls("makeClientConnection")[0]
data = call.getParam(0)
self.assertEqual(data, ("127.0.0.7", 93413))
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 1)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 0)
# raise connection in progress
def makeClientConnection(self, *args, **kw):
raise ConnectorInProgressException
em = Mock()
handler = Mock()
connector = DoNothingConnector()
DoNothingConnector.makeClientConnection = makeClientConnection
try:
bc = MTClientConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
finally:
DoNothingConnector.makeClientConnection = makeClientConnection_org
# check connector created and connection initialize
self.assertTrue(bc.connecting)
self.assertFalse(bc.isServerConnection())
self.assertNotEqual(bc.getConnector(), None)
conn = bc.getConnector()
self.assertEquals(len(conn.mockGetNamedCalls("makeClientConnection")), 1)
call = conn.mockGetNamedCalls("makeClientConnection")[0]
data = call.getParam(0)
self.assertEqual(data, ("127.0.0.7", 93413))
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 0)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 1)
# raise another error, connection must fail
def makeClientConnection(self, *args, **kw):
raise ConnectorException
em = Mock()
handler = Mock()
connector = DoNothingConnector()
DoNothingConnector.makeClientConnection = makeClientConnection
try:
self.assertRaises(ConnectorException, MTClientConnection, em, handler,
connector_handler=DoNothingConnector, addr=("127.0.0.7", 93413))
finally:
DoNothingConnector.makeClientConnection = makeClientConnection_org
# the connection is not created
# check call to handler
self.assertNotEqual(bc.getHandler(), None)
self.assertEquals(len(handler.mockGetNamedCalls("connectionStarted")), 1)
self.assertEquals(len(handler.mockGetNamedCalls("connectionCompleted")), 0)
self.assertEquals(len(handler.mockGetNamedCalls("connectionFailed")), 1)
# check call to event manager
self.assertNotEqual(bc.getEventManager(), None)
self.assertEquals(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEquals(len(em.mockGetNamedCalls("addWriter")), 0)
# XXX check locking ?
def test_16_MTServerConnection(self):
em = Mock()
handler = Mock()
bc = MTServerConnection(em, handler, connector_handler=DoNothingConnector,
addr=("127.0.0.7", 93413))
self.assertEqual(bc.getAddress(), ("127.0.0.7", 93413))
self.assertEqual(len(em.mockGetNamedCalls("addReader")), 0)
self.assertEqual(bc.getConnector(), None)
self.assertEqual(bc.read_buf, '')
self.assertEqual(bc.write_buf, '')
self.assertEqual(bc.cur_id, 0)
self.assertEqual(bc.event_dict, {})
self.assertEqual(bc.aborted, False)
# test uuid
self.assertEqual(bc.uuid, None)
self.assertEqual(bc.getUUID(), None)
uuid = getNewUUID()
bc.setUUID(uuid)
self.assertEqual(bc.getUUID(), uuid)
# test next id
bc._lock = Mock({'_is_owned': True})
cur_id = bc.cur_id
next_id = bc._getNextId()
self.assertEqual(next_id, cur_id)
next_id = bc._getNextId()
self.failUnless(next_id > cur_id)
# test overflow of next id
bc.cur_id = 0xffffffff
next_id = bc._getNextId()
self.assertEqual(next_id, 0xffffffff)
next_id = bc._getNextId()
self.assertEqual(next_id, 0)
# test abort
bc.abort()
self.assertEqual(bc.aborted, True)
self.assertTrue(bc.isServerConnection())
# XXX check locking ???
if __name__ == '__main__':
unittest.main()
neoppod-078c7786374475060d0f338fb18521a32282c5d4/neo/tests/testEvent.py 0000664 0000000 0000000 00000023517 11227104631 0023727 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2009 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import unittest, os
from mock import Mock
from time import time
from neo.tests.base import NeoTestBase
from neo.epoll import Epoll
from neo.event import EpollEventManager, IdleEvent
from neo.protocol import Packet, PING
class EventTests(NeoTestBase):
def setUp(self):
pass
def tearDown(self):
pass
def test_01_EpollEventManager(self):
# init one
em = EpollEventManager()
self.assertEqual(len(em.connection_dict), 0)
self.assertEqual(len(em.reader_set), 0)
self.assertEqual(len(em.writer_set), 0)
self.assertEqual(len(em.event_list), 0)
self.failUnless(em.prev_time