node.py 11.3 KB
Newer Older
Aurel's avatar
Aurel committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#
# 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
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Aurel's avatar
Aurel committed
17

18 19
from time import time

20
from neo import logging
Yoshinori Okuji's avatar
Yoshinori Okuji committed
21
from neo.util import dump
22
from neo.protocol import NodeTypes, NodeStates
Yoshinori Okuji's avatar
Yoshinori Okuji committed
23 24 25 26

class Node(object):
    """This class represents a node."""

27
    def __init__(self, manager, address=None, uuid=None, 
28
            state=NodeStates.UNKNOWN):
29 30 31 32 33
        self._state = state
        self._address = address
        self._uuid = uuid
        self._manager = manager
        self._last_state_change = time()
34

35
    def getLastStateChange(self):
36
        return self._last_state_change
Yoshinori Okuji's avatar
Yoshinori Okuji committed
37 38

    def getState(self):
39
        return self._state
Yoshinori Okuji's avatar
Yoshinori Okuji committed
40

41
    def setState(self, new_state):
42 43 44 45 46 47
        if self._state == new_state:
            return
        old_state = self._state
        self._state = new_state
        self._last_state_change = time()
        self._manager._updateState(self, old_state)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
48

49 50 51 52
    def setAddress(self, address):
        old_address = self._address
        self._address = address
        self._manager._updateAddress(self, old_address)
53

54 55
    def getAddress(self):
        return self._address
56 57

    def setUUID(self, uuid):
58 59 60
        old_uuid = self._uuid
        self._uuid = uuid
        self._manager._updateUUID(self, old_uuid)
61 62

    def getUUID(self):
63
        return self._uuid
64

65 66 67 68
    def __repr__(self):
        return '<%s(uuid=%s, address=%s, state=%s)>' % (
            self.__class__.__name__, 
            dump(self._uuid),
69
            self._address,
70 71
            self._state,
        )
72

73
    def isMaster(self):
74
        return False
75 76

    def isStorage(self):
77
        return False
78 79

    def isClient(self):
80
        return False
81 82

    def isAdmin(self):
83
        return False
84

85 86 87 88
    def isIdentified(self):
        # XXX: knowing the node's UUID is sufficient ?
        return self._uuid is not Node

89 90
    def isRunning(self):
        # FIXME: is it like 'connected' ?
91
        return self._state == NodeStates.RUNNING
92 93

    def isTemporarilyDown(self):
94 95
        # FIXME: is it like 'unconnected' or UNKNOWN state ? 
        return self._state == NodeStates.TEMPORARILY_DOWN
96 97 98

    def isDown(self):
        # FIXME: is it like 'unconnected' or 'forgotten' ?
99
        return self._state == NodeStates.DOWN
100 101
        
    def isBroken(self):
102
        return self._state == NodeStates.BROKEN
103 104

    def isHidden(self):
105
        return self._state == NodeStates.HIDDEN
106 107

    def isPending(self):
108
        return self._state == NodeStates.PENDING
109 110

    def setRunning(self):
111
        self.setState(NodeStates.RUNNING)
112 113

    def setTemporarilyDown(self):
114
        self.setState(NodeStates.TEMPORARILY_DOWN)
115 116

    def setDown(self):
117
        self.setState(NodeStates.DOWN)
118 119

    def setBroken(self):
120
        self.setState(NodeStates.BROKEN)
121 122

    def setHidden(self):
123
        self.setState(NodeStates.HIDDEN)
124 125

    def setPending(self):
126
        self.setState(NodeStates.PENDING)
127

128 129 130 131
    def asTuple(self):
        """ Returned tuple is intented to be used in procotol encoders """
        return (self.getType(), self._address, self._uuid, self._state)

132 133 134 135 136 137
    def __gt__(self, node):
        # sort per UUID if defined
        if self._uuid is not None:
            return self._uuid > node._uuid
        return self._address > node._address

138 139 140 141 142 143 144
    # XXX: for comptatibility, to be removed
    def getType(self):
        try:
            return NODE_CLASS_MAPPING[self.__class__]
        except KeyError:
            raise NotImplementedError

145

Yoshinori Okuji's avatar
Yoshinori Okuji committed
146 147
class MasterNode(Node):
    """This class represents a master node."""
148 149 150

    def isMaster(self):
        return True
Yoshinori Okuji's avatar
Yoshinori Okuji committed
151 152 153

class StorageNode(Node):
    """This class represents a storage node."""
154 155 156
    
    def isStorage(self):
        return True
Yoshinori Okuji's avatar
Yoshinori Okuji committed
157 158 159

class ClientNode(Node):
    """This class represents a client node."""
160 161 162
    
    def isClient(self):
        return True
Yoshinori Okuji's avatar
Yoshinori Okuji committed
163

Aurel's avatar
Aurel committed
164 165
class AdminNode(Node):
    """This class represents an admin node."""
166 167 168
    
    def isAdmin(self):
        return True
Aurel's avatar
Aurel committed
169

170

171
NODE_TYPE_MAPPING = {
172 173 174 175
    NodeTypes.MASTER: MasterNode,
    NodeTypes.STORAGE: StorageNode,
    NodeTypes.CLIENT: ClientNode,
    NodeTypes.ADMIN: AdminNode,
176
}
177
NODE_CLASS_MAPPING = {
178 179 180 181
    StorageNode: NodeTypes.STORAGE,
    MasterNode: NodeTypes.MASTER,
    ClientNode: NodeTypes.CLIENT,
    AdminNode: NodeTypes.ADMIN,
182
}
183

Yoshinori Okuji's avatar
Yoshinori Okuji committed
184 185 186 187
class NodeManager(object):
    """This class manages node status."""

    def __init__(self):
188 189 190 191 192
        self._node_set = set()
        self._address_dict = {}
        self._uuid_dict = {}
        self._type_dict = {}
        self._state_dict = {}
193 194

    def add(self, node):
195
        if node in self._node_set:
196
            return
197 198 199 200 201
        self._node_set.add(node)   
        self._updateAddress(node, None)
        self._updateUUID(node, None)
        self.__updateSet(self._type_dict, None, node.__class__, node)
        self.__updateSet(self._state_dict, None, node.getState(), node)
202

203 204
    def remove(self, node):
        if node is None or node not in self._node_set:
205
            return
206 207 208 209 210
        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)
211

212
    def __drop(self, index_dict, key):
213
        try:
214
            del index_dict[key]
215 216 217
        except KeyError:
            pass

218 219 220 221 222 223 224
    def __update(self, index_dict, old_key, new_key, node):
        """ Update an index from old to new key """
        # FIXME: should the old_key always be indexed ?
        if old_key is not None:
            del index_dict[old_key]
        if new_key is not None:
            index_dict[new_key] = node
225

226 227
    def _updateAddress(self, node, old_address):
        self.__update(self._address_dict, old_address, node.getAddress(), node)
228

229 230
    def _updateUUID(self, node, old_uuid):
        self.__update(self._uuid_dict, old_uuid, node.getUUID(), node)
231

232 233 234
    def __dropSet(self, set_dict, key, node):
        if key in set_dict and node in set_dict[key]:
            set_dict[key].remove(node)
235

236 237 238 239 240 241 242
    def __updateSet(self, set_dict, old_key, new_key, node):
        """ Update a set index from old to new key """
        # 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)
243

244 245
    def _updateState(self, node, old_state):
        self.__updateSet(self._state_dict, old_state, node.getState(), node)
246

247 248 249 250 251 252 253 254 255 256 257 258 259 260
    def getList(self, node_filter=None):
        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)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
261

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
    def getMasterList(self):
        """ Return a list with master nodes """
        return self.__getTypeList(MasterNode)

    def getStorageList(self):
        """ Return a list with storage nodes """
        return self.__getTypeList(StorageNode)

    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
291

292 293
    def _createNode(self, klass, **kw):
        node = klass(self, **kw)
294 295 296
        self.add(node)
        return node

297
    def createMaster(self, **kw):
298
        """ Create and register a new master """
299
        return self._createNode(MasterNode, **kw)
300

301
    def createStorage(self, **kw):
302
        """ Create and register a new storage """
303
        return self._createNode(StorageNode, **kw)
304

305
    def createClient(self, **kw):
306
        """ Create and register a new client """
307
        return self._createNode(ClientNode, **kw)
308
    
309
    def createAdmin(self, **kw):
310
        """ Create and register a new admin """
311
        return self._createNode(AdminNode, **kw)
312

313
    def createFromNodeType(self, node_type, **kw):
314
        klass = NODE_TYPE_MAPPING.get(node_type)
315 316
        if klass is None:
            raise RuntimeError('Unknown node type : %s' % node_type)
317
        return self._createNode(klass, **kw)
318
    
319
    def clear(self, filter=None):
320 321 322 323 324
        self._node_set.clear()
        self._type_dict.clear()
        self._state_dict.clear()
        self._uuid_dict.clear()
        self._address_dict.clear()
325

326 327 328
    def update(self, node_list):
        for node_type, addr, uuid, state in node_list:
            # lookup in current table
329 330
            node_by_uuid = self.getByUUID(uuid)
            node_by_addr = self.getByAddress(addr)
331 332 333
            node = node_by_uuid or node_by_addr

            log_args = (node_type, dump(uuid), addr, state)
334
            if state == NodeStates.DOWN:
335 336 337 338
                # drop down nodes
                logging.debug('drop node %s %s %s %s' % log_args)
                self.remove(node)
            elif node_by_uuid is not None:
339
                if node.getAddress() != addr:
340
                    # address changed, update it
341
                    node.setAddress(addr)
342 343 344 345 346 347 348 349 350 351
                logging.debug('update node %s %s %s %s' % log_args)
                node.setState(state)
            else:
                if node_by_addr is not None:
                    # exists only by address,
                    self.remove(node)
                # don't exists, add it
                klass = NODE_TYPE_MAPPING.get(node_type, None)
                if klass is None:
                    raise RuntimeError('Unknown node type')
352
                node = klass(self, address=addr, uuid=uuid)
353 354 355
                node.setState(state)
                self.add(node)
                logging.info('create node %s %s %s %s' % log_args)
356
        self.log()
357

358
    def log(self):
359
        logging.debug('Node manager : %d nodes' % len(self._node_set))
360 361 362 363 364
        for node in sorted(list(self._node_set)):
            uuid = dump(node.getUUID()) or '-' * 32
            address = node.getAddress() or ''
            if address:
                address = '%s:%d' % address
365
            logging.debug(' * %32s | %8s | %22s | %s' % (
366 367
                uuid, node.getType(), address, node.getState()))