def test_startup(self): '''A semi-interesting test: We send a STARTUP message and get back a SETCONFIG message with lots of good stuff in it.''' if BuildListOnly: return if DEBUG: print >> sys.stderr, 'Running test_startup()' droneid = 1 droneip = droneipaddress(droneid) designation = dronedesignation(droneid) designationframe = pyCstringFrame(FrameTypes.HOSTNAME, designation) dronediscovery = hostdiscoveryinfo(droneid) discoveryframe = pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery) fs = pyFrameSet(FrameSetTypes.STARTUP) fs.append(designationframe) fs.append(discoveryframe) fsin = ((droneip, (fs, )), ) io = TestIO(fsin, 0) #print >> sys.stderr, 'CMAinit: %s' % str(CMAinit) #print >> sys.stderr, 'CMAinit.__init__: %s' % str(CMAinit.__init__) CMAinit(io, cleanoutdb=True, debug=DEBUG) assimcli_check('loadqueries') OurAddr = pyNetAddr((127, 0, 0, 1), 1984) disp = MessageDispatcher({FrameSetTypes.STARTUP: DispatchSTARTUP()}, encryption_required=False) configinit = geninitconfig(OurAddr) config = pyConfigContext(init=configinit) listener = PacketListener(config, disp, io=io, encryption_required=False) io.mainloop = listener.mainloop TestIO.mainloop = listener.mainloop # We send the CMA an intial STARTUP packet listener.listen() # Let's see what happened... #print >> sys.stderr, ('WRITTEN: %s' % len(io.packetswritten)) self.assertEqual(len(io.packetswritten), 2) # Did we send out two packets? # Note that this change over time # As we change discovery... AUDITS().auditSETCONFIG(io.packetswritten[0], droneid, configinit) io.cleanio() assimcli_check("query allips", 1) assimcli_check("query allservers", 1) assimcli_check("query findip %s" % str(droneip), 1) assimcli_check("query shutdown", 0) assimcli_check("query crashed", 0) assimcli_check("query unknownips", 0)
def main(): 'Main program for the CMA (Collective Management Authority)' py2neo_major_version = int(PY2NEO_VERSION.partition('.')[0]) if py2neo_major_version not in SUPPORTED_PY2NEO_VERSIONS: raise EnvironmentError('py2neo version %s not supported' % PY2NEO_VERSION) DefaultPort = 1984 # VERY Linux-specific - but useful and apparently correct ;-) PrimaryIPcmd = \ "ip address show primary scope global | grep '^ *inet' | sed -e 's%^ *inet *%%' -e 's%/.*%%'" ipfd = os.popen(PrimaryIPcmd, 'r') OurAddrStr = ('%s:%d' % (ipfd.readline().rstrip(), DefaultPort)) ipfd.close() parser = optparse.OptionParser( prog='CMA', version=AssimCtypes.VERSION_STRING, description= 'Collective Management Authority for the Assimilation System', usage='cma.py [--bind address:port]') parser.add_option( '-b', '--bind', action='store', default=None, dest='bind', metavar='address:port-to-bind-to', help='Address:port to listen to - for nanoprobes to connect to') parser.add_option( '-d', '--debug', action='store', default=0, dest='debug', help= 'enable debug for CMA and libraries - value is debug level for C libraries.' ) parser.add_option('-s', '--status', action='store_true', default=False, dest='status', help='Return status of running CMA') parser.add_option('-k', '--kill', action='store_true', default=False, dest='kill', help='Shut down running CMA.') parser.add_option('-e', '--erasedb', action='store_true', default=False, dest='erasedb', help='Erase Neo4J before starting') parser.add_option('-f', '--foreground', action='store_true', default=False, dest='foreground', help='keep the CMA from going into the background') parser.add_option('-p', '--pidfile', action='store', default='/var/run/assimilation/cma', dest='pidfile', metavar='pidfile-pathname', help='full pathname of where to locate our pid file') parser.add_option('-T', '--trace', action='store_true', default=False, dest='doTrace', help='Trace CMA execution') parser.add_option('-u', '--user', action='store', default=CMAUSERID, dest='userid', metavar='userid', help='userid to run the CMA as') opt = parser.parse_args()[0] from AssimCtypes import daemonize_me, assimilation_openlog, are_we_already_running, \ kill_pid_service, pidrunningstat_to_status, remove_pid_file, rmpid_and_exit_on_signal if opt.status: rc = pidrunningstat_to_status(are_we_already_running( opt.pidfile, None)) return rc if opt.kill: if kill_pid_service(opt.pidfile, 15) < 0: print >> sys.stderr, "Unable to stop CMA." return 1 return 0 opt.debug = int(opt.debug) # This doesn't seem to work no matter where I invoke it... # But if we don't fork in daemonize_me() ('C' code), it works great... # def cleanup(): # remove_pid_file(opt.pidfile) # atexit.register(cleanup) # signal.signal(signal.SIGTERM, lambda sig, stack: sys.exit(0)) # signal.signal(signal.SIGINT, lambda sig, stack: sys.exit(0)) from cmadb import CMAdb CMAdb.running_under_docker() make_pid_dir(opt.pidfile, opt.userid) make_key_dir(CRYPTKEYDIR, opt.userid) cryptwarnings = pyCryptCurve25519.initkeys() for warn in cryptwarnings: print >> sys.stderr, ("WARNING: %s" % warn) #print >> sys.stderr, 'All known key ids:' keyids = pyCryptFrame.get_key_ids() keyids.sort() for keyid in keyids: if not keyid.startswith(CMA_KEY_PREFIX): try: # @FIXME This is not an ideal way to associate identities with hosts # in a multi-tenant environment # @FIXME - don't think I need to do the associate_identity at all any more... hostname, notused_post = keyid.split('@@', 1) notused_post = notused_post pyCryptFrame.associate_identity(hostname, keyid) except ValueError: pass #print >> sys.stderr, '> %s/%s' % (keyid, pyCryptFrame.get_identity(keyid)) daemonize_me(opt.foreground, '/', opt.pidfile, 20) rmpid_and_exit_on_signal(opt.pidfile, signal.SIGTERM) # Next statement can't appear before daemonize_me() or bind() fails -- not quite sure why... assimilation_openlog("cma") from packetlistener import PacketListener from messagedispatcher import MessageDispatcher from dispatchtarget import DispatchTarget from monitoring import MonitoringRule from AssimCclasses import pyNetAddr, pySignFrame, pyReliableUDP, \ pyPacketDecoder from AssimCtypes import CONFIGNAME_CMAINIT, CONFIGNAME_CMAADDR, CONFIGNAME_CMADISCOVER, \ CONFIGNAME_CMAFAIL, CONFIGNAME_CMAPORT, CONFIGNAME_OUTSIG, CONFIGNAME_COMPRESSTYPE, \ CONFIGNAME_COMPRESS, proj_class_incr_debug, LONG_LICENSE_STRING, MONRULEINSTALL_DIR if opt.debug: print >> sys.stderr, ('Setting debug to %s' % opt.debug) for debug in range(opt.debug): debug = debug print >> sys.stderr, ('Incrementing C-level debug by one.') proj_class_incr_debug(None) # Input our monitoring rule templates # They only exist in flat files and in memory - they aren't in the database MonitoringRule.load_tree(MONRULEINSTALL_DIR) print >> sys.stderr, ('Monitoring rules loaded from %s' % MONRULEINSTALL_DIR) execobserver_constraints = { 'nodetype': [ 'Drone', 'IPaddrNode', 'MonitorAction', 'NICNode', 'ProcessNode', 'SystemNode', ] } ForkExecObserver(constraints=execobserver_constraints, scriptdir=NOTIFICATION_SCRIPT_DIR) print >> sys.stderr, ('Fork/Event observer dispatching from %s' % NOTIFICATION_SCRIPT_DIR) if opt.bind is not None: OurAddrStr = opt.bind OurAddr = pyNetAddr(OurAddrStr) if OurAddr.port() == 0: OurAddr.setport(DefaultPort) try: configinfo = ConfigFile(filename=CMAINITFILE) except IOError: configinfo = ConfigFile() if opt.bind is not None: bindaddr = pyNetAddr(opt.bind) if bindaddr.port() == 0: bindaddr.setport(ConfigFile[CONFIGNAME_CMAPORT]) configinfo[CONFIGNAME_CMAINIT] = bindaddr configinfo[CONFIGNAME_CMADISCOVER] = OurAddr configinfo[CONFIGNAME_CMAFAIL] = OurAddr configinfo[CONFIGNAME_CMAADDR] = OurAddr if (CONFIGNAME_COMPRESSTYPE in configinfo): configinfo[CONFIGNAME_COMPRESS] \ = pyCompressFrame(compression_method=configinfo[CONFIGNAME_COMPRESSTYPE]) configinfo[CONFIGNAME_OUTSIG] = pySignFrame(1) config = configinfo.complete_config() addr = config[CONFIGNAME_CMAINIT] # pylint is confused: addr is a pyNetAddr, not a pyConfigContext # pylint: disable=E1101 if addr.port() == 0: addr.setport(DefaultPort) ourport = addr.port() for elem in (CONFIGNAME_CMAINIT, CONFIGNAME_CMAADDR, CONFIGNAME_CMADISCOVER, CONFIGNAME_CMAFAIL): if elem in config: config[elem] = pyNetAddr(str(config[elem]), port=ourport) io = pyReliableUDP(config, pyPacketDecoder()) io.setrcvbufsize( 10 * 1024 * 1024) # No harm in asking - it will get us the best we can get... io.setsendbufsize( 1024 * 1024) # Most of the traffic volume is inbound from discovery drop_privileges_permanently(opt.userid) try: cmainit.CMAinit(io, cleanoutdb=opt.erasedb, debug=(opt.debug > 0)) except RuntimeError: remove_pid_file(opt.pidfile) raise for warn in cryptwarnings: CMAdb.log.warning(warn) if CMAdb.cdb.db.neo4j_version[0] not in SUPPORTED_NEO4J_VERSIONS: raise EnvironmentError('Neo4j version %s.%s.%s not supported' % CMAdb.cdb.db.neo4j_version) CMAdb.log.info('Listening on: %s' % str(config[CONFIGNAME_CMAINIT])) CMAdb.log.info('Requesting return packets sent to: %s' % str(OurAddr)) CMAdb.log.info('Socket input buffer size: %d' % io.getrcvbufsize()) CMAdb.log.info('Socket output buffer size: %d' % io.getsendbufsize()) keyids = pyCryptFrame.get_key_ids() keyids.sort() for keyid in keyids: CMAdb.log.info('KeyId %s Identity %s' % (keyid, pyCryptFrame.get_identity(keyid))) if CMAdb.debug: CMAdb.log.debug('C-library Debug was set to %s' % opt.debug) CMAdb.log.debug('TheOneRing created - id = %s' % CMAdb.TheOneRing) CMAdb.log.debug('Config Object sent to nanoprobes: %s' % config) jvmfd = os.popen('java -version 2>&1') jvers = jvmfd.readline() jvmfd.close() disp = MessageDispatcher(DispatchTarget.dispatchtable) neovers = CMAdb.cdb.db.neo4j_version neoversstring = (('%s.%s.%s' if len(neovers) == 3 else '%s.%s.%s%s') % neovers[0:3]) CMAdb.log.info('Starting CMA version %s - licensed under %s' % (AssimCtypes.VERSION_STRING, LONG_LICENSE_STRING)) CMAdb.log.info( 'Neo4j version %s // py2neo version %s // Python version %s // %s' % (('%s.%s.%s' % CMAdb.cdb.db.neo4j_version), str(py2neo.__version__), ('%s.%s.%s' % sys.version_info[0:3]), jvers)) if opt.foreground: print >> sys.stderr, ( 'Starting CMA version %s - licensed under %s' % (AssimCtypes.VERSION_STRING, LONG_LICENSE_STRING)) print >> sys.stderr, ( 'Neo4j version %s // py2neo version %s // Python version %s // %s' % (neoversstring, PY2NEO_VERSION, ('%s.%s.%s' % sys.version_info[0:3]), jvers)) if len(neovers) > 3: CMAdb.log.warning( 'Neo4j version %s is beta code - results not guaranteed.' % str(neovers)) # Important to note that we don't want PacketListener to create its own 'io' object # or it will screw up the ReliableUDP protocol... listener = PacketListener(config, disp, io=io) mandatory_modules = ['discoverylistener'] for mandatory in mandatory_modules: importlib.import_module(mandatory) #pylint is confused here... # pylint: disable=E1133 for optional in config['optional_modules']: importlib.import_module(optional) if opt.doTrace: import trace tracer = trace.Trace(count=False, trace=True) if CMAdb.debug: CMAdb.log.debug('Starting up traced listener.listen(); debug=%d' % opt.debug) if opt.foreground: print >> sys.stderr, ( 'cma: Starting up traced listener.listen() in foreground; debug=%d' % opt.debug) tracer.run('listener.listen()') else: if CMAdb.debug: CMAdb.log.debug( 'Starting up untraced listener.listen(); debug=%d' % opt.debug) if opt.foreground: print >> sys.stderr, ( 'cma: Starting up untraced listener.listen() in foreground; debug=%d' % opt.debug) # This is kind of a kludge, we should really look again at # at initializition and so on. # This module *ought* to be optional. # that would involve adding some Drone callbacks for creation of new Drones BestPractices(config, io, CMAdb.store, CMAdb.log, opt.debug) listener.listen() return 0
def test_several_startups(self): '''A very interesting test: We send a STARTUP message and get back a SETCONFIG message and then send back a bunch of discovery requests.''' if DEBUG: print >> sys.stderr, 'Running test_several_startups()' OurAddr = pyNetAddr((10, 10, 10, 5), 1984) configinit = geninitconfig(OurAddr) # Create the STARTUP FrameSets that our fake Drones should appear to send fsin = [] droneid = 0 for droneid in range(1, MaxDrone + 1): droneip = droneipaddress(droneid) designation = dronedesignation(droneid) designationframe = pyCstringFrame(FrameTypes.HOSTNAME, designation) dronediscovery = hostdiscoveryinfo(droneid) discoveryframe = pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery) fs = pyFrameSet(FrameSetTypes.STARTUP) fs.append(designationframe) fs.append(discoveryframe) fsin.append((droneip, (fs, ))) addrone = droneipaddress(1) maxdrones = droneid if doHBDEAD: # Create the HBDEAD FrameSets that our first fake Drone should appear to send # concerning the death of its dearly departed peers #print >> sys.stderr, 'KILLING THEM ALL!!!' for droneid in range(2, maxdrones + 1): droneip = droneipaddress(droneid) designation = dronedesignation(droneid) #deadframe=pyIpPortFrame(FrameTypes.IPPORT, addrstring=droneip) fs = pyFrameSet(FrameSetTypes.HBSHUTDOWN) #fs.append(deadframe) hostframe = pyCstringFrame(FrameTypes.HOSTNAME, designation) fs.append(hostframe) fsin.append((droneip, (fs, ))) io = TestIO(fsin) CMAinit(io, cleanoutdb=True, debug=DEBUG) assert CMAdb.io.config is not None assimcli_check('loadqueries') disp = MessageDispatcher( { FrameSetTypes.STARTUP: DispatchSTARTUP(), FrameSetTypes.HBDEAD: DispatchHBDEAD(), FrameSetTypes.HBSHUTDOWN: DispatchHBSHUTDOWN(), }, encryption_required=False) config = pyConfigContext(init=configinit) listener = PacketListener(config, disp, io=io, encryption_required=False) io.mainloop = listener.mainloop TestIO.mainloop = listener.mainloop # We send the CMA a BUNCH of intial STARTUP packets # and (optionally) a bunch of HBDEAD packets assert CMAdb.io.config is not None listener.listen() # We audit after each packet is processed # The auditing code will make sure all is well... # But it doesn't know how many drones we just registered query = neo4j.CypherQuery(CMAdb.cdb.db, "START n=node:Drone('*:*') RETURN n") Drones = CMAdb.store.load_cypher_nodes(query, Drone) Drones = [drone for drone in Drones] #print >> sys.stderr, 'WE NOW HAVE THESE DRONES:', Drones self.assertEqual(len(Drones), maxdrones) if doHBDEAD: # Verify that all drones except one are dead #livecount, partnercount, ringmemberships #self.check_live_counts(1, 0, 1) assimcli_check("query allservers", maxdrones) assimcli_check("query down", maxdrones - 1) assimcli_check("query crashed", 0) assimcli_check("query shutdown", maxdrones - 1) else: if maxdrones == 1: partnercount = 0 elif maxdrones == 2: partnercount = 2 else: partnercount = 2 * maxdrones # livecount partnercount ringmemberships #self.check_live_counts(maxdrones, partnercount, maxdrones) assimcli_check("query allservers", maxdrones) assimcli_check("query down", 0) assimcli_check("query shutdown", 0) assimcli_check("query unknownips", 0) for droneid in range(1, MaxDrone + 1): droneip = droneipaddress(droneid) assimcli_check("query findip %s" % str(droneip), 1) if DoAudit: auditalldrones() auditallrings() if DEBUG: print "The CMA read %d packets." % io.packetsread print "The CMA wrote %d packets." % io.writecount #io.dumppackets() io.config = None io.cleanio()
def test_startup(self): '''A semi-interesting test: We send a STARTUP message and get back a SETCONFIG message with lots of good stuff in it. and for good measure, we also send along some discovery packets. ''' if BuildListOnly: return if DEBUG: print >> sys.stderr, 'Running test_startup()' AssimEvent.disable_all_observers() from dispatchtarget import DispatchTarget droneid = 1 droneip = droneipaddress(droneid) designation = dronedesignation(droneid) designationframe=pyCstringFrame(FrameTypes.HOSTNAME, designation) dronediscovery=hostdiscoveryinfo(droneid) discoveryframe=pyCstringFrame(FrameTypes.JSDISCOVER, dronediscovery) fs = pyFrameSet(FrameSetTypes.STARTUP) fs.append(designationframe) fs.append(discoveryframe) fs2 = pyFrameSet(FrameSetTypes.JSDISCOVERY) osdiscovery=pyCstringFrame(FrameTypes.JSDISCOVER, self.OS_DISCOVERY) fs2.append(osdiscovery) fs3 = pyFrameSet(FrameSetTypes.JSDISCOVERY) ulimitdiscovery=pyCstringFrame(FrameTypes.JSDISCOVER, self.ULIMIT_DISCOVERY) fs3.append(ulimitdiscovery) fsin = ((droneip, (fs,)), (droneip, (fs2,)), (droneip, (fs3,))) io = TestIO(fsin,0) #print >> sys.stderr, 'CMAinit: %s' % str(CMAinit) #print >> sys.stderr, 'CMAinit.__init__: %s' % str(CMAinit.__init__) OurAddr = pyNetAddr((127,0,0,1),1984) configinit = geninitconfig(OurAddr) config = pyConfigContext(init=configinit) io.config = config CMAinit(io, cleanoutdb=True, debug=DEBUG) CMAdb.io.config = config assimcli_check('loadqueries') disp = MessageDispatcher(DispatchTarget.dispatchtable, encryption_required=False) listener = PacketListener(config, disp, io=io, encryption_required=False) io.mainloop = listener.mainloop TestIO.mainloop = listener.mainloop # We send the CMA an intial STARTUP packet listener.listen() # Let's see what happened... #print >> sys.stderr, ('READ: %s' % io.packetsread) #print >> sys.stderr, ('WRITTEN: %s' % len(io.packetswritten)) #print >> sys.stderr, ('PACKETS WRITTEN: %s' % str(io.packetswritten)) self.assertEqual(len(io.packetswritten), 2) # Did we send out four packets? # Note that this change over time # As we change discovery... self.assertEqual(io.packetsread, 3) # Did we read 3 packets? AUDITS().auditSETCONFIG(io.packetswritten[0], droneid, configinit) assimcli_check("query allips", 1) assimcli_check("query allservers", 1) assimcli_check("query findip %s" % str(droneip), 1) assimcli_check("query shutdown", 0) assimcli_check("query crashed", 0) assimcli_check("query unknownips", 0) CMAdb.io.config = config Drones = CMAdb.store.load_cypher_nodes("START n=node:Drone('*:*') RETURN n", Drone) Drones = [drone for drone in Drones] for drone in Drones: self.check_discovery(drone, (dronediscovery, self.OS_DISCOVERY, self.ULIMIT_DISCOVERY)) self.assertEqual(len(Drones), 1) # Should only be one drone io.config = None io.cleanio() del io del ulimitdiscovery, osdiscovery, Drones DispatchTarget.dispatchtable = {} del DispatchTarget