The Assimilation Project
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
cma/tests/cma_test.py
Go to the documentation of this file.
1 # vim: smartindent tabstop=4 shiftwidth=4 expandtab
2 #
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 _suites = ['all', 'cma']
23 import sys
24 sys.path.append("../cma")
25 sys.path.append("/usr/local/lib/python2.7/dist-packages")
26 from py2neo import neo4j
27 from testify import *
28 from testify.utils import turtle
29 
30 from frameinfo import *
31 from AssimCclasses import *
32 import gc, sys, time, collections, os
33 from graphnodes import nodeconstructor, CMAclass
34 from cmainit import CMAinit
35 from cmadb import CMAdb
36 from packetlistener import PacketListener
37 from messagedispatcher import MessageDispatcher
38 from dispatchtarget import DispatchSTARTUP, DispatchHBDEAD, DispatchJSDISCOVERY, DispatchSWDISCOVER
39 from hbring import HbRing
40 from droneinfo import Drone, ProcessNode
41 import optparse
42 from graphnodes import GraphNode
43 from monitoring import MonitorAction, LSBMonitoringRule, MonitoringRule, OCFMonitoringRule
44 from transaction import Transaction
45 
46 
47 WorstDanglingCount = 0
48 CheckForDanglingClasses = False
49 DEBUG=False
50 DoAudit=True
51 SavePackets=True
52 doHBDEAD=True
53 MaxDrone=5
54 MaxDrone=10000
55 
56 MaxDrone=5
57 doHBDEAD=True
58 
59 BuildListOnly = False
60 if BuildListOnly:
61  doHBDEAD=False
62  SavePackets=False
63  DoAudit=False
64  CheckForDanglingClasses=False
65  DEBUG=False
66 
67 t1 = MaxDrone
68 if t1 < 1000: t1 = 1000
69 t2 = MaxDrone/100
70 if t2 < 10: t2 = 10
71 t3 = t2
72 
73 
74 #gc.set_threshold(t1, t2, t3)
75 
77  global CheckForDanglingClasses
78  global WorstDanglingCount
79  CMAdb.cdb = None
80  CMAdb.io = None
81  CMAdb.TheOneRing = None
82  CMAdb.store = None
83  gc.collect() # For good measure...
85  #print >>sys.stderr, "CHECKING FOR DANGLING CLASSES (%d)..." % count
86  # Avoid cluttering the output up with redundant messages...
87  if count > WorstDanglingCount and CheckForDanglingClasses:
88  WorstDanglingCount = count
90  raise AssertionError, "Dangling C-class objects - %d still around" % count
91 
92 # Values to substitute into this string via '%' operator:
93 # dronedesignation (%s) MAC address byte (%02x), MAC address byte (%02x), IP address (%s)
94 netdiscoveryformat='''
95 {
96  "discovertype": "netconfig",
97  "description": "IP Network Configuration",
98  "source": "netconfig",
99  "host": "%s",
100  "data": {
101  "eth0": {
102  "address": "00:1b:fc:1b:%02x:%02x",
103  "carrier": 1,
104  "duplex": "full",
105  "mtu": 1500,
106  "operstate": "up",
107  "speed": 1000,
108  "default_gw": true,
109  "ipaddrs": { "%s/16": {"brd":"10.20.255.255", "scope":"global", "name":"eth0"}}
110  },
111  "lo": {
112  "address": "00:00:00:00:00:00",
113  "carrier": 1,
114  "mtu": 16436,
115  "operstate": "unknown",
116  "ipaddrs": { "127.0.0.1/8": {"scope":"host"}, "::1/128": {"scope":"host"}}
117  }
118  }
119 }
120 '''
121 
122 
123 byte1 = 10
124 byte2 = 20
125 
126 def droneipaddress(hostnumber):
127  byte2 = int(hostnumber / 65536)
128  byte3 = int((hostnumber / 256) % 256)
129  byte4 = hostnumber % 256
130  return pyNetAddr([byte1,byte2,byte3,byte4],1984)
131 
132 def dronedesignation(hostnumber):
133  return 'drone%06d' % hostnumber
134 
135 def hostdiscoveryinfo(hostnumber):
136  byte3 = int(hostnumber / 256)
137  byte4 = hostnumber % 256
138  ip =droneipaddress(hostnumber)
139  ip.setport(0)
140  s = str(ip)
141  return netdiscoveryformat % (dronedesignation(hostnumber), byte3, byte4, s)
142 
143 def geninitconfig(ouraddr):
144  return {
145  'cmainit': ouraddr, # Initial 'hello' address
146  'cmaaddr': ouraddr, # not sure what this one does...
147  'cmadisc': ouraddr, # Discovery packets sent here
148  'cmafail': ouraddr, # Failure packets sent here
149  'cmaport': 1984,
150  'hbport': 1984,
151  'outsig': pySignFrame(1),
152  'deadtime': 10*1000000,
153  'warntime': 3*1000000,
154  'hbtime': 1*1000000,
155  }
156 
157 class AUDITS(TestCase):
158  def auditadrone(self, droneid):
159  designation = dronedesignation(droneid)
160  droneip = droneipaddress(droneid)
161  droneipstr = str(droneip)
162  # Did the drone get put in the Drone table?
163  drone=Drone.find(designation)
164  self.assertTrue(drone is not None)
165  # Did the drone's list of addresses get updated?
166  ipnodes = drone.get_owned_ips()
167  ipnodes = [ip for ip in ipnodes]
168  self.assertEqual(len(ipnodes), 1)
169  ipnode = ipnodes[0]
170  ipnodeaddr = pyNetAddr(ipnode.ipaddr)
171  json = drone.JSON_netconfig
172  jsobj = pyConfigContext(init=json)
173  jsdata = jsobj['data']
174  eth0obj = jsdata['eth0']
175  eth0addrcidr = eth0obj['ipaddrs'].keys()[0]
176  eth0addrstr, cidrmask = eth0addrcidr.split('/')
177  eth0addr = pyNetAddr(eth0addrstr)
178  self.assertTrue(eth0addr == ipnodeaddr)
179 
180  # Do we know that eth0 is the default gateway?
181  self.assertEqual(eth0obj['default_gw'], True)
182 
183  # the JSON should have exactly 5 top-level keys
184  self.assertEqual(len(jsobj.keys()), 5)
185  # Was the JSON host name saved away correctly?
186  self.assertEqual(jsobj['host'], designation)
187 
188  return
189  peercount=0
190  ringcount=0
191  for ring in drone.ringmemberships.values():
192  ringcount += 1
193  # How many peers should it have?
194  if len(ring.memberlist) == 1:
195  pass # No peers in this ring...
196  elif len(ring.memberlist) == 2:
197  peercount += 1
198  else:
199  peercount += 2
200  # Make sure we're listed under our designation
201  #print >>sys.stderr, "DRONE is %s status %s" % (drone.designation, drone.status)
202  #print >>sys.stderr, "DRONE ringmemberships:", drone.ringmemberships.keys()
203  self.assertEqual(ring.members[drone.designation].designation, drone.designation)
204  self.assertEqual(len(ring.members), len(ring.memberlist))
205  if drone.status != 'dead':
206  # We have to be members of at least one ring...
207  self.assertTrue(ringcount >= 1)
208  # Drone should be a member of one ring (for now)
209  self.assertEqual(len(drone.ringmemberships),1)
210  # Do we have the right number of ring peers?
211  #print >>sys.stderr, "Checking peer count for drone %s (%d)" % (drone, len(drone.ringpeers))
212  self.assertEqual(len(drone.ringpeers), peercount)
213 
214  def auditSETCONFIG(self, packetreturn, droneid, configinit):
215  toaddr = packetreturn[0]
216  sentfs = packetreturn[1]
217  droneip = droneipaddress(droneid)
218 
219  # Was it a SETCONFIG packet?
220  self.assertEqual(sentfs.get_framesettype(), FrameSetTypes.SETCONFIG)
221  # Was the SETCONFIG sent back to the drone?
222  self.assertEqual(toaddr,droneip)
223  # Lets check the number of Frames in the SETCONFIG Frameset
224  self.assertEqual(1, len(sentfs)) # Was it the right size?
225 
226  def auditaRing(self, ring):
227  'Verify that each ring has its neighbor pairs set up properly'
228  # Check that each element of the ring is connected to its neighbors...
229  print "Ring %s" % (str(ring))
230  listmembers = {}
231 
232  ringmembers = {}
233  for drone in ring.members():
234  ringmembers[drone.designation] = None
235  for drone in ring.membersfromlist():
236  listmembers[drone.designation] = None
237  for drone in listmembers.keys():
238  self.assertTrue(drone in ringmembers)
239  for drone in ringmembers.keys():
240  print >> sys.stderr, 'RINGMEMBERS: %s: members:%s' % (str(drone), listmembers)
241  self.assertTrue(drone in listmembers)
242 
243 
245  audit = AUDITS()
246  qtext = "START droneroot=node:CMAclass('Drone:*') MATCH drone-[:IS_A]->droneroot RETURN drone"
247  query = neo4j.CypherQuery(CMAdb.cdb.db, qtext)
248  droneobjs = CMAdb.store.load_cypher_nodes(query, Drone)
249  droneobjs = [drone for drone in droneobjs]
250  numdrones = len(droneobjs)
251  for droneid in range(0, numdrones):
252  audit.auditadrone(droneid+1)
253  query = neo4j.CypherQuery(CMAdb.cdb.db, '''START n=node:Drone('*:*') RETURN n''')
254  queryobjs = CMAdb.store.load_cypher_nodes(query, Drone)
255  queryobjs = [drone for drone in queryobjs]
256  dronetbl = {}
257  for drone in droneobjs:
258  dronetbl[drone.designation] = drone
259  querytbl = {}
260  for drone in queryobjs:
261  querytbl[drone.designation] = drone
262  # Now compare them
263  for drone in dronetbl:
264  assert(querytbl[drone] is dronetbl[drone])
265  for drone in querytbl:
266  assert(querytbl[drone] is dronetbl[drone])
267 
268 
270  audit = AUDITS()
271  query = neo4j.CypherQuery(CMAdb.cdb.db, '''START n=node:HbRing('*:*') RETURN n''')
272  for ring in CMAdb.store.load_cypher_nodes(query, HbRing):
273  ring.AUDIT()
274 
275 class TestIO:
276  '''A pyNetIOudp replacement for testing. It is given a list of packets to be 'read'
277  and in turn saves all the packets it 'writes' for us to inspect.
278  '''
279  def __init__(self, addrframesetpairs, sleepatend=0):
280  if isinstance(addrframesetpairs, tuple):
281  addrframesetpairs = addrframesetpairs
282  self.inframes = addrframesetpairs
284  self.packetsread=0
285  self.sleepatend=sleepatend
286  self.index=0
287  self.writecount=0
288  self.config = {CONFIGNAME_CMAPORT: 1984}
289 
290  def recvframesets(self):
291  # Audit after each packet is processed - and once before the first packet.
292  if DoAudit:
293  if self.packetsread < 200 or (self.packetsread % 500) == 0:
295  auditallrings()
296  if self.index >= len(self.inframes):
297  time.sleep(self.sleepatend)
298  raise StopIteration('End of Packets')
299  ret = self.inframes[self.index]
300  self.index += 1
301  self.packetsread += len(ret[1])
302  return ret
303 
304  def sendframesets(self, dest, fslist):
305  if not isinstance(fslist, collections.Sequence):
306  return self._sendaframeset(dest, fslist)
307  for fs in fslist:
308  self._sendaframeset(dest, fs)
309 
310  def sendreliablefs(self, dest, fslist):
311  self.sendframesets(dest, fslist)
312 
313  def ackmessage(self, dest, fs):
314  pass
315 
316  def closeconn(self, qid, dest):
317  pass
318 
319  def _sendaframeset(self, dest, fslist):
320  self.writecount += 1
321  if SavePackets:
322  self.packetswritten.append((dest,fslist))
323 
324  def getmaxpktsize(self): return 60000
325  def getfd(self): return 4
326  def bindaddr(self, addr): return True
327  def mcastjoin(self, addr): return True
328  def setblockio(self, tf): return
329 
330  def dumppackets(self):
331  print >>sys.stderr, 'Sent %d packets' % len(self.packetswritten)
332  for packet in self.packetswritten:
333  print '%s (%s)' % (packet[0], packet[1])
334 
335 
336 class TestTestInfrastructure(TestCase):
337  def test_eof(self):
338  'Get EOF with empty input'
339  if BuildListOnly: return
340  if DEBUG:
341  print >> sys.stderr, 'Running test_test_eof()'
342  framesets=[]
343  io = TestIO(framesets, 0)
344  CMAinit(io, cleanoutdb=True, debug=DEBUG)
345  # just make sure it seems to do the right thing
346  self.assertRaises(StopIteration, io.recvframesets)
348 
349  def test_get1pkt(self):
350  'Read a single packet'
351  if BuildListOnly: return
352  if DEBUG:
353  print >> sys.stderr, 'Running test_test_eof()'
354  otherguy = pyNetAddr([1,2,3,4],)
355  strframe1=pyCstringFrame(FrameTypes.CSTRINGVAL, "Hello, world.")
356  fs = pyFrameSet(42)
357  fs.append(strframe1)
358  framesets=((otherguy, (strframe1,)),)
359  io = TestIO(framesets, 0)
360  CMAinit(io, cleanoutdb=True, debug=DEBUG)
361  gottenfs = io.recvframesets()
362  self.assertEqual(len(gottenfs), 2)
363  self.assertEqual(gottenfs, framesets[0])
364  self.assertRaises(StopIteration, io.recvframesets)
365 
366  def test_echo1pkt(self):
367  'Read a packet and write it back out'
368  if BuildListOnly: return
369  if DEBUG:
370  print >> sys.stderr, 'Running test_echo1pkt()'
371  strframe1=pyCstringFrame(FrameTypes.CSTRINGVAL, "Hello, world.")
372  fs = pyFrameSet(42)
373  fs.append(strframe1)
374  otherguy = pyNetAddr([1,2,3,4],)
375  framesets=((otherguy, (strframe1,)),)
376  io = TestIO(framesets, 0)
377  CMAinit(io, cleanoutdb=True, debug=DEBUG)
378  fslist = io.recvframesets() # read in a packet
379  self.assertEqual(len(fslist), 2)
380  self.assertEqual(fslist, framesets[0])
381  io.sendframesets(fslist[0], fslist[1]) # echo it back out
382  self.assertEqual(len(io.packetswritten), 1)
383  self.assertEqual(len(io.packetswritten), len(framesets))
384  self.assertRaises(StopIteration, io.recvframesets)
385 
386  @class_teardown
387  def tearDown(self):
389 
390 class TestCMABasic(TestCase):
391  def test_startup(self):
392  '''A semi-interesting test: We send a STARTUP message and get back a
393  SETCONFIG message with lots of good stuff in it.'''
394  if BuildListOnly: return
395  if DEBUG:
396  print >> sys.stderr, 'Running test_startup()'
397  droneid = 1
398  droneip = droneipaddress(droneid)
399  designation = dronedesignation(droneid)
400  designationframe=pyCstringFrame(FrameTypes.HOSTNAME, designation)
401  dronediscovery=hostdiscoveryinfo(droneid)
402  discoveryframe=pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery)
403  fs = pyFrameSet(FrameSetTypes.STARTUP)
404  fs.append(designationframe)
405  fs.append(discoveryframe)
406  fsin = ((droneip, (fs,)),)
407  io = TestIO(fsin,0)
408  CMAinit(io, cleanoutdb=True, debug=DEBUG)
409  OurAddr = pyNetAddr((127,0,0,1),1984)
410  disp = MessageDispatcher({FrameSetTypes.STARTUP: DispatchSTARTUP()})
411  configinit = geninitconfig(OurAddr)
412  config = pyConfigContext(init=configinit)
413  listener = PacketListener(config, disp, io=io)
414  # We send the CMA an intial STARTUP packet
415  self.assertRaises(StopIteration, listener.listen) # We audit after each packet is processed
416  # Let's see what happened...
417 
418  self.assertEqual(len(io.packetswritten), 2) # Did we send out two packets?
419  # Note that this change over time
420  # As we change discovery...
421  AUDITS().auditSETCONFIG(io.packetswritten[0], droneid, configinit)
422  # Drone and Ring tables are automatically audited after each packet
423 
425  '''A very interesting test: We send a STARTUP message and get back a
426  SETCONFIG message and then send back a bunch of discovery requests.'''
427  if DEBUG:
428  print >> sys.stderr, 'Running test_several_startups()'
429  OurAddr = pyNetAddr((10,10,10,5), 1984)
430  configinit = geninitconfig(OurAddr)
431  fsin = []
432  droneid=0
433  for droneid in range(1,MaxDrone+1):
434  droneip = droneipaddress(droneid)
435  designation = dronedesignation(droneid)
436  designationframe=pyCstringFrame(FrameTypes.HOSTNAME, designation)
437  dronediscovery=hostdiscoveryinfo(droneid)
438  discoveryframe=pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery)
439  fs = pyFrameSet(FrameSetTypes.STARTUP)
440  fs.append(designationframe)
441  fs.append(discoveryframe)
442  fsin.append((droneip, (fs,)))
443  addrone = droneipaddress(1)
444  maxdrones = droneid
445  if doHBDEAD:
446  #print >> sys.stderr, 'KILLING THEM ALL!!!'
447  for droneid in range(2,maxdrones+1):
448  droneip = droneipaddress(droneid)
449  deadframe=pyIpPortFrame(FrameTypes.IPPORT, addrstring=droneip)
450  fs = pyFrameSet(FrameSetTypes.HBDEAD)
451  fs.append(deadframe)
452  fsin.append((addrone, (fs,)))
453  io = TestIO(fsin)
454  CMAinit(io, cleanoutdb=True, debug=DEBUG)
455  disp = MessageDispatcher( {
456  FrameSetTypes.STARTUP: DispatchSTARTUP(),
457  FrameSetTypes.HBDEAD: DispatchHBDEAD(),
458  })
459  config = pyConfigContext(init=configinit)
460  listener = PacketListener(config, disp, io=io)
461  # We send the CMA a BUNCH of intial STARTUP packets
462  try:
463  listener.listen()
464  except StopIteration as foo:
465  pass
466  #self.assertRaises(StopIteration, listener.listen)
467  # We audit after each packet is processed
468  # The auditing code will make sure all is well...
469  # But it doesn't know how many drones we just registered
470  query = neo4j.CypherQuery(CMAdb.cdb.db, "START n=node:Drone('*:*') RETURN n")
471  Drones = CMAdb.store.load_cypher_nodes(query, Drone)
472  Drones = [drone for drone in Drones]
473  #print >> sys.stderr, 'WE NOW HAVE THESE DRONES:', Drones
474  self.assertEqual(len(Drones), maxdrones)
475  if doHBDEAD:
476  partnercount = 0
477  livecount = 0
478  ringcount = 0
479  for drone1 in Drones:
480  if drone1.status != 'dead': livecount += 1
481  for partner in CMAdb.store.load_related(drone1, CMAdb.TheOneRing.ournexttype, Drone):
482  partnercount += 1
483  for partner in CMAdb.store.load_in_related(drone1, CMAdb.TheOneRing.ournexttype, Drone):
484  partnercount += 1
485  for ring in CMAdb.store.load_in_related(drone1, CMAdb.TheOneRing.ourreltype, HbRing):
486  ringcount += 1
487  self.assertEqual(partnercount, 0)
488  self.assertEqual(livecount, 1)
489  self.assertEqual(ringcount, 1)
490  if DoAudit:
492  auditallrings()
493 
494  if DEBUG:
495  print "The CMA read %d packets." % io.packetsread
496  print "The CMA wrote %d packets." % io.writecount
497  #io.dumppackets()
498 
499 
500  @class_teardown
501  def tearDown(self):
503 
504 class TestMonitorBasic(TestCase):
505  def test_activate(self):
506  io = TestIO([],0)
507  CMAinit(io, cleanoutdb=True, debug=DEBUG)
508 
509  dummy = CMAdb.store.load_or_create(MonitorAction, domain='global', monitorname='DummyName'
510  , monitorclass='OCF', monitortype='Dummy', interval=1, timeout=120, provider='heartbeat')
511 
512  self.assertEqual(len(CMAdb.transaction.tree['packets']), 0)
513  CMAdb.store.commit()
514  CMAdb.transaction.commit_trans(io)
515  self.assertEqual(len(io.packetswritten), 0) # Shouldn't have sent out any pkts yet...
516  CMAdb.transaction = Transaction()
517 
518  droneid = 1
519  droneip = droneipaddress(droneid)
520  designation = dronedesignation(droneid)
521  droneAddr = pyNetAddr((127,0,0,1),1984)
522  droneone = CMAdb.store.load_or_create(Drone, designation=designation, port=1984
523  , startaddr=droneip, primary_ip_addr=droneip)
524  self.assertTrue(not dummy.isactive)
525  dummy.activate(droneone)
526  CMAdb.store.commit()
527  count=0
528  for obj in CMAdb.store.load_related(droneone, CMAconsts.REL_hosting, MonitorAction):
529  self.assertTrue(obj is dummy)
530  count += 1
531  self.assertEqual(count, 1)
532  self.assertTrue(dummy.isactive)
533  count=0
534  for obj in CMAdb.store.load_related(dummy, CMAconsts.REL_monitoring, Drone):
535  self.assertTrue(obj is droneone)
536  count += 1
537  self.assertEqual(count, 1)
538 
539  CMAdb.transaction.commit_trans(io)
540  self.assertEqual(len(io.packetswritten), 1) # Did we send out exactly one packet?
541  if SavePackets:
542  #io.dumppackets()
543  for fstuple in io.packetswritten:
544  (dest, frameset) = fstuple
545  self.assertEqual(frameset.get_framesettype(), FrameSetTypes.DORSCOP)
546  for frame in frameset.iter():
547  self.assertEqual(frame.frametype(), FrameTypes.RSCJSON)
548  table = pyConfigContext(init=frame.getstr())
549  for field in ('class', 'type', 'resourcename', 'repeat_interval'):
550  self.assertTrue(field in table)
551  if field == 'monitorclass' and table['monitorclass'] == 'OCF':
552  self.assertTrue('provider' in table)
553  for tup in (('class', str), ('type', str), ('resourcename', str)
554  , ('monitorclass', str), ('provider', str)
555  , ('repeat_interval', (int, long))
556  , ('timeout', (int,long))):
557  (n, t) = tup
558  if n in table:
559  self.assertTrue(isinstance(table[n], t))
560 
561  # TODO: Add test for deactivating the resource(s)
562 
564  neoargs = (
565  ('argv[0]', r'.*/[^/]*java[^/]*$'), # Might be overkill
566  ('argv[3]', r'-server$'), # Probably overkill
567  ('argv[-1]', r'org\.neo4j\.server\.Bootstrapper$'),
568  )
569  neorule = LSBMonitoringRule('neo4j-service', neoargs)
570 
571  sshnode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/sshd', ['/usr/bin/sshd', '-D' ]
572  #ProcessNode:
573  # (domain, host, nodename, pathname, argv, uid, gid, cwd, roles=None):
574  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
575 
576  sshargs = (
577  # This means one of our nodes should have a value called
578  # pathname, and it should end in '/sshd'
579  ('@basename()', 'sshd$'),
580  )
581  sshrule = LSBMonitoringRule('ssh', sshargs)
582 
583  udevnode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/udevd', ['/usr/bin/udevd']
584  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
585 
586 
587  neoprocargs = ("/usr/bin/java", "-cp"
588  , "/var/lib/neo4j/lib/concurrentlinkedhashmap-lru-1.3.1.jar:"
589  "AND SO ON:"
590  "/var/lib/neo4j/system/lib/slf4j-api-1.6.2.jar:"
591  "/var/lib/neo4j/conf/", "-server", "-XX:"
592  "+DisableExplicitGC"
593  , "-Dorg.neo4j.server.properties=conf/neo4j-server.properties"
594  , "-Djava.util.logging.config.file=conf/logging.properties"
595  , "-Dlog4j.configuration=file:conf/log4j.properties"
596  , "-XX:+UseConcMarkSweepGC"
597  , "-XX:+CMSClassUnloadingEnabled"
598  , "-Dneo4j.home=/var/lib/neo4j"
599  , "-Dneo4j.instance=/var/lib/neo4j"
600  , "-Dfile.encoding=UTF-8"
601  , "org.neo4j.server.Bootstrapper")
602 
603  neonode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/java', neoprocargs
604  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
605 
606  for tup in (sshrule.specmatch(None, (udevnode,))
607  , sshrule.specmatch(None, (neonode,))
608  , neorule.specmatch(None, (sshnode,))):
609  (prio, table) = tup
610  self.assertEqual(prio, MonitoringRule.NOMATCH)
611  self.assertTrue(table is None)
612 
613  (prio, table) = sshrule.specmatch(None, (sshnode,))
614  self.assertEqual(prio, MonitoringRule.LOWPRIOMATCH)
615  self.assertEqual(table['monitorclass'], 'lsb')
616  self.assertEqual(table['monitortype'], 'ssh')
617 
618  (prio, table) = neorule.specmatch(None, (neonode,))
619  self.assertEqual(prio, MonitoringRule.LOWPRIOMATCH)
620  self.assertEqual(table['monitorclass'], 'lsb')
621  self.assertEqual(table['monitortype'], 'neo4j-service')
622 
624  self.assertRaises(ValueError, LSBMonitoringRule, 'neo4j-service', [])
625  self.assertRaises(ValueError, LSBMonitoringRule, 'neo4j-service',
626  (('a.b.c', ')'),))
627  self.assertRaises(ValueError, LSBMonitoringRule, 'neo4j-service',
628  ((1,2,3,4,5),))
629  self.assertRaises(ValueError, LSBMonitoringRule, 'neo4j-service',
630  ((1,),))
631  self.assertRaises(ValueError, LSBMonitoringRule, 'neo4j-service',
632  ((),))
633 
634 
636  # @TODO What I have in mind for this test is that it
637  # actually construct an auto-generated LSB monitoring node and activate it
638  # It will have to add name, timeout and repeat intervals before activating it.
639  pass
640 
642  self.assertRaises(ValueError, OCFMonitoringRule, 'assimilation', 'neo4j',
643  ((1,2,3,4,5),))
644  self.assertRaises(ValueError, OCFMonitoringRule, 'assimilation', 'neo4j',
645  ((),))
646 
648  kitchensink = OCFMonitoringRule('assimilation', 'neo4j',
649  ( ('cantguess',) # length 1 - name
650  , ('port', 'port') # length 2 - name, expression
651  , (None, 'port') # length 2 - name, expression
652  , ('-', 'pathname') # length 2 - name, expression
653  , ('port', 'port', '[0-9]+$') # length 3 - name, expression, regex
654  , (None, 'pathname', '.*/java$') # length 3 - name, expression, regex
655  , (None, '@basename()', 'java$') # length 3 - name, expression, regex
656  , ('-', 'argv[-1]', r'org\.neo4j\.server\.Bootstrapper$')
657  # length 3 - name, expression, regex
658  , ('port', '@serviceport()', '[0-9]+$', re.I) # length 4 - name, expression, regex, flags
659  ))
660  keys = kitchensink.nvpairs.keys()
661  keys.sort()
662  self.assertEqual(str(keys), "['cantguess', 'port']")
663  values = []
664  for key in keys:
665  values.append(kitchensink.nvpairs[key])
666  self.assertEqual(str(values), "[None, '@serviceport()']")
667  regex = re.compile('xxx')
668  regextype = type(regex)
669  exprlist = []
670  for tup in kitchensink._tuplespec:
671  self.assertEqual(type(tup[1]), regextype)
672  exprlist.append(tup[0])
673  self.assertEqual(str(exprlist)
674  , "['port', 'pathname', '@basename()', 'argv[-1]', '@serviceport()']")
675  #
676  # That was a pain...
677  #
678  # Now, let's test the basics in a little more depth by creating what should be a working
679  # set of arguments to a (hypothetical) OCF resource agent
680  #
681  neo4j = OCFMonitoringRule('assimilation', 'neo4j',
682  ( ('port', 'port')
683  , (None, 'pathname', '.*/java$')
684  , ('-', 'argv[-1]', r'org\.neo4j\.server\.Bootstrapper$')
685  , ('home', '@argequals(-Dneo4j.home)', '/.*')
686  , ('neo4j', '@basename(@argequals(-Dneo4j.home))', '.')
687  )
688  )
689  neoprocargs = ("/usr/bin/java", "-cp"
690  , "/var/lib/neo4j/lib/concurrentlinkedhashmap-lru-1.3.1.jar:"
691  "AND SO ON:"
692  "/var/lib/neo4j/system/lib/slf4j-api-1.6.2.jar:"
693  "/var/lib/neo4j/conf/", "-server", "-XX:"
694  "+DisableExplicitGC"
695  , "-Dorg.neo4j.server.properties=conf/neo4j-server.properties"
696  , "-Djava.util.logging.config.file=conf/logging.properties"
697  , "-Dlog4j.configuration=file:conf/log4j.properties"
698  , "-XX:+UseConcMarkSweepGC"
699  , "-XX:+CMSClassUnloadingEnabled"
700  , "-Dneo4j.home=/var/lib/neo4j"
701  , "-Dneo4j.instance=/var/lib/neo4j"
702  , "-Dfile.encoding=UTF-8"
703  , "org.neo4j.server.Bootstrapper")
704 
705  neonode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/java', neoprocargs
706  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
707  # We'll be missing the value of 'port'
708  (prio, table, missing) = neo4j.specmatch(None, (neonode,))
709  self.assertEqual(prio, MonitoringRule.PARTMATCH)
710  self.assertEqual(missing, ['port'])
711  # Now fill in the port value
712  neonode.port=7474
713  (prio, table) = neo4j.specmatch(None, (neonode,))
714  self.assertEqual(prio, MonitoringRule.HIGHPRIOMATCH)
715  self.assertEqual(table['monitortype'], 'neo4j')
716  self.assertEqual(table['monitorclass'], 'ocf')
717  self.assertEqual(table['provider'], 'assimilation')
718  keys = table.keys()
719  keys.sort()
720  self.assertEqual(str(keys), "['arglist', 'monitorclass', 'monitortype', 'provider']")
721  arglist = table['arglist']
722  keys = arglist.keys()
723  keys.sort()
724  self.assertEqual(keys, ['home', 'neo4j', 'port'])
725  self.assertEqual(arglist['port'], '7474')
726  self.assertEqual(arglist['home'], '/var/lib/neo4j')
727  self.assertEqual(arglist['neo4j'], 'neo4j')
728 
730  # Clean things out so we only see what we want to see...
731  ocf_string = '''{
732 # comment
733  "class": "ocf",
734  "type": "neo4j",
735  "provider": "assimilation",
736  "classconfig": [
737  [null, "@basename()", "java$"],
738  [null, "argv[-1]", "org\\.neo4j\\.server\\.Bootstrapper$"],
739  ["PORT", "serviceport", "[0-9]+$"],
740  ["NEOHOME", "@argequals(-Dneo4j.home)", "/.*"]
741  ]
742 }'''
743  ocf = MonitoringRule.ConstructFromString(ocf_string)
744  self.assertTrue(isinstance(ocf, OCFMonitoringRule))
745  lsb_string = '''{
746 # comment
747  "class": "lsb",
748  "type": "neo4j",
749  "classconfig": [
750  ["@basename()", "java$"],
751  ["argv[-1]", "org\\.neo4j\\.server\\.Bootstrapper$"],
752  ]
753 }'''
754  lsb = MonitoringRule.ConstructFromString(lsb_string)
755  self.assertTrue(isinstance(lsb, LSBMonitoringRule))
756 
758  MonitoringRule.monitorobjects = {}
759  ocf_string = '''{
760  "class": "ocf", "type": "neo4j", "provider": "assimilation",
761  "classconfig": [
762  [null, "@basename()", "java$"],
763  [null, "argv[-1]", "org\\.neo4j\\.server\\.Bootstrapper$"],
764  ["PORT", "serviceport"],
765  ["NEOHOME", "@argequals(-Dneo4j.home)", "/.*"]
766  ]
767  }'''
768  MonitoringRule.ConstructFromString(ocf_string)
769  lsb_string = '''{
770  "class": "lsb", "type": "neo4j",
771  "classconfig": [
772  ["@basename()", "java$"],
773  ["argv[-1]", "org\\.neo4j\\.server\\.Bootstrapper$"],
774  ]
775  }'''
776  MonitoringRule.ConstructFromString(lsb_string)
777  neoprocargs = ("/usr/bin/java", "-cp"
778  , "/var/lib/neo4j/lib/concurrentlinkedhashmap-lru-1.3.1.jar:"
779  "AND SO ON:"
780  "/var/lib/neo4j/system/lib/slf4j-api-1.6.2.jar:"
781  "/var/lib/neo4j/conf/", "-server", "-XX:"
782  "+DisableExplicitGC"
783  , "-Dneo4j.home=/var/lib/neo4j"
784  , "-Dneo4j.instance=/var/lib/neo4j"
785  , "-Dfile.encoding=UTF-8"
786  , "org.neo4j.server.Bootstrapper")
787 
788  neonode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/java', neoprocargs
789  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
790  #neonode.serviceport=7474
791  first = MonitoringRule.findbestmatch((neonode,))
792  second = MonitoringRule.findbestmatch((neonode,), False)
793  list1 = MonitoringRule.findallmatches((neonode,))
794  neonode.serviceport=7474
795  third = MonitoringRule.findbestmatch((neonode,))
796  list2 = MonitoringRule.findallmatches((neonode,))
797 
798  # first should be the LSB instance
799  self.assertEqual(first[1]['monitorclass'], 'lsb')
800  self.assertEqual(first[0], MonitoringRule.LOWPRIOMATCH)
801  # second should be the incomplete OCF instance
802  self.assertEqual(second[1]['monitorclass'], 'ocf')
803  self.assertEqual(second[0], MonitoringRule.PARTMATCH)
804  # third should be the high priority OCF instance
805  self.assertEqual(third[1]['monitorclass'], 'ocf')
806  self.assertEqual(third[0], MonitoringRule.HIGHPRIOMATCH)
807  # list1 should be the incomplete OCF and the complete LSB - in that order
808  self.assertEqual(len(list1), 2)
809  # They should come out sorted by monitorclass
810  self.assertEqual(list1[0][0], MonitoringRule.LOWPRIOMATCH)
811  self.assertEqual(list1[0][1]['monitorclass'], 'lsb')
812  self.assertEqual(list1[1][0], MonitoringRule.PARTMATCH)
813  self.assertEqual(list1[1][1]['monitorclass'], 'ocf')
814  # third should be a complete OCF match
815  # list2 should be the complete OCF and the complete OCF - in that order
816  self.assertEqual(len(list2), 2)
817  self.assertEqual(list2[0][0], MonitoringRule.LOWPRIOMATCH)
818  self.assertEqual(list2[0][1]['monitorclass'], 'lsb')
819  self.assertEqual(list2[1][0], MonitoringRule.HIGHPRIOMATCH)
820  self.assertEqual(list2[1][1]['monitorclass'], 'ocf')
821 
823  MonitoringRule.monitorobjects = {}
824  ocf_string = '''{
825  "class": "ocf", "type": "neo4j", "provider": "assimilation",
826  "classconfig": [
827  ["classpath", "@flagvalue(-cp)"],
828  ["ipaddr", "@serviceip(JSON_procinfo.listenaddrs)"],
829  ["port", "@serviceport()", "[0-9]+$"]
830  ]
831  }'''
832  ssh_json = '''{
833  "exe": "/usr/sbin/sshd",
834  "argv": [ "/usr/sbin/sshd", "-D" ],
835  "uid": "root",
836  "gid": "root",
837  "cwd": "/",
838  "listenaddrs": {
839  "0.0.0.0:22": {
840  "proto": "tcp",
841  "addr": "0.0.0.0",
842  "port": 22
843  },
844  ":::22": {
845  "proto": "tcp6",
846  "addr": "::",
847  "port": 22
848  }
849  }
850  }'''
851  neo4j_json = '''{
852  "exe": "/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java",
853  "argv": [ "/usr/bin/java", "-cp", "/var/lib/neo4j/lib/concurrentlinkedhashmap-lru-1.3.1.jar: ...", "-server", "-XX:+DisableExplicitGC", "-Dorg.neo4j.server.properties=conf/neo4
854  j-server.properties", "-Djava.util.logging.config.file=conf/logging.properties", "-Dlog4j.configuration=file:conf/log4j.properties", "-XX:
855  +UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", "-Dneo4j.home=/var/lib/neo4j", "-Dneo4j.instance=/var/lib/neo4j", "-Dfile.encoding=
856  UTF-8", "org.neo4j.server.Bootstrapper" ],
857  "uid": "neo4j",
858  "gid": "neo4j",
859  "cwd": "/var/lib/neo4j",
860  "listenaddrs": {
861  ":::1337": {
862  "proto": "tcp6",
863  "addr": "::",
864  "port": 1337
865  },
866  ":::39185": {
867  "proto": "tcp6",
868  "addr": "::",
869  "port": 39185
870  }
871  }
872  }'''
873  bacula_json = '''{
874  "exe": "/usr/sbin/bacula-dir",
875  "argv": [ "/usr/sbin/bacula-dir", "-c", "/etc/bacula/bacula-dir.conf", "-u", "bacula", "-g", "bacula" ],
876  "uid": "bacula",
877  "gid": "bacula",
878  "cwd": "/",
879  "listenaddrs": {
880  "10.10.10.5:9101": {
881  "proto": "tcp",
882  "addr": "10.10.10.5",
883  "port": 9101
884  }
885  }
886  }'''
887  MonitoringRule.ConstructFromString(ocf_string)
888  neoargs = pyConfigContext(neo4j_json)['argv']
889  testnode = ProcessNode('global', 'foofred', 'fred', '/usr/bin/java', neoargs
890  , 'root', 'root', '/', roles=(CMAconsts.ROLE_server,))
891 
892  testnode.JSON_procinfo = neo4j_json
893  (prio, match) = MonitoringRule.findbestmatch((testnode,))
894  self.assertEqual(prio, MonitoringRule.HIGHPRIOMATCH)
895  self.assertEqual(match['arglist']['ipaddr'], '::1')
896  self.assertEqual(match['arglist']['port'], '1337')
897 
898  testnode.JSON_procinfo = ssh_json
899  (prio, match) = MonitoringRule.findbestmatch((testnode,))
900  self.assertEqual(prio, MonitoringRule.HIGHPRIOMATCH)
901  self.assertEqual(match['arglist']['port'], '22')
902  self.assertEqual(match['arglist']['ipaddr'], '127.0.0.1')
903 
904  testnode.JSON_procinfo = bacula_json
905  (prio, match) = MonitoringRule.findbestmatch((testnode,))
906  self.assertEqual(prio, MonitoringRule.HIGHPRIOMATCH)
907  self.assertEqual(match['arglist']['port'], '9101')
908  self.assertEqual(match['arglist']['ipaddr'], '10.10.10.5')
909 
910 
911 
913  # @TODO What I have in mind for this test is that it
914  # actually construct an auto-generated OCF monitoring node and activate it
915  # It will have to add name, timeout and repeat intervals before activating it.
916  pass
917 
918 
919  @class_teardown
920  def tearDown(self):
922 
923 
924 if __name__ == "__main__":
925  run()