The Assimilation Monitoring Project
cma_test.py
Go to the documentation of this file.
00001 # vim: smartindent tabstop=4 shiftwidth=4 expandtab
00002 _suites = ['all', 'cma']
00003 import sys
00004 sys.path.append("../cma")
00005 sys.path.append("/usr/local/lib/python2.7/dist-packages")
00006 from testify import *
00007 from testify.utils import turtle
00008 
00009 from frameinfo import *
00010 from AssimCclasses import *
00011 import gc, sys, time, collections, os
00012 from cma import *
00013 
00014 
00015 WorstDanglingCount = 0
00016 CheckForDanglingClasses = True
00017 DEBUG=False
00018 DoAudit=True
00019 SavePackets=True
00020 doHBDEAD=False
00021 MaxDrone=4
00022 MaxDrone=3
00023 MaxDrone=10000
00024 MaxDrone=5
00025 BuildListOnly = True
00026 
00027 t1 = MaxDrone
00028 if t1 < 1000: t1 = 1000
00029 t2 = MaxDrone/100
00030 if t2 < 10: t2 = 10
00031 t3 = t2
00032 
00033 if BuildListOnly:
00034     doHBDEAD=False
00035     SavePackets=False
00036     DoAudit=False
00037     CheckForDanglingClasses=False
00038     DEBUG=False
00039 
00040 
00041 
00042 #gc.set_threshold(t1, t2, t3)
00043 
00044 def assert_no_dangling_Cclasses():
00045     global CheckForDanglingClasses
00046     global WorstDanglingCount
00047     CMAdb.cdb = None
00048     CMAdb.io = None
00049     CMAdb.TheOneRing = None
00050     HbRing.ringnames = {}
00051     gc.collect()    # For good measure...
00052     count =  proj_class_live_object_count()
00053     #print >>sys.stderr, "CHECKING FOR DANGLING CLASSES (%d)..." % count
00054     # Avoid cluttering the output up with redundant messages...
00055     if count > WorstDanglingCount and CheckForDanglingClasses:
00056         WorstDanglingCount = count
00057         proj_class_dump_live_objects()
00058         raise AssertionError, "Dangling C-class objects - %d still around" % count
00059 
00060 # Values to substitute into this string via '%' operator:
00061 # dronedesignation (%s) MAC address byte (%02x), MAC address byte (%02x), IP address (%s)
00062 netdiscoveryformat='''
00063 {
00064   "discovertype": "netconfig",
00065   "description": "IP Network Configuration",
00066   "source": "netconfig",
00067   "host": "%s",
00068   "data": {
00069     "eth0": {
00070     "address": "00:1b:fc:1b:%02x:%02x",
00071     "carrier": 1,
00072     "duplex": "full",
00073     "mtu": 1500,
00074     "operstate": "up",
00075     "speed": 1000,
00076     "default_gw": true,
00077     "ipaddrs": { "%s/16": {"brd":"10.20.255.255", "scope":"global", "name":"eth0"}}
00078     }, 
00079     "lo": {
00080     "address": "00:00:00:00:00:00",
00081     "carrier": 1,
00082     "mtu": 16436,
00083     "operstate": "unknown",
00084     "ipaddrs": { "127.0.0.1/8": {"scope":"host"}, "::1/128": {"scope":"host"}}
00085     }
00086   }
00087 }
00088 '''
00089 
00090 
00091 byte1 = 10
00092 byte2 = 20
00093 
00094 def droneipaddress(hostnumber):
00095     byte2 = int(hostnumber / 65536)
00096     byte3 = int((hostnumber / 256) % 256)
00097     byte4 = hostnumber % 256
00098     return pyNetAddr([byte1,byte2,byte3,byte4],)
00099 
00100 def dronedesignation(hostnumber):
00101     return 'drone%06d' % hostnumber
00102 
00103 def hostdiscoveryinfo(hostnumber):
00104     byte3 = int(hostnumber / 256)
00105     byte4 = hostnumber % 256
00106     s = str(droneipaddress(hostnumber))
00107     return netdiscoveryformat % (dronedesignation(hostnumber), byte3, byte4, s)
00108     
00109 def geninitconfig(ouraddr):
00110     return {
00111         'cmainit':  ouraddr,    # Initial 'hello' address
00112         'cmaaddr':  ouraddr,    # not sure what this one does...
00113         'cmadisc':  ouraddr,    # Discovery packets sent here
00114         'cmafail':  ouraddr,    # Failure packets sent here
00115         'cmaport':  1984,
00116         'hbport':   1984,
00117         'outsig':   pySignFrame(1),
00118         'deadtime': 10*1000000,
00119         'warntime': 3*1000000,
00120         'hbtime':   1*1000000,
00121         }
00122 
00123 class AUDITS(TestCase):
00124     def auditadrone(self, droneid):
00125         designation = dronedesignation(droneid)
00126         droneip = droneipaddress(droneid)
00127         droneipstr = str(droneip)
00128         # Did the drone get put in the DroneInfo table?
00129         drone=DroneInfo.find(designation)
00130         self.assertTrue(drone is not None)
00131         # Did the drone's list of addresses get updated?
00132         ipnodes = drone.node.get_related_nodes('incoming', 'iphost')
00133         self.assertEqual(len(ipnodes), 1)
00134         ipnode = ipnodes[0]
00135         ipnodeaddr = ipnode['name']
00136         ipnodeaddrfoo = ipnodeaddr + '/16'
00137         json = drone['JSON_netconfig']
00138         jsobj = pyConfigContext(init=json)
00139         jsdata = jsobj['data']
00140         eth0obj = jsdata['eth0']
00141         # Does the drone address table match the info from JSON?
00142         eth0addrs = eth0obj['ipaddrs']
00143         self.assertTrue(eth0addrs.has_key(ipnodeaddrfoo))
00144         # Do we know that eth0 is the default gateway?
00145         self.assertEqual(eth0obj['default_gw'], 1)
00146         
00147         # the JSON should have exactly 5 top-level keys
00148         self.assertEqual(len(jsobj.keys()), 5)
00149         # Was the JSON host name saved away correctly?
00150         self.assertEqual(jsobj['host'], designation)
00151     
00152         return
00153         peercount=0
00154         ringcount=0
00155         for ring in drone.ringmemberships.values():
00156             ringcount += 1
00157             # How many peers should it have?
00158             if len(ring.memberlist) == 1:
00159                 pass # No peers in this ring...
00160             elif len(ring.memberlist) == 2:
00161                 peercount += 1
00162             else:
00163                 peercount += 2
00164             # Make sure we're listed under our designation
00165             #print >>sys.stderr, "DRONE is %s status %s" % (drone.designation, drone.status)
00166             #print >>sys.stderr, "DRONE ringmemberships:", drone.ringmemberships.keys()
00167             self.assertEqual(ring.members[drone.designation].designation, drone.designation)
00168             self.assertEqual(len(ring.members), len(ring.memberlist))
00169         if drone.status != 'dead':
00170             # We have to be members of at least one ring...
00171             self.assertTrue(ringcount >= 1)
00172             # Drone should be a member of one ring (for now)
00173             self.assertEqual(len(drone.ringmemberships),1)
00174         # Do we have the right number of ring peers?
00175         #print >>sys.stderr, "Checking peer count for drone %s (%d)" % (drone, len(drone.ringpeers))
00176         self.assertEqual(len(drone.ringpeers), peercount)
00177 
00178     def auditSETCONFIG(self, packetreturn, droneid, configinit):
00179         toaddr = packetreturn[0]
00180         sentfs = packetreturn[1]
00181         droneip = droneipaddress(droneid)
00182         
00183         # Was it a SETCONFIG packet?
00184         self.assertEqual(sentfs.get_framesettype(), FrameSetTypes.SETCONFIG)
00185         # Was the SETCONFIG sent back to the drone?
00186         self.assertEqual(toaddr,droneip)
00187         # Lets check the number of Frames in the SETCONFIG Frameset
00188         configlen =  len(configinit)-1  # We do not send Frames in configinfo
00189         expectedlen = 2 * configlen + 4 # each address has a port that goes with it
00190         self.assertEqual(expectedlen, len(sentfs))  # Was it the right size?
00191 
00192     def auditaRing(self, ringname):
00193         'Verify that each ring has its neighbor pairs set up properly'
00194         # Check that each element of the ring is connected to its neighbors...
00195         ring = HbRing.ringnames[ringname]
00196         #print "Ring %s: %s" % (ringname, str(ring))
00197         listmembers = {}
00198         ringmembers = {}
00199         for drone in ring.members():
00200             ringmembers[drone.node['name']] = None
00201         for drone in ring.membersfromlist():
00202             listmembers[drone.node['name']] = None
00203         for drone in listmembers.keys():
00204             self.assertTrue(drone in ringmembers)
00205         for drone in ringmembers.keys():
00206             self.assertTrue(drone in listmembers)
00207         
00208 
00209 def auditalldrones():
00210     audit = AUDITS()
00211     dronetype = CMAdb.cdb.nodetypetbl['Drone']
00212     droneobjs = dronetype.get_related_nodes('incoming', 'IS_A')
00213     numdrones = len(droneobjs)
00214     for droneid in range(0,numdrones):
00215         audit.auditadrone(droneid+1)
00216 
00217 def auditallrings():
00218     audit = AUDITS()
00219     for ring in HbRing.ringnames:
00220         audit.auditaRing(ring)
00221 
00222 class TestIO:
00223     '''A pyNetIOudp replacement for testing.  It is given a list of packets to be 'read' and in turn
00224     saves all the packets it 'writes' for us to inspect.
00225     '''
00226     def __init__(self, addrframesetpairs, sleepatend=0):
00227         if isinstance(addrframesetpairs, tuple):
00228             addrframesetpairs = addrframesetpairs
00229         self.inframes = addrframesetpairs
00230         self.packetswritten=[]
00231         self.packetsread=0
00232         self.sleepatend=sleepatend
00233         self.index=0
00234         self.writecount=0
00235         self.config = {CONFIGNAME_CMAPORT: 1984}
00236 
00237     def recvframesets(self):
00238         # Audit after each packet is processed - and once before the first packet.
00239         if DoAudit:
00240             if self.packetsread < 200 or (self.packetsread % 500) == 0:
00241                 auditalldrones()
00242                 auditallrings()
00243         if self.index >= len(self.inframes):
00244             time.sleep(self.sleepatend)
00245             raise StopIteration('End of Packets')
00246         ret = self.inframes[self.index]
00247         self.index += 1
00248         self.packetsread += len(ret[1])
00249         return ret
00250 
00251     def sendframesets(self, dest, fslist):
00252         if not isinstance(fslist, collections.Sequence):
00253             return self._sendaframeset(dest, fslist)
00254         for fs in fslist:
00255             self._sendaframeset(dest, fs)
00256 
00257     def _sendaframeset(self, dest, fslist):
00258         self.writecount += 1
00259         if SavePackets:
00260             self.packetswritten.append((dest,fslist))
00261 
00262     def getmaxpktsize(self):    return 60000
00263     def getfd(self):            return 4
00264     def bindaddr(self, addr):   return True
00265     def mcastjoin(self, addr):  return True
00266     def setblockio(self, tf):   return
00267 
00268     def dumppackets(self):
00269         print >>sys.stderr, 'Sent %d packets' % len(self.packetswritten)
00270         for packet in self.packetswritten:
00271             print '%s (%s)' % (packet[0], packet[1])
00272     
00273 
00274 class TestTestInfrastructure(TestCase):
00275     def test_eof(self):
00276         'Get EOF with empty input'
00277         if BuildListOnly: return
00278         framesets=[]
00279         io = TestIO(framesets, 0)
00280         CMAdb.initglobal(io, True)
00281         # just make sure it seems to do the right thing
00282         self.assertRaises(StopIteration, io.recvframesets)
00283         assert_no_dangling_Cclasses()
00284 
00285     def test_get1pkt(self):
00286         'Read a single packet'
00287         if BuildListOnly: return
00288         otherguy = pyNetAddr([1,2,3,4],)
00289         strframe1=pyCstringFrame(FrameTypes.CSTRINGVAL, "Hello, world.")
00290         fs = pyFrameSet(42)
00291         fs.append(strframe1)
00292         framesets=((otherguy, (strframe1,)),)
00293         io = TestIO(framesets, 0)
00294         CMAdb.initglobal(io, True)
00295         gottenfs = io.recvframesets()
00296         self.assertEqual(len(gottenfs), 2)
00297         self.assertEqual(gottenfs, framesets[0])
00298         self.assertRaises(StopIteration, io.recvframesets)
00299 
00300     def test_echo1pkt(self):
00301         'Read a packet and write it back out'
00302         if BuildListOnly: return
00303         strframe1=pyCstringFrame(FrameTypes.CSTRINGVAL, "Hello, world.")
00304         fs = pyFrameSet(42)
00305         fs.append(strframe1)
00306         otherguy = pyNetAddr([1,2,3,4],)
00307         framesets=((otherguy, (strframe1,)),)
00308         io = TestIO(framesets, 0)
00309         CMAdb.initglobal(io, True)
00310         fslist = io.recvframesets()     # read in a packet
00311         self.assertEqual(len(fslist), 2)
00312         self.assertEqual(fslist, framesets[0])
00313         io.sendframesets(fslist[0], fslist[1])  # echo it back out
00314         self.assertEqual(len(io.packetswritten), 1)
00315         self.assertEqual(len(io.packetswritten), len(framesets))
00316         self.assertRaises(StopIteration, io.recvframesets)
00317 
00318     @class_teardown
00319     def tearDown(self):
00320         assert_no_dangling_Cclasses()
00321 
00322 class TestCMABasic(TestCase):
00323     def test_startup(self):
00324         '''A semi-interesting test: We send a STARTUP message and get back a
00325         SETCONFIG message with lots of good stuff in it.'''
00326         if BuildListOnly: return
00327         droneid = 1
00328         droneip = droneipaddress(droneid)
00329         designation = dronedesignation(droneid)
00330         designationframe=pyCstringFrame(FrameTypes.HOSTNAME, designation)
00331         dronediscovery=hostdiscoveryinfo(droneid)
00332         discoveryframe=pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery)
00333         fs = pyFrameSet(FrameSetTypes.STARTUP)
00334         fs.append(designationframe)
00335         fs.append(discoveryframe)
00336         fsin = ((droneip, (fs,)),)
00337         io = TestIO(fsin,0)
00338         CMAdb.initglobal(io, True)
00339         OurAddr = pyNetAddr((127,0,0,1),1984)
00340         disp = MessageDispatcher({FrameSetTypes.STARTUP: DispatchSTARTUP()})
00341         configinit = geninitconfig(OurAddr)
00342         config = pyConfigContext(init=configinit)
00343         listener = PacketListener(config, disp, io=io)
00344         # We send the CMA an intial STARTUP packet
00345         self.assertRaises(StopIteration, listener.listen) # We audit after each packet is processed
00346         # Let's see what happened...
00347 
00348         self.assertEqual(len(io.packetswritten), 2) # Did we send out two packets?
00349                             # Note that this change over time
00350                             # As we change discovery...
00351         AUDITS().auditSETCONFIG(io.packetswritten[0], droneid, configinit)
00352     # Drone and Ring tables are automatically audited after each packet
00353 
00354     def test_several_startups(self):
00355         '''A very interesting test: We send a STARTUP message and get back a
00356         SETCONFIG message and then send back a bunch of discovery requests.'''
00357         OurAddr = pyNetAddr((10,10,10,5), 1984)
00358         configinit = geninitconfig(OurAddr)
00359         fsin = []
00360         droneid=0
00361         for droneid in range(1,MaxDrone+1):
00362             droneip = droneipaddress(droneid)
00363             designation = dronedesignation(droneid)
00364             designationframe=pyCstringFrame(FrameTypes.HOSTNAME, designation)
00365             dronediscovery=hostdiscoveryinfo(droneid)
00366             discoveryframe=pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery)
00367             fs = pyFrameSet(FrameSetTypes.STARTUP)
00368             fs.append(designationframe)
00369             fs.append(discoveryframe)
00370             fsin.append((droneip, (fs,)))
00371         addrone = droneipaddress(1)
00372         maxdrones = droneid
00373         if doHBDEAD:
00374             for droneid in range(2,maxdrones+1):
00375                 droneip = droneipaddress(droneid)
00376                 deadframe=pyAddrFrame(FrameTypes.IPADDR, addrstring=droneip)
00377                 fs = pyFrameSet(FrameSetTypes.HBDEAD)
00378                 fs.append(deadframe)
00379                 fsin.append((addrone, (fs,)))
00380         io = TestIO(fsin)
00381         CMAdb.initglobal(io, True)
00382         disp = MessageDispatcher( {
00383         FrameSetTypes.STARTUP: DispatchSTARTUP(),
00384         FrameSetTypes.HBDEAD: DispatchHBDEAD(),
00385         })
00386         config = pyConfigContext(init=configinit)
00387         listener = PacketListener(config, disp, io=io)
00388         # We send the CMA a BUNCH of intial STARTUP packets
00389         try:
00390           listener.listen()
00391         except StopIteration as foo:
00392             pass
00393         #self.assertRaises(StopIteration, listener.listen)
00394     # We audit after each packet is processed
00395         # The auditing code will make sure all is well...
00396         # But it doesn't know how many drones we just registered
00397         droneroot = CMAdb.cdb.nodetypetbl['Drone']
00398         Dronerels = droneroot.get_relationships('incoming', 'IS_A')
00399         self.assertEqual(len(Dronerels), maxdrones)
00400         if doHBDEAD:
00401             partnercount = 0
00402             livecount = 0
00403             ringcount = 0
00404             for dronerel in Dronerels:
00405                 drone1 = DroneInfo(dronerel.start_node)
00406                 if drone1.node['status'] != 'dead': livecount += 1
00407                 drone1rels = drone1.node.get_relationships()
00408                 for rel in drone1rels:
00409                     reltype = rel.type
00410                     if reltype.startswith(HbRing.memberprefix):
00411                          ringcount += 1
00412                     if reltype.startswith(HbRing.nextprefix):
00413                          partnercount += 1
00414             self.assertEqual(partnercount, 0)
00415             self.assertEqual(livecount, 1)
00416             self.assertEqual(ringcount, 1)
00417         if DoAudit:
00418             auditalldrones()
00419             auditallrings()
00420 
00421         print "The CMA read %d packets."  % io.packetsread
00422         print "The CMA wrote %d packets." % io.writecount
00423         #io.dumppackets()
00424 
00425 
00426     @class_teardown
00427     def tearDown(self):
00428         assert_no_dangling_Cclasses()
00429 
00430 if __name__ == "__main__":
00431     run()
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines