Commit 86819c9c authored by Grégory Wisniewski's avatar Grégory Wisniewski

Partial rewrite of Node and NodeManager classes:

* Manager now index per node type and node state
* Use set() instead of list() to ensure node unicity
* Rename 'server' attribute to 'address' for consistency
* Remove 'filter' parameter from clear(), because it's never used.
* Update tests 


git-svn-id: https://svn.erp5.org/repos/neo/trunk@1317 71dcc9de-d417-0410-9af5-da40c76e7ee4
parent 053eaa8b
...@@ -21,59 +21,60 @@ from neo import logging ...@@ -21,59 +21,60 @@ from neo import logging
from neo import protocol from neo import protocol
from neo.util import dump from neo.util import dump
# TODO:
# Node requires a manager
# Index nodes per node type, server, uuid and state
# A subclass should not tell it's own type
class Node(object): class Node(object):
"""This class represents a node.""" """This class represents a node."""
def __init__(self, manager, server=None, uuid=None, state=protocol.UNKNOWN_STATE): def __init__(self, manager, address=None, uuid=None,
self.state = state state=protocol.UNKNOWN_STATE, server=None):
self.server = server # XXX: backward compatibility
self.uuid = uuid if server is not None:
self.manager = manager address = server
self.last_state_change = time() self._state = state
self._address = address
self._uuid = uuid
self._manager = manager
self._last_state_change = time()
def getLastStateChange(self): def getLastStateChange(self):
return self.last_state_change return self._last_state_change
def getState(self): def getState(self):
return self.state return self._state
def setState(self, new_state): def setState(self, new_state):
if self.state != new_state: if self._state == new_state:
self.state = new_state return
self.last_state_change = time() old_state = self._state
self._state = new_state
self._last_state_change = time()
self._manager._updateState(self, old_state)
def setServer(self, server): def setAddress(self, address):
self.manager.unregisterServer(self) old_address = self._address
self.server = server self._address = address
self.manager.registerServer(self) self._manager._updateAddress(self, old_address)
def getServer(self): def getAddress(self):
return self.server return self._address
def setUUID(self, uuid): def setUUID(self, uuid):
self.manager.unregisterUUID(self) old_uuid = self._uuid
self.uuid = uuid self._uuid = uuid
self.manager.registerUUID(self) self._manager._updateUUID(self, old_uuid)
def getUUID(self): def getUUID(self):
return self.uuid return self._uuid
def getType(self): def getType(self):
raise NotImplementedError raise NotImplementedError
def __str__(self): def __repr__(self):
server = self.getServer() return '<%s(uuid=%s, address=%s, state=%s)>' % (
if server is None: self.__class__.__name__,
address, port = None, None self._address,
else: dump(self._uuid),
address, port = server self._state,
uuid = self.getUUID() )
return '%s (%s:%s)' % (dump(uuid), address, port)
def isMaster(self): def isMaster(self):
return isinstance(self, MasterNode) return isinstance(self, MasterNode)
...@@ -87,30 +88,31 @@ class Node(object): ...@@ -87,30 +88,31 @@ class Node(object):
def isAdmin(self): def isAdmin(self):
return isinstance(self, AdminNode) return isinstance(self, AdminNode)
# XXX: for comptatibility, to be removed
def getType(self):
try:
return NODE_CLASS_MAPPING[self.__class__]
except KeyError:
raise NotImplementedError
getServer = getAddress
setServer = setAddress
class MasterNode(Node): class MasterNode(Node):
"""This class represents a master node.""" """This class represents a master node."""
pass
def getType(self):
return protocol.MASTER_NODE_TYPE
class StorageNode(Node): class StorageNode(Node):
"""This class represents a storage node.""" """This class represents a storage node."""
pass
def getType(self):
return protocol.STORAGE_NODE_TYPE
class ClientNode(Node): class ClientNode(Node):
"""This class represents a client node.""" """This class represents a client node."""
pass
def getType(self):
return protocol.CLIENT_NODE_TYPE
class AdminNode(Node): class AdminNode(Node):
"""This class represents an admin node.""" """This class represents an admin node."""
pass
def getType(self):
return protocol.ADMIN_NODE_TYPE
NODE_TYPE_MAPPING = { NODE_TYPE_MAPPING = {
...@@ -119,77 +121,120 @@ NODE_TYPE_MAPPING = { ...@@ -119,77 +121,120 @@ NODE_TYPE_MAPPING = {
protocol.CLIENT_NODE_TYPE: ClientNode, protocol.CLIENT_NODE_TYPE: ClientNode,
protocol.ADMIN_NODE_TYPE: AdminNode, protocol.ADMIN_NODE_TYPE: AdminNode,
} }
NODE_CLASS_MAPPING = {
StorageNode: protocol.STORAGE_NODE_TYPE,
MasterNode: protocol.MASTER_NODE_TYPE,
ClientNode: protocol.CLIENT_NODE_TYPE,
AdminNode: protocol.ADMIN_NODE_TYPE,
}
class NodeManager(object): class NodeManager(object):
"""This class manages node status.""" """This class manages node status."""
def __init__(self): def __init__(self):
self.node_list = [] self._node_set = set()
self.server_dict = {} self._address_dict = {}
self.uuid_dict = {} self._uuid_dict = {}
self._type_dict = {}
self._state_dict = {}
def add(self, node): def add(self, node):
self.node_list.append(node) if node in self._node_set:
self.registerServer(node)
self.registerUUID(node)
def remove(self, node):
if node is None:
return return
self.node_list.remove(node) self._node_set.add(node)
self.unregisterServer(node) self._updateAddress(node, None)
self.unregisterUUID(node) self._updateUUID(node, None)
self.__updateSet(self._type_dict, None, node.__class__, node)
self.__updateSet(self._state_dict, None, node.getState(), node)
def registerServer(self, node): def remove(self, node):
if node.getServer() is None: if node is None or node not in self._node_set:
return return
self.server_dict[node.getServer()] = node self._node_set.remove(node)
self.__drop(self._address_dict, node.getAddress())
self.__drop(self._uuid_dict, node.getUUID())
self.__dropSet(self._state_dict, node.getState(), node)
self.__dropSet(self._type_dict, node.__class__, node)
def unregisterServer(self, node): def __drop(self, index_dict, key):
if node.getServer() is None:
return
try: try:
del self.server_dict[node.getServer()] del index_dict[key]
except KeyError: except KeyError:
pass pass
def registerUUID(self, node): def __update(self, index_dict, old_key, new_key, node):
if node.getUUID() is None: """ Update an index from old to new key """
return # FIXME: should the old_key always be indexed ?
self.uuid_dict[node.getUUID()] = node if old_key is not None:
del index_dict[old_key]
if new_key is not None:
index_dict[new_key] = node
def unregisterUUID(self, node): def _updateAddress(self, node, old_server):
if node.getUUID() is None: self.__update(self._address_dict, old_server, node.getAddress(), node)
return
try:
del self.uuid_dict[node.getUUID()]
except KeyError:
pass
def getNodeList(self, node_filter=None): def _updateUUID(self, node, old_uuid):
if node_filter is None: self.__update(self._uuid_dict, old_uuid, node.getUUID(), node)
return list(self.node_list)
return filter(node_filter, self.node_list)
def getMasterNodeList(self): def __dropSet(self, set_dict, key, node):
node_filter = lambda node: node.isMaster() if key in set_dict and node in set_dict[key]:
return self.getNodeList(node_filter=node_filter) set_dict[key].remove(node)
def getStorageNodeList(self): def __updateSet(self, set_dict, old_key, new_key, node):
node_filter = lambda node: node.isStorage() """ Update a set index from old to new key """
return self.getNodeList(node_filter=node_filter) # FIXME: should the old_key always be indexed ?
if old_key in set_dict and node in set_dict[old_key]:
set_dict[old_key].remove(node)
if new_key is not None:
set_dict.setdefault(new_key, set()).add(node)
def getClientNodeList(self): def _updateState(self, node, old_state):
node_filter = lambda node: node.isClient() self.__updateSet(self._state_dict, old_state, node.getState(), node)
return self.getNodeList(node_filter=node_filter)
def getNodeByServer(self, server): def getList(self, node_filter=None):
return self.server_dict.get(server) if filter is None:
return list(self._node_set)
return filter(node_filter, self._node_set)
def __getList(self, index_dict, key):
return list(index_dict.setdefault(key, set()))
def getByStateList(self, state):
""" Get a node list filtered per the node state """
return self.__getList(self._state_dict, state)
def __getTypeList(self, type_klass):
return self.__getList(self._type_dict, type_klass)
def getMasterList(self):
""" Return a list with master nodes """
return self.__getTypeList(MasterNode)
def getNodeByUUID(self, uuid): def getStorageList(self):
if uuid is None: """ Return a list with storage nodes """
return None return self.__getTypeList(StorageNode)
return self.uuid_dict.get(uuid)
def getClientList(self):
""" Return a list with client nodes """
return self.__getTypeList(ClientNode)
def getAdminList(self):
""" Return a list with admin nodes """
return self.__getTypeList(AdminNode)
def getByAddress(self, address):
""" Return the node that match with a given address """
return self._address_dict.get(address, None)
def getByUUID(self, uuid):
""" Return the node that match with a given UUID """
return self._uuid_dict.get(uuid, None)
def hasAddress(self, address):
return self._address_dict.get(address, None) is not None
def hasUUID(self, uuid):
return self._uuid_dict.get(uuid, None) is not None
def _createNode(self, klass, **kw): def _createNode(self, klass, **kw):
node = klass(self, **kw) node = klass(self, **kw)
...@@ -200,40 +245,36 @@ class NodeManager(object): ...@@ -200,40 +245,36 @@ class NodeManager(object):
""" Create and register a new master """ """ Create and register a new master """
return self._createNode(MasterNode, **kw) return self._createNode(MasterNode, **kw)
def createStorage(self, *args, **kw): def createStorage(self, **kw):
""" Create and register a new storage """ """ Create and register a new storage """
return self._createNode(StorageNode, **kw) return self._createNode(StorageNode, **kw)
def createClient(self, *args, **kw): def createClient(self, **kw):
""" Create and register a new client """ """ Create and register a new client """
return self._createNode(ClientNode, **kw) return self._createNode(ClientNode, **kw)
def createAdmin(self, *args, **kw): def createAdmin(self, **kw):
""" Create and register a new admin """ """ Create and register a new admin """
return self._createNode(AdminNode, **kw) return self._createNode(AdminNode, **kw)
def createFromNodeType(self, node_type, **kw): def createFromNodeType(self, node_type, **kw):
# XXX: use a static dict or drop this klass = NODE_TYPE_MAPPING.get(node_type)
klass = {
protocol.MASTER_NODE_TYPE: MasterNode,
protocol.STORAGE_NODE_TYPE: StorageNode,
protocol.CLIENT_NODE_TYPE: ClientNode,
protocol.ADMIN_NODE_TYPE: AdminNode,
}.get(node_type)
if klass is None: if klass is None:
raise RuntimeError('Unknown node type : %s' % node_type) raise RuntimeError('Unknown node type : %s' % node_type)
return self._createNode(klass, **kw) return self._createNode(klass, **kw)
def clear(self, filter=None): def clear(self, filter=None):
for node in self.getNodeList(): self._node_set.clear()
if filter is None or filter(node): self._type_dict.clear()
self.remove(node) self._state_dict.clear()
self._uuid_dict.clear()
self._address_dict.clear()
def update(self, node_list): def update(self, node_list):
for node_type, addr, uuid, state in node_list: for node_type, addr, uuid, state in node_list:
# lookup in current table # lookup in current table
node_by_uuid = self.getNodeByUUID(uuid) node_by_uuid = self.getByUUID(uuid)
node_by_addr = self.getNodeByServer(addr) node_by_addr = self.getByAddress(addr)
node = node_by_uuid or node_by_addr node = node_by_uuid or node_by_addr
log_args = (node_type, dump(uuid), addr, state) log_args = (node_type, dump(uuid), addr, state)
...@@ -242,9 +283,9 @@ class NodeManager(object): ...@@ -242,9 +283,9 @@ class NodeManager(object):
logging.debug('drop node %s %s %s %s' % log_args) logging.debug('drop node %s %s %s %s' % log_args)
self.remove(node) self.remove(node)
elif node_by_uuid is not None: elif node_by_uuid is not None:
if node.getServer() != addr: if node.getAddress() != addr:
# address changed, update it # address changed, update it
node.setServer(addr) node.setAddress(addr)
logging.debug('update node %s %s %s %s' % log_args) logging.debug('update node %s %s %s %s' % log_args)
node.setState(state) node.setState(state)
else: else:
...@@ -255,16 +296,16 @@ class NodeManager(object): ...@@ -255,16 +296,16 @@ class NodeManager(object):
klass = NODE_TYPE_MAPPING.get(node_type, None) klass = NODE_TYPE_MAPPING.get(node_type, None)
if klass is None: if klass is None:
raise RuntimeError('Unknown node type') raise RuntimeError('Unknown node type')
node = klass(self, server=addr, uuid=uuid) node = klass(self, address=addr, uuid=uuid)
node.setState(state) node.setState(state)
self.add(node) self.add(node)
logging.info('create node %s %s %s %s' % log_args) logging.info('create node %s %s %s %s' % log_args)
self.log() self.log()
def log(self): def log(self):
logging.debug('Node manager : %d nodes' % len(self.node_list)) logging.debug('Node manager : %d nodes' % len(self._node_set))
node_with_uuid = set(sorted(self.uuid_dict.values())) node_with_uuid = set(sorted(self._uuid_dict.values()))
node_without_uuid = set(self.node_list) - node_with_uuid node_without_uuid = self._node_set - node_with_uuid
for node in node_with_uuid | node_without_uuid: for node in node_with_uuid | node_without_uuid:
if node.getUUID() is not None: if node.getUUID() is not None:
uuid = dump(node.getUUID()) uuid = dump(node.getUUID())
...@@ -276,7 +317,7 @@ class NodeManager(object): ...@@ -276,7 +317,7 @@ class NodeManager(object):
node.getState() node.getState()
) )
logging.debug('nm: %s : %s/%s' % args) logging.debug('nm: %s : %s/%s' % args)
for address, node in sorted(self.server_dict.items()): for address, node in sorted(self._address_dict.items()):
args = ( args = (
address, address,
node.getType(), node.getType(),
...@@ -284,3 +325,12 @@ class NodeManager(object): ...@@ -284,3 +325,12 @@ class NodeManager(object):
) )
logging.debug('nm: %s : %s/%s' % args) logging.debug('nm: %s : %s/%s' % args)
# XXX: backward compatibility, to be removed
getNodeList = getList
getMasterNodeList = getMasterList
getStorageNodeList = getStorageList
getClientNodeList = getClientList
getAdminNodeList = getAdminList
getNodeByUUID = getByUUID
def getNodeByServer(self, server):
return self.getByAddress(address=server)
...@@ -31,19 +31,13 @@ class NodesTests(NeoTestBase): ...@@ -31,19 +31,13 @@ class NodesTests(NeoTestBase):
def setUp(self): def setUp(self):
self.manager = Mock() self.manager = Mock()
def _updatedByServer(self, node, index=0): def _updatedByAddress(self, node, index=0):
calls = self.manager.mockGetNamedCalls('unregisterServer') calls = self.manager.mockGetNamedCalls('_updateAddress')
self.assertEqual(len(calls), index + 1)
self.assertEqual(calls[index].getParam(0), node)
calls = self.manager.mockGetNamedCalls('registerServer')
self.assertEqual(len(calls), index + 1) self.assertEqual(len(calls), index + 1)
self.assertEqual(calls[index].getParam(0), node) self.assertEqual(calls[index].getParam(0), node)
def _updatedByUUID(self, node, index=0): def _updatedByUUID(self, node, index=0):
calls = self.manager.mockGetNamedCalls('unregisterUUID') calls = self.manager.mockGetNamedCalls('_updateUUID')
self.assertEqual(len(calls), index + 1)
self.assertEqual(calls[index].getParam(0), node)
calls = self.manager.mockGetNamedCalls('registerUUID')
self.assertEqual(len(calls), index + 1) self.assertEqual(len(calls), index + 1)
self.assertEqual(calls[index].getParam(0), node) self.assertEqual(calls[index].getParam(0), node)
...@@ -74,7 +68,7 @@ class NodesTests(NeoTestBase): ...@@ -74,7 +68,7 @@ class NodesTests(NeoTestBase):
self.assertEqual(node.getServer(), None) self.assertEqual(node.getServer(), None)
server = ('127.0.0.1', 10000) server = ('127.0.0.1', 10000)
node.setServer(server) node.setServer(server)
self._updatedByServer(node) self._updatedByAddress(node)
def testUUID(self): def testUUID(self):
""" As for Server but UUID """ """ As for Server but UUID """
...@@ -238,26 +232,6 @@ class NodeManagerTests(NeoTestBase): ...@@ -238,26 +232,6 @@ class NodeManagerTests(NeoTestBase):
self.checkNodes([]) self.checkNodes([])
self.checkClients([]) self.checkClients([])
def testFilteredClear(self):
""" Check the clear filter works well """
manager = self.manager
manager.add(self.master)
manager.add(self.storage)
manager.add(self.client)
self.checkNodes([self.master, self.storage, self.client])
drop_clients = lambda node: isinstance(node, ClientNode)
manager.clear(filter=drop_clients)
self.checkNodes([self.master, self.storage])
self.checkClients([])
drop_masters = lambda node: isinstance(node, MasterNode)
manager.clear(filter=drop_masters)
self.checkNodes([self.storage])
self.checkMasters([])
drop_storage = lambda node: node is self.storage
manager.clear(filter=drop_storage)
self.checkNodes([])
self.checkStorages([])
def testUpdate(self): def testUpdate(self):
""" Check manager content update """ """ Check manager content update """
# set up four nodes # set up four nodes
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment