The Assimilation Project  based on Assimilation version 1.1.7.1474836767
hbring.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # vim: smartindent tabstop=4 shiftwidth=4 expandtab number
3 #
4 # This file is part of the Assimilation Project.
5 #
6 # Copyright (C) 2011, 2012 - Alan Robertson <alanr@unix.sh>
7 #
8 # The Assimilation software is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # The Assimilation software is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with the Assimilation Project software. If not, see http://www.gnu.org/licenses/
20 #
21 #
22 '''
23 This file is all about the Rings - we implement rings.
24 '''
25 #import sys
26 from cmadb import CMAdb
27 from droneinfo import Drone
28 from graphnodes import GraphNode, RegisterGraphClass
29 from store import Store
30 
31 @RegisterGraphClass
32 class HbRing(GraphNode):
33  'Class defining the behavior of a heartbeat ring.'
34  SWITCH = 1
35  SUBNET = 2
36  THEONERING = 3 # And The One Ring to rule them all...
37  memberprefix = 'RingMember_'
38  nextprefix = 'RingNext_'
39 
40  def __init__(self, name, ringtype):
41  '''Constructor for a heartbeat ring.
42  '''
43  GraphNode.__init__(self, domain=CMAdb.globaldomain)
44  if ringtype < HbRing.SWITCH or ringtype > HbRing.THEONERING:
45  raise ValueError("Invalid ring type [%s]" % str(ringtype))
46  self.ringtype = ringtype
47  self.name = str(name)
48  self.ourreltype = HbRing.memberprefix + self.name # Our membership relationship type
49  self.ournexttype = HbRing.nextprefix + self.name # Our 'next' relationship type
50  self._ringinitfinished = False
51  self._insertpoint1 = None
52  self._insertpoint2 = None
53 
54  @staticmethod
56  'Return our key attributes in order of decreasing significance'
57  return ['name']
58 
59 
60  def post_db_init(self):
61  GraphNode.post_db_init(self)
62  if self._ringinitfinished:
63  return
64  self._ringinitfinished = True
65  self._insertpoint1 = None
66  self._insertpoint2 = None
67  #print >> sys.stderr, 'CMAdb(hbring.py):', CMAdb
68  #print >> sys.stderr, 'CMAdb.store(hbring.py):', CMAdb.store
69  #print >> sys.stderr, 'Our relation type: %s' % self.ourreltype
70  rellist = CMAdb.store.load_related(self, self.ourreltype, Drone)
71  for rel in rellist:
72  self._insertpoint1 = rel
73  #print >> sys.stderr, 'INSERTPOINT1: ', self._insertpoint1
74  #print >> sys.stderr, 'Our relation type: %s' % self.ournexttype
75  ip2rellist = CMAdb.store.load_related(self._insertpoint1, self.ournexttype, Drone)
76  for rel2 in ip2rellist:
77  self._insertpoint2 = rel2
78  break
79  break
80  # Need to figure out what to do about pre-existing members of this ring...
81  # For the moment, let's make the entirely inadequate assumption that
82  # the data in the database is correct.
83  ## FIXME - assumption about database being correct :-D
84 
85 
86  def _findringpartners(self, drone):
87  '''Find (one or) two partners for this drone to heartbeat with.
88  We _should_ do this in such a way that we don't continually beat on the
89  same nodes in the ring as we insert new nodes into the ring.'''
90  drone = drone # Eventually we'll use this argument...
91  partners = None
92  if self._insertpoint1 is not None:
93  partners = []
94  partners.append(self._insertpoint1)
95  if self._insertpoint2 is not None:
96  partners.append(self._insertpoint2)
97  return partners
98 
99  def join(self, drone):
100  'Add this drone to our ring'
101  if CMAdb.debug:
102  CMAdb.log.debug('1:Adding Drone %s to ring %s w/port %s' \
103  % (str(drone), str(self), drone.port))
104  # Make sure he's not already in our ring according to our 'database'
105  if not Store.is_abstract(drone):
106  rels = CMAdb.store.load_in_related(drone, self.ourreltype, HbRing)
107  rels = [rel for rel in rels]
108  if len(rels) > 0:
109  CMAdb.log.critical("%s is already a member of this ring [%s]"
110  " - removing and re-adding." % (drone, self))
111  self.leave(drone)
112 
113  # Create a 'ringmember' relationship to this drone
114  CMAdb.store.relate(self, self.ourreltype, drone)
115  # Should we keep a 'ringip' relationship for this drone?
116  # Probably eventually...
117 
118  #print >>sys.stderr,'Adding drone %s to talk to partners' % drone
119 
120  if self._insertpoint1 is None: # Zero nodes previously
121  self._insertpoint1 = drone
122  return
123 
124  if CMAdb.debug:
125  CMAdb.log.debug('2:Adding Drone %s to ring %s w/port %s' \
126  % (str(drone), str(self), drone.port))
127  if self._insertpoint2 is None: # One node previously
128  # Create the initial circular list.
129  ## FIXME: Ought to label ring membership relationships with IP involved
130  # This is because we might change configurations and we need to know
131  # what IP we're actually using for this connection...
132  CMAdb.store.relate(drone, self.ournexttype, self._insertpoint1)
133  CMAdb.store.relate(self._insertpoint1, self.ournexttype, drone)
134  if CMAdb.debug:
135  CMAdb.log.debug('3:Adding Drone %s to ring %s w/port %s'
136  % (str(drone), str(self), drone.port))
137  drone.start_heartbeat(self, self._insertpoint1)
138  self._insertpoint1.start_heartbeat(self, drone)
139  self._insertpoint2 = self._insertpoint1
140  self._insertpoint1 = drone
141  #print >>sys.stderr, 'RING2 IS NOW:', str(self)
142  return
143 
144  # Two or more nodes previously
145  nextnext = None
146  for nextnext in CMAdb.store.load_related(self._insertpoint2, self.ournexttype, Drone):
147  break
148  if CMAdb.debug:
149  CMAdb.log.debug('4:Adding Drone %s to ring %s w/port %s' \
150  % (str(drone), str(self), drone.port))
151  if nextnext is not None and nextnext is not self._insertpoint1:
152  #print >> sys.stderr, 'HAD AT LEAST 3 NODES BEFORE'
153  # At least 3 nodes before
154  # We had X->point1->point2->nextnext (where X and nextnext might be the same)
155  # We just verified that point1 and Y are different
156  #
157  # What we have right now is insertpoint1->insertpoint2->nextnext
158  # Let's move the insert point down the ring so that the same node doesn't get hit
159  # over and over with stop/start requests
160  #
161  self._insertpoint1 = self._insertpoint2
162  self._insertpoint2 = nextnext
163  self._insertpoint1.stop_heartbeat(self, self._insertpoint2)
164  self._insertpoint2.stop_heartbeat(self, self._insertpoint1)
165  CMAdb.store.separate(self._insertpoint1, self.ournexttype, self._insertpoint2)
166  # Now we just have had X->point1 and point2->Y
167  if CMAdb.debug:
168  CMAdb.log.debug('5:Adding Drone %s to ring %s w/port %s' \
169  % (str(drone), str(self), drone.port))
170  drone.start_heartbeat(self, self._insertpoint1, self._insertpoint2)
171  self._insertpoint1.start_heartbeat(self, drone)
172  self._insertpoint2.start_heartbeat(self, drone)
173  CMAdb.store.relate(self._insertpoint1, self.ournexttype, drone)
174  # after above statement: we have x->insertpoint1-> drone and insertpoint2->y
175  CMAdb.store.relate(drone, self.ournexttype, self._insertpoint2)
176  # after above statement: we have insertpoint1-> drone->insertpoint2
177  #
178  # In the future we might want to mark these relationships with the IP addresses involved
179  # so that even if the systems change network configurations we can still know what IP to
180  # remove. Right now we rely on the configuration not changing "too much".
181  ## FIXME: Ought to label relationships with IP addresses involved.
182  #
183  # We should ensure that we don't keep beating the same nodes over and over
184  # again as new nodes join the system. Instead the latest newbie becomes the next
185  # insert point in the ring - spreading the work to the new guys as they arrive.
186  # Probably should use nextnext from above...
187  self._insertpoint1 = drone
188  #print >>sys.stderr, 'RING3 IS NOW:', str(self), 'DRONE ADDED:', drone
189 
190  def leave(self, drone):
191  'Remove a drone from this heartbeat Ring.'
192  #print >> sys.stderr, 'DRONE %s leaving Ring [%s]' % (drone, self)
193  #ringlist = self.members_ring_order()
194  #print >>sys.stderr, 'RING IN ORDER:'
195  #for elem in ringlist:
196  #print >>sys.stderr, 'RING NODE: %s' % elem
197 
198  prevnode = None
199  for prevnode in CMAdb.store.load_in_related(drone, self.ournexttype, Drone):
200  break
201  nextnode = None
202  for nextnode in CMAdb.store.load_related(drone, self.ournexttype, Drone):
203  break
204 
205  # Clean out the parent (ring) relationship to our dearly departed drone
206  #print >> sys.stderr, 'Separating ourselves (%s) from drone %s' % (self, drone)
207  CMAdb.store.separate(self, self.ourreltype, drone)
208  # Clean out the next link relationships to our dearly departed drone
209  if nextnode is None and prevnode is None: # Previous length: 1
210  self._insertpoint1 = None # result length: 0
211  self._insertpoint2 = None
212  # No other database links to remove
213  if CMAdb.debug:
214  CMAdb.log.debug('Last Drone %s has now left the building...' % (drone))
215  return
216 
217  # Clean out the next link relationships to our dearly departed drone
218  CMAdb.store.separate(prevnode, self.ournexttype, obj=drone)
219  CMAdb.store.separate(drone, self.ournexttype, obj=nextnode)
220 
221  #print >> sys.stderr, ('PREVNODE: %s NEXTNODE: %s prev is next? %s'
222  #% (str(prevnode), str(nextnode), prevnode is nextnode))
223 
224  if prevnode is nextnode: # Previous length: 2
225  #drone.stop_heartbeat(self, prevnode) # Result length: 1
226  # but drone is dead - don't talk to it.
227  prevnode.stop_heartbeat(self, drone)
228  self._insertpoint2 = None
229  self._insertpoint1 = prevnode
230  return
231 
232  # Previous length had to be >= 3 # Previous length: >=3
233  # Result length: >=2
234  nextnext = None
235  for nextnext in CMAdb.store.load_related(nextnode, self.ournexttype, Drone):
236  break
237  prevnode.stop_heartbeat(self, drone)
238  nextnode.stop_heartbeat(self, drone)
239  if nextnext is not prevnode: # Previous length: >= 4
240  nextnode.start_heartbeat(self, prevnode) # Result length: >= 3
241  prevnode.start_heartbeat(self, nextnode)
242  # (in the nextnext is prevnode case, they're already heartbeating)
243  # Poor drone -- all alone in the universe... (maybe even dead...)
244  #drone.stop_heartbeat(self, prevnode, nextnode) # don't send packets to dead machines
245  self._insertpoint1 = prevnode
246  self._insertpoint2 = nextnode
247  CMAdb.store.relate(prevnode, self.ournexttype, nextnode)
248 
249  def are_partners(self, drone1, drone2):
250  'Return True if these two drones are heartbeat partners in our ring'
251  CMAdb.log.debug('calling are_partners(%s-[%s]-%s)'
252  % (drone1, self.ournexttype, drone2))
253  nextelems = CMAdb.store.load_related(drone1, self.ournexttype, Drone)
254  for elem in nextelems: # nextelems is a generator
255  if elem is drone2:
256  return True
257  prevelems = CMAdb.store.load_in_related(drone1, self.ournexttype, Drone)
258  for elem in prevelems: # prevelems is a generator
259  if elem is drone2:
260  return True
261  return False
262 
263  def members(self):
264  'Return all the Drones that are members of this ring - in some random order'
265  return CMAdb.store.load_related(self, self.ourreltype, Drone)
266 
268  'Return all the Drones that are members of this ring - in ring order'
269  ## FIXME - There's a cypher query that will return these all in one go
270  # START Drone=node:Drone(Drone="drone000001")
271  # MATCH (Drone)-[:RingNext_The_One_Ring*]->(NextDrone)
272  # RETURN NextDrone.designation, NextDrone
273 
274  if self._insertpoint1 is None:
275  #print >> sys.stderr, 'NO INSERTPOINT1'
276  return
277  if Store.is_abstract(self._insertpoint1):
278  #print >> sys.stderr, ('YIELDING INSERTPOINT1:', self._insertpoint1
279  #, type(self._insertpoint1))
280  yield self._insertpoint1
281  return
282  startid = Store.id(self._insertpoint1)
283  # We can't pre-compile this, but we hopefully we won't use it much...
284  q = '''START Drone=node(%s)
285  MATCH p=(Drone)-[:%s*0..]->(NextDrone)
286  WHERE length(p) = 0 or Drone <> NextDrone
287  RETURN NextDrone''' % (startid, self.ournexttype)
288  for elem in CMAdb.store.load_cypher_nodes(q, Drone):
289  yield elem
290  return
291 
292  def AUDIT(self):
293  '''Audit our ring to see if it's well-formed'''
294  listmembers = {}
295  ringmembers = {}
296  mbrcount = 0
297  for drone in self.members():
298  ringmembers[drone.designation] = None
299  mbrcount += 1
300 
301  for drone in self.members_ring_order():
302  listmembers[drone.designation] = None
303  nextcount = 0
304  nextlist = CMAdb.store.load_related(drone, self.ournexttype, Drone)
305  # pylint: disable=W0612
306  for elem in nextlist:
307  nextcount += 1
308  incount = 0
309  inlist = CMAdb.store.load_in_related(drone, self.ournexttype, Drone)
310  for elem in inlist:
311  incount += 1
312  ringcount = 0
313  dronelist = CMAdb.store.load_in_related(drone, self.ourreltype, Drone)
314  for elem in dronelist:
315  ringcount += 1
316  #print >> sys.stderr \
317  #, ('%s status: %s mbrcount: %d, nextcount:%d, incount:%d, ringcount:%d'
318  #% (drone, drone.status, mbrcount, nextcount, incount, ringcount))
319  assert drone.status == 'up'
320  assert mbrcount < 2 or nextcount == 1
321  assert mbrcount < 2 or incount == 1
322  assert ringcount == 1
323 
324  for drone in listmembers.keys():
325  assert(drone in ringmembers)
326  for drone in ringmembers.keys():
327  assert(drone in listmembers)
328 
329 
330 
331  def __str__(self):
332  ret = 'Ring("%s"' % self.name
333  #comma = ', ['
334  #for drone in self.members_ring_order():
335  # ret += '%s%s' % (comma, drone)
336  # comma = ', '
337  #ret += ']'
338  ret += ')'
339  return ret
def members(self)
Definition: hbring.py:263
_insertpoint2
FIXME: Ought to label ring membership relationships with IP involved This is because we might change ...
Definition: hbring.py:52
def leave(self, drone)
Definition: hbring.py:190
_insertpoint1
FIXME: Ought to label relationships with IP addresses involved.
Definition: hbring.py:51
def join(self, drone)
Definition: hbring.py:99
def __str__(self)
Definition: hbring.py:331
def AUDIT(self)
Definition: hbring.py:292
def post_db_init(self)
Definition: hbring.py:60
def are_partners(self, drone1, drone2)
Definition: hbring.py:249
def __meta_keyattrs__()
Definition: hbring.py:55
def members_ring_order(self)
Definition: hbring.py:267
def __init__(self, name, ringtype)
Definition: hbring.py:40