|
The Assimilation Monitoring Project
|
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()