示例#1
0
    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)
示例#2
0
    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)
示例#3
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
示例#4
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()
示例#5
0
    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
    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 Store.debug:
            raise ValueError('Debug enabled')
        if DEBUG:
            print >> sys.stderr, 'Running test_several_startups()'
        AssimEvent.disable_all_observers()
        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
        Drones = CMAdb.store.load_cypher_nodes("START n=node:Drone('*:*') RETURN n", 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()
        del io
    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
示例#8
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