Example #1
0
class ContextModalityTimer(object):
    """Provide a timer service for physical and virtual context modality."""
    def __init__(self, contextmodalityid, period):
        """Initialize a context modality timer."""
        self.contextmodalityid = contextmodalityid
        self.period = period
        self.timer = None

    def start(self, callback, *args, **kwargs):
        """Start the timer with a given period."""
        if args:
            if kwargs:
                self.timer = TimerService(self.period, callback, args, kwargs)
            else:
                self.timer = TimerService(self.period, callback, args)
        else:
            if kwargs:
                self.timer = TimerService(self.period, callback, kwargs)
            else:
                self.timer = TimerService(self.period, callback)
        self.timer.startService()
        return

    def stop(self):
        """'Stop the timer."""
        return self.timer.stopService()
Example #2
0
 def startService(self):
     """
     Start this service. This will start buckets partitioning
     """
     self.kz_partition = self.kz_client.SetPartitioner(
         self.zk_partition_path, set=set(self.buckets),
         time_boundary=self.time_boundary)
     TimerService.startService(self)
Example #3
0
 def test_pickleTimerServiceNotPickleLoopFinished(self):
     """
     When pickling L{internet.TimerService}, it won't pickle
     L{internet.TimerService._loopFinished}.
     """
     # We need a pickleable callable to test pickling TimerService. So we
     # can't use self.timer
     timer = TimerService(1, fakeTargetFunction)
     timer.startService()
     dumpedTimer = pickle.dumps(timer)
     timer.stopService()
     loadedTimer = pickle.loads(dumpedTimer)
     nothing = object()
     value = getattr(loadedTimer, "_loopFinished", nothing)
     self.assertIdentical(nothing, value)
 def test_pickleTimerServiceNotPickleLoopFinished(self):
     """
     When pickling L{internet.TimerService}, it won't pickle
     L{internet.TimerService._loopFinished}.
     """
     # We need a pickleable callable to test pickling TimerService. So we
     # can't use self.timer
     timer = TimerService(1, fakeTargetFunction)
     timer.startService()
     dumpedTimer = pickle.dumps(timer)
     timer.stopService()
     loadedTimer = pickle.loads(dumpedTimer)
     nothing = object()
     value = getattr(loadedTimer, "_loopFinished", nothing)
     self.assertIdentical(nothing, value)
Example #5
0
class Server(object):
    m_isRunning = False

    m_timer = None

    m_port = 0
    m_connectorServer = None
    m_svrDataList = {}  # [conn] = ""
    m_svrList = {}  # [appid] = conn

    def init(self, conf):
        self.m_port = int(conf.get("serverConfig", "port"))
        logging.info("svrport=%d" % self.m_port)
        self.m_connectorServer = ConnectorServer.ConnectorServer(self)

        dbip = str(conf.get("dbConfig", "ip"))
        dbport = int(conf.get("dbConfig", "port"))
        dbuser = str(conf.get("dbConfig", "user"))
        dbpwd = str(conf.get("dbConfig", "pwd"))
        datatable = str(conf.get("dbConfig", "database"))
        dbcharset = str(conf.get("dbConfig", "charset"))
        gDBManager.init(dbip, dbport, dbuser, dbpwd, datatable, dbcharset)

    def run(self):
        self.m_connectorServer.begin(self.m_port)

        self.m_timer = TimerService(1, self.timer)
        self.m_timer.startService()

        from twisted.internet import reactor
        self.m_isRunning = True
        reactor.run()

    def timer(self):
        gDBManager.onTimer()

    def stop(self):
        self.m_timer.stopService()
        if self.m_isRunning:
            from twisted.internet import reactor
            reactor.stop()

    def newClient(self, conn):
        logging.info("conn ip=%s" % conn.transport.hostname)
        self.m_svrDataList[conn] = ""

    def loseClient(self, conn):
        logging.info("conn ip=%s,appid=%d" %
                     (conn.transport.hostname, conn.m_numid))
        if self.m_svrDataList.has_key(conn):
            del self.m_svrDataList[conn]
        if self.m_svrList.has_key(conn.m_numid):
            del self.m_svrList[conn.m_numid]

    def recvFromClient(self, conn, packlen, appid, numid, xyid, data):
        self.selectProtocol(conn, packlen, appid, numid, xyid, data)

    def selectProtocol(self, conn, packlen, appid, numid, xyid, data):
        logging.debug("packlen=%d,appid=%d,srcappid=%d,numid=%d,xyid=%d" %
                      (packlen, appid, conn.m_numid, numid, xyid))
        if xyid == ProtocolCFG.XYID_CFG_REQ_CONNECT:
            req = ProtocolCFG.ReqConnect()
            ret = req.make(data)
            logging.info("ReqConnect:subtype=%d,svrtype=%d,ip=%s" %
                         (req.subtype, req.svrtype, conn.transport.hostname))

            resp = ProtocolCFG.RespConnect()

            #获取配置
            appid = 0
            port = 0
            config = ""
            sql = "select A.id,A.port,A.config from config_svr A,config_routing_table B where A.subtype=%d and A.svrtype=%d and B.ip='%s' and A.svrid=B.id and A.hide=0" % (
                req.subtype, req.svrtype, conn.transport.hostname)
            ret, row, rslt = gDBManager.select(sql)
            if not ret:
                resp.flag = resp.FLAG.DBERR
                logging.error("select ret err,sql=%s" % sql)
                buf = resp.pack()
                conn.sendData(buf)
                return
            elif row <= 0:
                resp.flag = resp.FLAG.NOCONFIG
                logging.info("select no data,sql=%s" % sql)
                buf = resp.pack()
                conn.sendData(buf)
                return
            else:
                appid = int(rslt[0][0])
                port = int(rslt[0][1])
                config = rslt[0][2]
                logging.debug("appid=%d,port=%d,config=%s" %
                              (appid, port, config))

            #验证appid
            if appid > 65535 or appid <= 0:
                resp.flag = resp.FLAG.ERRAPPID
                logging.error("error appid,appid=%d" % appid)
                buf = resp.pack()
                conn.sendData(buf)
                return

            #检查是否重复连接
            if self.m_svrList.has_key(appid):
                resp.flag = resp.FLAG.CONNECTED
                logging.warning("appid=%d had been connected" % appid)
                buf = resp.pack()
                conn.sendData(buf)

            #这里将appid赋给conn.m_numid
            conn.m_numid = appid
            self.m_svrList[appid] = conn

            logging.info("auth success,flag=%d,appid=%d,port=%d,config=%s" %
                         (resp.flag, resp.appid, resp.port, resp.config))
            resp.flag = resp.FLAG.SUCCESS
            resp.appid = appid
            resp.port = port
            resp.config = config
            buf = resp.pack()
            conn.sendData(buf)
        elif xyid == ProtocolCFG.XYID_CFG_REQ_CONFIG:

            req = ProtocolCFG.ReqConfig()
            ret = req.make(data)
            logging.info("ReqConfig:sql=%s" % req.sqlstr)

            respFinish = ProtocolCFG.RespConfigFinish()
            respFinish.askid = req.askid

            if not self.m_svrList.has_key(conn.m_numid):
                logging.warning("conn is not in svrlist,sql=%s" % req.sqlstr)
                respFinish.flag = respFinish.FLAG.BAN
                buf = respFinish.pack()
                conn.sendData(buf)
                return

            ret, row, rslt = gDBManager.select(req.sqlstr)
            if not ret:
                respFinish.flag = respFinish.FLAG.DBERR
                logging.error("select ret err,sql=%s" % req.sqlstr)
                buf = respFinish.pack()
                conn.sendData(buf)
                return
            elif row <= 0:
                respFinish.flag = respFinish.FLAG.NOCONFIG
                logging.warning("had no config,sql=%s" % req.sqlstr)
                buf = respFinish.pack()
                conn.sendData(buf)
                return
            else:
                resp = ProtocolCFG.RespConfig()
                for i in range(len(rslt)):
                    respFinish.count += 1
                    resp.askid = req.askid
                    logging.debug("type=%s,ret=%s" %
                                  (type(rslt[i][0]), rslt[i][0]))
                    resp.retstr = rslt[i][0]
                    buf = resp.pack()
                    conn.sendData(buf)

            respFinish.flag = respFinish.FLAG.SUCCESS
            buf = respFinish.pack()
            logging.info("appid=%d,flag=%d,count=%d" %
                         (conn.m_numid, respFinish.flag, respFinish.count))
            conn.sendData(buf)

        else:
            logging.warning("error xyid=%d" % xyid)
Example #6
0
class BackupScheduler(object):
    """The container for the backup schedules.

    Internally, it keeps the list of schedules in always-sorted state;
    they are sorted by the expected time of fire (then by UUID;
    but two schedules with the same UUID always match).

    @todo: The scheduler must be dynamically updated:
           1. if some "backup scheduled" settings are changed;
           2. if some "timezone" setting is changed.

    @note: schedule support is disabled, so this class is not used anymore.
    """
    __slots__ = ('server_process', 'lock',
                 '__schedules', '__schedules_by_host_uuid',
                 '__schedule_check_timer', '__last_reread_from_db')


    @exceptions_logged(logger)
    def __init__(self, server_process):
        assert isinstance(server_process, ServerProcess), \
               repr(server_process)

        self.server_process = server_process
        self.lock = RLock()

        self.__schedule_check_timer = \
            TimerService(BACKUP_SCHEDULES_CHECK_PERIOD.total_seconds(),
                         self.__on_schedule_check_timer)

        self.reread_cache()


    def __repr__(self):
        return u'<BackupScheduler: {} schedule(s)>'\
                   .format(len(self.__schedules))


    @property
    def app(self):
        """
        @rtype: NodeApp
        """
        return self.server_process.app


    @exceptions_logged(logger)
    def __reset(self):
        self.__schedules = []
        self.__schedules_by_host_uuid = defaultdict(set)


    @exceptions_logged(logger)
    def start(self):
        self.__schedule_check_timer.startService()


    @exceptions_logged(logger)
    def stop(self):
        """
        @todo: It is nowhere stopped at the moment, should it be?
        """
        self.__schedule_check_timer.stopService()


    @contract_epydoc
    def add(self, schedule):
        """
        @type schedule: BackupSchedule
        """
        with self.lock:
            assert schedule not in self.__schedules, repr(schedule)
            bisect.insort(self.__schedules, schedule)
            self.__schedules_by_host_uuid[schedule.host_uuid].add(schedule)
            assert self.__schedules == sorted(self.__schedules), 'Not sorted!'


    @contract_epydoc
    def remove(self, schedule):
        """
        @type schedule: BackupSchedule
        """
        with self.lock:
            assert schedule in self.__schedules, repr(schedule)
            index = bisect.bisect(self.__schedules, schedule) - 1
            assert schedule == self.__schedules[index], \
                   (index, schedule, self.__schedules)
            del self.__schedules[index]
            self.__schedules_by_host_uuid[schedule.host_uuid].remove(schedule)
            assert self.__schedules == sorted(self.__schedules), 'Not sorted!'


    @contract_epydoc
    def get_schedules_by_host_uuid(self, host_uuid):
        """
        @type host_uuid: UUID
        @rtype: frozenset
        """
        return frozenset(self.__schedules_by_host_uuid[host_uuid])


    @contract_epydoc
    def get_schedules_older_than(self, dt):
        """
        @param dt: The time, the schedules older than, are returned.
        @type dt: datetime

        @precondition: not is_naive_dt(dt) # repr(dt)

        @returns: The schedules which are older than the "dt".
        @rtype: list
        @postcondition: consists_of(result, BackupSchedule)
        """
        with self.lock:
            i = bisect.bisect(self.__schedules,
                              BackupSchedule._dummy_schedule_for_time(dt))
            return self.__schedules[:i]


    @contract_epydoc
    def reread_cache_for_host(self, host_uuid):
        """
        Reread all the schedules for a single host from the DB.
        It is assumed they've just been updated, so they should not be
        sent back.

        @param host_uuid: UUID of the host which schedules need to be updated.
        @type host_uuid: UUID
        """
        logger.debug('Rereading the schedules on %r for host %r',
                     self.server_process.me, host_uuid)

        with self.lock:
            old_schedules = self.get_schedules_by_host_uuid(host_uuid)
            logger.debug('Removing schedules for host %s: %r',
                         host_uuid, old_schedules)
            for schedule in old_schedules:
                self.remove(schedule)

            with db.RDB() as rdbw:
                new_schedules = \
                    Queries.Settings.get_setting(
                        host_uuid=host_uuid,
                        setting_name=Queries.Settings.BACKUP_SCHEDULE,
                        rdbw=rdbw)

                tz_name = Queries.Settings.get_setting(
                              host_uuid=host_uuid,
                              setting_name=Queries.Settings.TIMEZONE,
                              rdbw=rdbw)

            if tz_name:
                try:
                    tz_info = pytz.timezone(tz_name)
                except:
                    logger.error('Cannot parse timezone %r for host %r, '
                                     'fallback to UTC',
                                 tz_name, host_uuid)
                    tz_info = pytz.utc
            else:
                tz_info = pytz.utc

            logger.debug('Adding new schedules for host %s: %r',
                         host_uuid, new_schedules)
            for schedule in new_schedules:
                self.add(BackupSchedule.from_dict(host_uuid,
                                                  tz_info,
                                                  schedule))


    def reread_cache(self):
        """
        Reread the cache/info from the database.
        """
        self.__last_reread_from_db = utcnow()
        logger.debug('Rereading the schedules on %r', self.server_process.me)

        with self.lock:
            self.__reset()
            for host_uuid, (schedules, tz_name) \
                    in TrustedQueries.TrustedSettings.get_all_schedules() \
                                                     .iteritems():
                #
                if tz_name:
                    try:
                        tz_info = pytz.timezone(tz_name)
                    except:
                        logger.error('Cannot parse timezone %r for host %r, '
                                         'fallback to UTC',
                                     tz_name, host_uuid)
                        tz_info = pytz.utc
                else:
                    tz_info = pytz.utc

                for schedule in schedules:
                    self.add(BackupSchedule.from_dict(host_uuid,
                                                      tz_info,
                                                      schedule))

        logger.debug('Reread %i schedules on %r',
                     len(self.__schedules), self.server_process.me)


    @exceptions_logged(logger)
    def __on_schedule_check_timer(self):
        """
        Called whenever the schedule check timer is fired.
        """
        pass


    @exceptions_logged(logger)
    def __check_schedules(self):

        _now = utcnow()

        with self.lock:
            #
            # First, do we need to reread the schedules from the DB.
            #
            assert isinstance(self.__last_reread_from_db, datetime), \
                   repr(self.__last_reread_from_db)
            maxdelta = timedelta(seconds=BACKUP_SCHEDULES_REREAD_PERIOD
                                             .total_seconds())
            if _now - self.__last_reread_from_db > maxdelta:
                self.reread_cache()

            #
            # Now, we can check the schedules.
            #
            if self.__schedules:
                logger.debug('Checking for suspect schedules '
                                 'at %s among %i schedules...',
                             _now, len(self.__schedules))
            suspect_schedules = self.get_schedules_older_than(_now)
            if suspect_schedules:
                logger.debug('On node %r, the following (%i) schedules '
                                 'have passed their time:\n%s',
                             self.server_process.me, len(suspect_schedules),
                             '\n'.join(repr(s)
                                           for s in suspect_schedules))

                # But what hosts are actually alive at the moment?
                alive_host_uuids = \
                    [h.uuid
                         for h in self.app.known_hosts.alive_peers()]
                if alive_host_uuids:
                    logger.debug('Alive hosts at the moment are: %r',
                                 alive_host_uuids)
                # The schedule will fire a backup only if both its time is out,
                # and its host is alive.
                process_schedules = {sch
                                         for sch in suspect_schedules
                                         if sch.host_uuid in alive_host_uuids}

                if process_schedules:
                    processed_host_uuids = set()
                    logger.debug('The following (%i) schedules will fire:\n%s',
                                 len(suspect_schedules),
                                 '\n'.join(repr(sch)
                                               for sch in process_schedules))

                    # Loop over schedules, and run the backup transactions.
                    for schedule in process_schedules:
                        logger.debug('Firing a backup for schedule %r',
                                         schedule)

                        # TODO: Add "if user is suspended" check when
                        # TODO: BackupScheduler will be uncommented.
                        raise NotImplementedError
                        self.start_scheduled_backup(schedule)

                        new_schedule = schedule.copy()
                        new_schedule.advance_by_period()
                        logger.debug('%r advanced to %r',
                                     schedule, new_schedule)

                        # Remove old schedule; add new if needed.
                        self.remove(schedule)
                        if new_schedule.next_backup_datetime is not None:
                            self.add(new_schedule)

                        processed_host_uuids.add(new_schedule.host_uuid)

                    # We've done with the backup transactions.
                    # Would be cool to update the settings.
                    for host_uuid in processed_host_uuids:
                        schedules = self.get_schedules_by_host_uuid(
                                        host_uuid)
                        logger.debug('Updating the schedules '
                                         'for host %s:\n%r',
                                     host_uuid, schedules)
                        with db.RDB() as rdbw:
                            TrustedQueries.TrustedSettings.set_setting(
                                rdbw=rdbw,
                                host_uuid=host_uuid,
                                setting_name=Queries.Settings.BACKUP_SCHEDULE,
                                setting_value=[s.to_dict()
                                                   for s in schedules],
                                setting_time=_now.replace(tzinfo=None))
                        self.send_updated_schedules_to_host(host_uuid)


    def start_scheduled_backup(self, schedule):
        """Given a schedule, start an appropriate backup transaction."""
        assert isinstance(schedule, BackupSchedule), repr(schedule)

        _manager = self.server_process.tr_manager

        b_tr = _manager.create_new_transaction(
                    name='BACKUP',
                    src=self.server_process.me,
                    dst=self.app.known_hosts[schedule.host_uuid],
                    parent=None,
                    # BACKUP-specific
                    schedule_uuid=schedule.uuid,
                    schedule_name=schedule.name,
                    schedule_paths=schedule.paths)


    @contract_epydoc
    def send_updated_schedules_to_host(self, host_uuid):
        """
        Given an UUID of the host,
        start an appropriate "UPDATE_CONFIGURATION" transaction.

        @type host_uuid: UUID
        """
        _setting_name = Queries.Settings.BACKUP_SCHEDULE

        with db.RDB() as rdbw:
            settings = {_setting_name: Queries.Settings.get_setting(
                                           host_uuid=host_uuid,
                                           setting_name=_setting_name,
                                           direct=True,
                                           with_time=True,
                                           rdbw=rdbw)}

        uc_tr = self.server_process.tr_manager.create_new_transaction(
                    name='UPDATE_CONFIGURATION',
                    src=self.server_process.me,
                    dst=self.app.known_hosts[host_uuid],
                    parent=None,
                    # UPDATE_CONFIGURATION-specific
                    settings=settings)
Example #7
0
class AcmeIssuingService(Service):
    """
    A service for keeping certificates up to date by using an ACME server.

    :type cert_store: `~txacme.interfaces.ICertificateStore`
    :param cert_store: The certificate store containing the certificates to
        manage.

    :type client: `txacme.client.Client`
    :param client: A client which is already set to be used for an
        environment.  For example, ``Client.from_url(reactor=reactor,
        url=LETSENCRYPT_STAGING_DIRECTORY, key=acme_key, alg=RS256)``.
        When the service is stopped, it will automatically call the stop
        method on the client.

    :param clock: ``IReactorTime`` provider; usually the reactor, when not
        testing.

    :type responders: List[`~txacme.interfaces.IResponder`]
    :param responders: Challenge responders.  Usually only one responder is
        needed; if more than one responder for the same type is provided, only
        the first will be used.
    :param str email: An (optional) email address to use during registration.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    cert_store = attr.ib()
    _client = attr.ib()
    _clock = attr.ib()
    _responders = attr.ib()
    _email = attr.ib(default=None)
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))

    _waiting = attr.ib(default=attr.Factory(list), init=False)
    _issuing = attr.ib(default=attr.Factory(dict), init=False)
    ready = False
    # Service used to repeatedly call the certificate check and renewal.
    _timer_service = None
    # Deferred of the current certificates check.
    # Added to help the automated testing.
    _ongoing_check = None

    def _now(self):
        """
        Get the current time.
        """
        return clock_now(self._clock)

    def _check_certs(self):
        """
        Check all of the certs in the store, and reissue any that are expired
        or close to expiring.
        """
        log.info('Starting scheduled check for expired certificates.')

        def check(certs):
            panicing = set()
            expiring = set()
            for server_names, objects in certs.items():
                if len(objects) == 0:
                    panicing.add(server_names)
                for o in filter(lambda o: isinstance(o, pem.Certificate),
                                objects):
                    cert = x509.load_pem_x509_certificate(
                        o.as_bytes(), default_backend())
                    until_expiry = cert.not_valid_after - self._now()
                    if until_expiry <= self.panic_interval:
                        panicing.add(server_names)
                    elif until_expiry <= self.reissue_interval:
                        expiring.add(server_names)

            log.info(
                'Found {panicing_count:d} overdue / expired and '
                '{expiring_count:d} expiring certificates.',
                panicing_count=len(panicing),
                expiring_count=len(expiring))

            d1 = (defer.gatherResults(
                [
                    self._issue_cert(server_names).addErrback(
                        self._panic, server_names) for server_names in panicing
                ],
                consumeErrors=True).addCallback(done_panicing))
            d2 = defer.gatherResults([
                self.issue_cert(server_names).addErrback(lambda f: log.failure(
                    u'Error issuing certificate for: {server_names!r}',
                    f,
                    server_names=server_names)) for server_names in expiring
            ],
                                     consumeErrors=True)
            return defer.gatherResults([d1, d2], consumeErrors=True)

        def done_panicing(ignored):
            self.ready = True
            for d in list(self._waiting):
                d.callback(None)
            self._waiting = []

        self._ongoing_check = (self.cert_store.as_dict().addCallback(
            check).addErrback(lambda f: log.failure(
                u'Error in scheduled certificate check.', f)))
        return self._ongoing_check

    def issue_cert(self, server_names):
        """
        Issue a new cert for a particular list of FQDNs.

        If an existing cert exists, it will be replaced with the new cert.  If
        issuing is already in progress for the given name, a second issuing
        process will *not* be started.

        :param str server_names: The comma separated list of names to issue a
            cert for.

        :rtype: ``Deferred``
        :return: A deferred that fires when issuing is complete.
        """
        canonical_names = self._canonicalNames(server_names)

        def finish(result):
            _, waiting = self._issuing.pop(canonical_names)
            for d in waiting:
                d.callback(result)

        # d_issue is assigned below, in the conditional, since we may be
        # creating it or using the existing one.
        d = defer.Deferred(lambda _: d_issue.cancel())
        if canonical_names in self._issuing:
            d_issue, waiting = self._issuing[canonical_names]
            waiting.append(d)
        else:
            d_issue = self._issue_cert(canonical_names)
            waiting = [d]
            self._issuing[canonical_names] = (d_issue, waiting)
            # Add the callback afterwards in case we're using a client
            # implementation that isn't actually async
            d_issue.addBoth(finish)
        return d

    @staticmethod
    def _canonicalNames(server_names):
        """
        Return the canonical representation for `server_names`.
        """
        names = [n.strip() for n in server_names.split(',')]
        return ','.join(names)

    def _issue_cert(self, server_names):
        """
        Issue a new cert for the list of server_names.

        `server_names` is already canonized.
        """
        names = [n.strip() for n in server_names.split(',')]

        log.info('Requesting a certificate for {server_names!r}.',
                 server_names=server_names)
        key = self._generate_key()
        objects = [
            pem.Key(
                key.private_bytes(
                    encoding=serialization.Encoding.PEM,
                    format=serialization.PrivateFormat.TraditionalOpenSSL,
                    encryption_algorithm=serialization.NoEncryption()))
        ]

        @defer.inlineCallbacks
        def answer_to_order(orderr):
            """
            Answer the challenges associated with the order.
            """
            for authorization in orderr.authorizations:
                yield answer_challenge(
                    authorization,
                    self._client,
                    self._responders,
                    clock=self._clock,
                )
            certificate = yield get_certificate(orderr,
                                                self._client,
                                                clock=self._clock)
            defer.returnValue(certificate)

        def got_cert(certr):
            """
            Called when we got a certificate.
            """
            # The certificate is returned as chain.
            objects.extend(pem.parse(certr.body))
            self.cert_store.store(','.join(names), objects)

        return (self._client.submit_order(
            key, names).addCallback(answer_to_order).addCallback(got_cert))

    def when_certs_valid(self):
        """
        Get a notification once the startup check has completed.

        When the service starts, an initial check is made immediately; the
        deferred returned by this function will only fire once reissue has been
        attempted for any certificates within the panic interval.

        ..  note:: The reissue for any of these certificates may not have been
            successful; the panic callback will be invoked for any certificates
            in the panic interval that failed reissue.

        :rtype: ``Deferred``
        :return: A deferred that fires once the initial check has resolved.
        """
        if self.ready:
            return defer.succeed(None)
        d = defer.Deferred()
        self._waiting.append(d)
        return d

    def start(self):
        """
        Like startService, but will return a deferred once the service was
        started and operational.
        """
        Service.startService(self)

        def cb_start(result):
            """
            Called when the client is ready for operation.
            """
            self._timer_service = TimerService(
                self.check_interval.total_seconds(), self._check_certs)
            self._timer_service.clock = self._clock
            self._timer_service.startService()

        return self._client.start(email=self._email).addCallback(cb_start)

    def startService(self):
        """
        Start operating the service.

        See `when_certs_valid` if you want to be notified when all the
        certificate from the storage were validated after startup.
        """
        self.start().addErrback(self._panic, 'FAIL-TO-START')

    def stopService(self):
        Service.stopService(self)
        self.ready = False
        for d in list(self._waiting):
            d.cancel()
        self._waiting = []

        def stop_timer(ignored):
            if not self._timer_service:
                return
            return self._timer_service.stopService()

        def cleanup(ignored):
            self._timer_service = None

        return (self._client.stop().addBoth(tap(stop_timer)).addBoth(
            tap(cleanup)))
Example #8
0
class TimerServiceTests(TestCase):
    """
    Tests for L{twisted.application.internet.TimerService}.

    @type timer: L{TimerService}
    @ivar timer: service to test

    @type clock: L{task.Clock}
    @ivar clock: source of time

    @type deferred: L{Deferred}
    @ivar deferred: deferred returned by L{TimerServiceTests.call}.
    """
    def setUp(self):
        """
        Set up a timer service to test.
        """
        self.timer = TimerService(2, self.call)
        self.clock = self.timer.clock = task.Clock()
        self.deferred = Deferred()

    def call(self):
        """
        Function called by L{TimerService} being tested.

        @returns: C{self.deferred}
        @rtype: L{Deferred}
        """
        return self.deferred

    def test_startService(self):
        """
        When L{TimerService.startService} is called, it marks itself
        as running, creates a L{task.LoopingCall} and starts it.
        """
        self.timer.startService()
        self.assertTrue(self.timer.running, "Service is started")
        self.assertIsInstance(self.timer._loop, task.LoopingCall)
        self.assertIdentical(self.clock, self.timer._loop.clock)
        self.assertTrue(self.timer._loop.running, "LoopingCall is started")

    def test_startServiceRunsCallImmediately(self):
        """
        When L{TimerService.startService} is called, it calls the function
        immediately.
        """
        result = []
        self.timer.call = (result.append, (None, ), {})
        self.timer.startService()
        self.assertEqual([None], result)

    def test_startServiceUsesGlobalReactor(self):
        """
        L{TimerService.startService} uses L{internet._maybeGlobalReactor} to
        choose the reactor to pass to L{task.LoopingCall}
        uses the global reactor.
        """
        otherClock = task.Clock()

        def getOtherClock(maybeReactor):
            return otherClock

        self.patch(internet, "_maybeGlobalReactor", getOtherClock)
        self.timer.startService()
        self.assertIdentical(otherClock, self.timer._loop.clock)

    def test_stopServiceWaits(self):
        """
        When L{TimerService.stopService} is called while a call is in progress.
        the L{Deferred} returned doesn't fire until after the call finishes.
        """
        self.timer.startService()
        d = self.timer.stopService()
        self.assertNoResult(d)
        self.assertEqual(True, self.timer.running)
        self.deferred.callback(object())
        self.assertIdentical(self.successResultOf(d), None)

    def test_stopServiceImmediately(self):
        """
        When L{TimerService.stopService} is called while a call isn't in progress.
        the L{Deferred} returned has already been fired.
        """
        self.timer.startService()
        self.deferred.callback(object())
        d = self.timer.stopService()
        self.assertIdentical(self.successResultOf(d), None)

    def test_failedCallLogsError(self):
        """
        When function passed to L{TimerService} returns a deferred that errbacks,
        the exception is logged, and L{TimerService.stopService} doesn't raise an error.
        """
        self.timer.startService()
        self.deferred.errback(Failure(ZeroDivisionError()))
        errors = self.flushLoggedErrors(ZeroDivisionError)
        self.assertEqual(1, len(errors))
        d = self.timer.stopService()
        self.assertIdentical(self.successResultOf(d), None)

    def test_pickleTimerServiceNotPickleLoop(self):
        """
        When pickling L{internet.TimerService}, it won't pickle
        L{internet.TimerService._loop}.
        """
        # We need a pickleable callable to test pickling TimerService. So we
        # can't use self.timer
        timer = TimerService(1, fakeTargetFunction)
        timer.startService()
        dumpedTimer = pickle.dumps(timer)
        timer.stopService()
        loadedTimer = pickle.loads(dumpedTimer)
        nothing = object()
        value = getattr(loadedTimer, "_loop", nothing)
        self.assertIdentical(nothing, value)

    def test_pickleTimerServiceNotPickleLoopFinished(self):
        """
        When pickling L{internet.TimerService}, it won't pickle
        L{internet.TimerService._loopFinished}.
        """
        # We need a pickleable callable to test pickling TimerService. So we
        # can't use self.timer
        timer = TimerService(1, fakeTargetFunction)
        timer.startService()
        dumpedTimer = pickle.dumps(timer)
        timer.stopService()
        loadedTimer = pickle.loads(dumpedTimer)
        nothing = object()
        value = getattr(loadedTimer, "_loopFinished", nothing)
        self.assertIdentical(nothing, value)
Example #9
0
 def startService(self):
     if self.log_file is not None:
         return
     self.log_file = open(self.log_path, 'r')
     TimerService.startService(self)
Example #10
0
class AcmeIssuingService(Service):
    """
    A service for keeping certificates up to date by using an ACME server.

    :type cert_store: `~txacme.interfaces.ICertificateStore`
    :param cert_store: The certificate store containing the certificates to
        manage.

    :type client_creator: Callable[[], Deferred[`txacme.client.Client`]]
    :param client_creator: A callable called with no arguments for creating the
        ACME client.  For example, ``partial(Client.from_url, reactor=reactor,
        url=LETSENCRYPT_STAGING_DIRECTORY, key=acme_key, alg=RS256)``.
    :param clock: ``IReactorTime`` provider; usually the reactor, when not
        testing.

    :type responders: List[`~txacme.interfaces.IResponder`]
    :param responders: Challenge responders.  Usually only one responder is
        needed; if more than one responder for the same type is provided, only
        the first will be used.
    :param str email: An (optional) email address to use during registration.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    cert_store = attr.ib()
    _client_creator = attr.ib()
    _clock = attr.ib()
    _responders = attr.ib()
    _email = attr.ib(default=None)
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))

    _waiting = attr.ib(default=attr.Factory(list), init=False)
    _issuing = attr.ib(default=attr.Factory(dict), init=False)
    ready = False

    def _now(self):
        """
        Get the current time.
        """
        return clock_now(self._clock)

    def _check_certs(self):
        """
        Check all of the certs in the store, and reissue any that are expired
        or close to expiring.
        """
        log.info('Starting scheduled check for expired certificates.')

        def check(certs):
            panicing = set()
            expiring = set()
            for server_name, objects in certs.items():
                if len(objects) == 0:
                    panicing.add(server_name)
                for o in filter(lambda o: isinstance(o, Certificate), objects):
                    cert = x509.load_pem_x509_certificate(
                        o.as_bytes(), default_backend())
                    until_expiry = cert.not_valid_after - self._now()
                    if until_expiry <= self.panic_interval:
                        panicing.add(server_name)
                    elif until_expiry <= self.reissue_interval:
                        expiring.add(server_name)

            log.info(
                'Found {panicing_count:d} overdue / expired and '
                '{expiring_count:d} expiring certificates.',
                panicing_count=len(panicing),
                expiring_count=len(expiring))

            d1 = (
                gatherResults(
                    [self._with_client(self._issue_cert, server_name)
                     .addErrback(self._panic, server_name)
                     for server_name in panicing],
                    consumeErrors=True)
                .addCallback(done_panicing))
            d2 = gatherResults(
                [self.issue_cert(server_name)
                 .addErrback(
                     lambda f: log.failure(
                         u'Error issuing certificate for: {server_name!r}',
                         f, server_name=server_name))
                 for server_name in expiring],
                consumeErrors=True)
            return gatherResults([d1, d2], consumeErrors=True)

        def done_panicing(ignored):
            self.ready = True
            for d in list(self._waiting):
                d.callback(None)
            self._waiting = []

        return (
            self._ensure_registered()
            .addCallback(lambda _: self.cert_store.as_dict())
            .addCallback(check)
            .addErrback(
                lambda f: log.failure(
                    u'Error in scheduled certificate check.', f)))

    def issue_cert(self, server_name):
        """
        Issue a new cert for a particular name.

        If an existing cert exists, it will be replaced with the new cert.  If
        issuing is already in progress for the given name, a second issuing
        process will *not* be started.

        :param str server_name: The name to issue a cert for.

        :rtype: ``Deferred``
        :return: A deferred that fires when issuing is complete.
        """
        def finish(result):
            _, waiting = self._issuing.pop(server_name)
            for d in waiting:
                d.callback(result)

        # d_issue is assigned below, in the conditional, since we may be
        # creating it or using the existing one.
        d = Deferred(lambda _: d_issue.cancel())
        if server_name in self._issuing:
            d_issue, waiting = self._issuing[server_name]
            waiting.append(d)
        else:
            d_issue = self._with_client(self._issue_cert, server_name)
            waiting = [d]
            self._issuing[server_name] = (d_issue, waiting)
            # Add the callback afterwards in case we're using a client
            # implementation that isn't actually async
            d_issue.addBoth(finish)
        return d

    def _with_client(self, f, *a, **kw):
        """
        Construct a client, and perform an operation with it.
        """
        return self._client_creator().addCallback(f, *a, **kw)

    def _issue_cert(self, client, server_name):
        """
        Issue a new cert for a particular name.
        """
        log.info(
            'Requesting a certificate for {server_name!r}.',
            server_name=server_name)
        key = self._generate_key()
        objects = [
            Key(key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption()))]

        def answer_and_poll(authzr):
            def got_challenge(stop_responding):
                return (
                    poll_until_valid(authzr, self._clock, client)
                    .addBoth(tap(lambda _: stop_responding())))
            return (
                answer_challenge(authzr, client, self._responders)
                .addCallback(got_challenge))

        def got_cert(certr):
            objects.append(
                Certificate(
                    x509.load_der_x509_certificate(
                        certr.body, default_backend())
                    .public_bytes(serialization.Encoding.PEM)))
            return certr

        def got_chain(chain):
            for certr in chain:
                got_cert(certr)
            log.info(
                'Received certificate for {server_name!r}.',
                server_name=server_name)
            return objects

        return (
            client.request_challenges(fqdn_identifier(server_name))
            .addCallback(answer_and_poll)
            .addCallback(lambda ign: client.request_issuance(
                CertificateRequest(
                    csr=csr_for_names([server_name], key))))
            .addCallback(got_cert)
            .addCallback(client.fetch_chain)
            .addCallback(got_chain)
            .addCallback(partial(self.cert_store.store, server_name)))

    def _ensure_registered(self):
        """
        Register if needed.
        """
        if self._registered:
            return succeed(None)
        else:
            return self._with_client(self._register)

    def _register(self, client):
        """
        Register and agree to the TOS.
        """
        def _registered(regr):
            self._regr = regr
            self._registered = True
        regr = messages.NewRegistration.from_data(email=self._email)
        return (
            client.register(regr)
            .addCallback(client.agree_to_tos)
            .addCallback(_registered))

    def when_certs_valid(self):
        """
        Get a notification once the startup check has completed.

        When the service starts, an initial check is made immediately; the
        deferred returned by this function will only fire once reissue has been
        attempted for any certificates within the panic interval.

        ..  note:: The reissue for any of these certificates may not have been
            successful; the panic callback will be invoked for any certificates
            in the panic interval that failed reissue.

        :rtype: ``Deferred``
        :return: A deferred that fires once the initial check has resolved.
        """
        if self.ready:
            return succeed(None)
        d = Deferred()
        self._waiting.append(d)
        return d

    def startService(self):
        Service.startService(self)
        self._registered = False
        self._timer_service = TimerService(
            self.check_interval.total_seconds(), self._check_certs)
        self._timer_service.clock = self._clock
        self._timer_service.startService()

    def stopService(self):
        Service.stopService(self)
        self.ready = False
        self._registered = False
        for d in list(self._waiting):
            d.cancel()
        self._waiting = []
        return self._timer_service.stopService()
Example #11
0
class FeedPollerService(Service):
    """
    Polls AtomHopper feeds
    """
    def __init__(self, agent, url, event_listeners, interval=DEFAULT_INTERVAL,
                 state_store=None,
                 TimerService=TimerService, coiterate=coiterate):
        """
        :param agent: a :class:`twisted.web.client.Agent` to use to poll

        :param url: the url to poll

        :param event_listeners: listeners that handle a particular event
        :type event_listeners: `iterable` of `callables` that take an event
            as an argument

        :param interval: how often to poll, given in seconds - defaults to 10
        :type interval: ``int`` or ``float``

        :param state_store: where to store the current polling state
        :type state_store: :class:`otter.indexer.state.IStateStore` provider

        :param TimerService: factory (not instance) that produces something
            like a :class:`twisted.application.internet.TimerService` -
            defaults to :class:`twisted.application.internet.TimerService`
            (this parameter is mainly used for dependency injection for
            testing)
        :type TimerService: ``callable``

        :param coiterate: function that is used to coiterate tasks - defaults
            to :func:`twisted.internet.task.coiterate` - (this parameter is
            mainly used for dependency injection for testing)
        :type coiterate: ``callable``
        """
        self._url = url
        self._interval = interval

        self._timer_service = TimerService(interval, self._do_poll)

        self._next_url = None

        self._agent = agent
        self._state_store = state_store or DummyStateStore()

        self._event_listeners = event_listeners
        self._poll_timer = timer('FeedPollerService.poll.{0}'.format(url))
        self._fetch_timer = timer('FeedPollerService.fetch.{0}'.format(url))

        self._coiterate = coiterate

    def startService(self):
        """
        Start the feed polling service - called by the twisted
        application when starting up
        """
        self._timer_service.startService()

    def stopService(self):
        """
        Stop the feed polling service - called by the twisted
        application when shutting down

        :return: ``Deferred``
        """
        return self._timer_service.stopService()

    def _fetch(self, url):
        """
        Get atom feed from AtomHopper url
        """
        def _parse(data):
            e = parse(data)
            return e

        def _gotResponse(resp):
            br = _BodyReceiver()

            resp.deliverBody(br)

            return br.finish

        log.msg(format="Fetching url: %(url)r", url=url)
        d = self._agent.request('GET', url, Headers({}), None)
        d.addCallback(_gotResponse)
        d.addCallback(_parse)

        return d

    def _do_poll(self):
        """
        Do one interation of polling AtomHopper.
        """
        start = time.time()

        def _get_next_url(feed):
            self._fetch_timer.update(time.time() - start)
            # next is previous, because AtomHopper is backwards in time
            next_url = previous_link(feed)

            if next_url is not None:
                self._next_url = next_url

            log.msg(format="URLS: %(url)r\n\t->%(next_url)s",
                    url=self._url, next_url=self._next_url)

            sd = self._state_store.save_state(self._next_url)
            sd.addCallback(lambda _: feed)
            return sd

        def _dispatch_entries(feed):
            # Actually sort by updated date.
            sorted_entries = sorted(entries(feed),
                                    key=lambda x: parse_date(updated(x)))
            return self._coiterate(chain.from_iterable(
                                   ((el(entry) for el in self._event_listeners)
                                    for entry in sorted_entries)))

        def _finish_iteration(ignore):
            self._poll_timer.update(time.time() - start)

        d = self._state_store.get_state()
        d.addCallback(
            lambda saved_url: self._next_url or saved_url or self._url)
        d.addCallback(self._fetch)
        d.addCallback(_get_next_url)
        d.addCallback(_dispatch_entries)
        d.addErrback(log.err)
        d.addBoth(_finish_iteration)
        return d
Example #12
0
class TehBot(irc.IRCClient):
    """A IRC bot."""

    def connectionMade(self):
        self.logger = MessageLogger(open(self.factory.filename, "a"))
        self.logger.log("[connected at %s]" % asctime(localtime(time())))
        self.options = self.factory.options
        self.svn = self.factory.svn
        self.nickname = self.options.nick
        self.realname = self.options.name
        self.authQ = []
        self.loggedIn = []
        self.topics = {}
        self.redos = {}
        self.undos = {}
        self.userWatch = {}
        self.undo = False
        self.lastSearchStart = -1
        self.lastSearchQuery = None
        self.init()
        irc.IRCClient.connectionMade(self)

    def connectionLost(self, reason):
        irc.IRCClient.connectionLost(self, reason)
        self.logger.log("[disconnected at %s]" % asctime(localtime(time())))
        self.logger.close()

    def init(self):
        # set vars that may change during execution
        self.versionName = self.options.options['CLIENTNAME']
        self.versionNum = self.options.options['VERSION']
        self.versionEnv = sys.platform
        self.options.options['REVISION'] = self.svn.lastRev()
        self.readWatchDataFromFile()
        
        # SVN announce code
        if self.options.announce:    # if svn announce commits mode
            # create/start timer
            self.timer = TimerService(self.options.frequency, self.svnAnnounce)
            self.timer.startService()
        else:
            if hasattr(self, 'timer') and self.timer.running:  # if running
                self.timer.stopService() # stop it
    
    def svnAnnounce(self):
        rev = self.svn.lastRev()

        if rev == -1:
            print 'ERROR: Error connecting to SVN repo'
            return
        
        if rev != self.options.options['REVISION']: # check against stored revision
            # new commit, yay :)
            temp = int(self.options.options['REVISION'])
            temp += 1
            self.options.options['REVISION'] = str(temp)
            for target in self.options.announce_targets:    # tell everybody about it
                self.cmd_lastlog(target, target, [])

    def writeWatchDataToFile(self):
        """Outputs watch data to permanent storage (disk)"""
        if not checkDir('watchdata'):
            mkdir('watchdata')

        current = getcwd()
        chdir('watchdata')

        for user in self.userWatch:
            f = open(user + '.watch', 'w')
            for message in self.userWatch[user]:
                f.write('%s<*!*>%s' % (message, self.userWatch[user][message]))
            f.close()   

        chdir(current)

    def readWatchDataFromFile(self):
        """Outputs watch data to permanent storage (disk)"""
        if not checkDir('watchdata'):
            mkdir('watchdata')

        current = getcwd()
        chdir('watchdata')

        for user in self.options.watchUsers:
            if not self.userWatch.has_key(user):
                self.userWatch[user] = {}
            try:
                f = open(user + '.watch', 'r')
                for line in f:
                    message, count = line.split('<*!*>')
                    self.userWatch[user][message.strip()] = int(count)
                f.close()
            except IOError:
                continue
            
        chdir(current)

    # callbacks for events

    def signedOn(self):
        """Called when bot has succesfully signed on to server."""
        if self.options.registeredNick:
            self.msg('nickserv','identify ' + self.options.nickPassword)
        for chan in self.options.chanstojoin:
            self.join(chan)

    def joined(self, channel):
        """This will get called when the bot joins the channel."""
        self.options.channels.append(channel.lower())
        for user in self.options.authUsers:
            if user.lower() not in self.loggedIn:
                print ' *** Attempting login for %s on %s' % (user, channel)
                self.cmd_login(user, channel, [])

    def userJoined(self, user, channel):
        """Called when a user joins a channel Im on"""
        user = user.split('!', 1)[0]
        if user != self.nickname:
            print 'JOIN: %s on %s' % (user, channel)
            self.logger.log('JOIN: %s on %s' % (user, channel))
            
            if self.options.welcome:
                if user.lower() in self.options.authUsers:
                    self.msg(channel, 'Welcome to %s, the all powerful %s, thank you for blessing us with your presence' % (channel, user))
                else:
                    self.msg(channel, 'Welcome to %s, %s' % (channel, user))

            if user in self.options.authUsers:
                print ' *** Attempting login for %s on %s' % (user, channel)
                self.cmd_login(user, channel, [])

    def kickedFrom(self, channel, kicker, message):
        """Called when Im kicked from a channel"""
        if self.options.rejoin:
            self.join(channel)
            self.msg(channel, '%s: thanks for that (%s)' % (kicker, message))

    def action(self, user, channel, data):
        """Called when another user performs an action"""
        user = user.split('!', 1)[0]
        msg = data.strip()
        self.logger.log('(%s): *%s %s ' % (channel, user, msg))
   
    def cmd_login(self, user, channel, params):
        """Gains usage access to bot. Usage: LOGIN"""
        #XXX:   this is non-RFC standard message (307), so may not 
        #       work on other servers, besides Shadowfire
        if user.lower() in self.options.authUsers:
            self.authQ.append(user)
            self.sendLine("WHOIS %s" % user)
        else:
            self.msg(user, 'ERROR: You Are Not Authorised!')
    
    def cmd_logout(self, user, channel, params):
        """Removes usage access to bot. Usage: LOGOUT"""
        if user.lower() in self.loggedIn:
            self.loggedIn.remove(user.lower())
        else:
            self.msg(user, 'ERROR: Not Logged In')
    
    def irc_307(self, prefix, params):
        """Reply from WHOIS message, indicates a registered nick"""
        if len(params) == 3:
            user = params[1].lower()
            msg = params[2]

            if user in self.authQ:
                self.authQ.remove(user)
                if user not in self.loggedIn:
                    if msg == 'is a registered nick':
                        self.loggedIn.append(user)
                
                        if self.options.announceLogins:
                            self.msg(user, 'You are now Logged In!')
   
    def privmsg(self, user, channel, msg):
        """This will get called when the bot receives a message."""
        user = user.split('!', 1)[0]
        message = msg.strip()
        checkUser = user.lower()

        self.logger.log('%s (%s): %s' % (user, channel, msg))

        if channel in self.options.channels:
        # if from channel,then only process if proceeded by nick: or !nick:
        # if nick, then respond to user from which msg originated
        # if !nick, then respond to channel
            if message.startswith(self.nickname + ':'):
                message = message[len(self.nickname)+1:].strip()
            elif message.startswith('!' + self.nickname + ':'):
                message = message[len(self.nickname)+2:].strip()
                user = channel
            elif message.startswith(self.options.channelChar):
                message = message[len(self.options.channelChar):].strip()
                user = channel
            else:
                return
        elif self.nickname != channel:
            return

        params = message.split()
        command = params.pop(0)

        # empty command like "::", just ignore
        if not command:
            return
        
        # is the message from an authorised user? or open command? 
        if checkUser in self.loggedIn or command.lower() in self.options.openCommands:
            print 'DEBUG: Remote Command %s from %s with parameters: %s' % (command, user, params)
            self.logger.log('Remote Command: %s from %s with parameters: %s' % (command, user, params))

            # special cases
            if command.lower() == 'login':
                self.cmd_login(checkUser, channel, params)
            elif command.lower() == 'logout':
                self.cmd_logout(checkUser, channel, params)
            elif command.lower() in _admin_commands and checkUser not in self.options.authors:
                self.msg(user, 'ERROR: Administrator only command: %s' % command)
            elif command.lower() in self.options.disabledCommands and checkUser not in self.options.authors:
                self.msg(user, 'ERROR: Disabled command: %s' % command)
            else:
                # despatch command
                try:
                    handler = getattr(self, 'cmd_' + command.lower())
                except AttributeError:
                    self.msg(user, 'Unrecognised command: %s' % command)
                else:
                    # call the handler here in case it throws an AttributeError
                    handler(user, channel, params)

    # User commands

    def cmd_quit(self, user, channel, params):
        """Quits IRC. Usage: QUIT <quit message>"""
        self.factory.quit = True
        self.quit(' '.join(params))

    def cmd_op(self, user, channel, params):
        """Gives Channel operator status to a user. Usage: OP [channel] <user | me>"""
        if len(params) > 1:
            # they've specified a channel
            channel = params.pop(0)

        target = params[0]
        if (target.lower() == 'me'):
            target = user

        self.mode(channel, 1, 'o', user=target)

    def cmd_deop(self, user, channel, params):
        """Removes Channel operator status from a user. Usage: DEOP [channel] <user | me>"""
        if len(params) > 1:
            # they've specified a channel
            channel = params.pop(0)

        target = params[0]
        if (target.lower() == 'me'):
            target = user

        self.mode(channel, 0, 'o', user=target)

    def cmd_topic(self, user, channel, params):
        """ Updates the current channel's topic. Usage: TOPIC [channel] [command] <topic>

             Commands:

               add  - add to current topic. Usage: TOPIC [channel] add <text>
               del  - remove from topic, based on position (starting at 0). Usage: TOPIC [channel] del <index>
               edit - replaces topic in specified position with new text. Usage: TOPIC [channel] edit <index> <text>
               set  - sets the topic. Usage: TOPIC [channel] set <text>
               get  - gets the current topic. Usage: TOPIC [channel] get
               undo - undo the last topic change. Usage: TOPIC [channel] undo
               redo - redo the last topic undo. Usage: TOPIC [channel] redo
               
               replace - replaces one word (using regexp) with a phrase throughout the whole topic. Usage: TOPIC [channel] replace <to replace - regexp> <phrase>
        """

        if len(params) > 1:
            if params[0] in self.options.channels:
                channel = params.pop(0)
            
            command = params.pop(0).lower()
    
            if not self.topics.has_key(channel): 
                self.topics[channel] = []
                
            current = self.topics[channel]

            if command == 'add':
                temp = current + [' '.join(params)]
#               current.append(' '.join(params))
                topic = ' | '.join(temp)
            elif command == 'del':
                index = int(params.pop(0))
                topic = current[:index]
                index += 1

                if index > 0:
                    topic.extend(current[index:])

                topic = ' | '.join(topic)
            elif command == 'edit':
                index = int(params.pop(0))
                current[index] = ' '.join(params)
                topic = ' | '.join(current)
            elif command == 'replace':
                #what = params.pop(0)
                what = re.compile(params.pop(0))
                with = ' '.join(params)
                topic = ' | '.join(current) 
                #topic = topic.replace(what, with)
                topic = what.sub(with, topic)
            elif command == 'get':
                self.msg(user, 'topic for %s is: %s' % (channel, ' | '.join(current)))
                return
Example #13
0
class SyncDirFSNotifyProxy(AbstractFSNotifyManager):
    """
    This proxy tracks the top-level directory, the symlinks in it (note:
    in top-level directory only!), and remaps events occuring in the symlinked
    directory to the final path, and vice versa.
    It uses a different, OS-specific implementation of the FS Notify Manager,
    to track

    @type __inner_fsnotify_manager: AbstractFSNotifyManager

    @ivar __watched_dir_to_cb: mapping from basestring to Callbable
    @ivar __implicitly_watched_dirs: set of basestring

    # @ivar __watched_dir_to_cb: mapping from basestring to WatchInfo
    # @ivar __implicitly_watched_dirs: mapping from basestring to WatchInfo

    @ivar __links_map_pair: a pair of:
        1. C{col.Mapping} - the mapping from the symlink source (inside some
           of the watched dirs) to the information about the symlink
           (C{SymlinkInfo}).
        2. C{col.Mapping} -  the mapping from the symlink destination
           to the set of symlink sources.
    @type __links_map_pair: LinksMapPair
    """

    __slots__ = ('__inner_fsnotify_manager', '__watched_dir_to_cb',
                 '__implicitly_watched_dirs', '__poll_sync_dir_timer',
                 '__links_map_pair')

    @contract_epydoc
    def __init__(self, fsnotify_manager):
        """Constructor.

        @type fsnotify_manager: AbstractFSNotifyManager
        """
        self.__inner_fsnotify_manager = fsnotify_manager
        self.__watched_dir_to_cb = {}  # mapping from path to callback
        self.__implicitly_watched_dirs = set()
        self.__poll_sync_dir_timer = \
            TimerService(POLL_SYNC_DIR_FOR_SYMLINKS_PERIOD.total_seconds(),
                         lambda: callInThread(
                                     self.__on_poll_sync_dir_timer))
        self.__links_map_pair = LinksMapPair(links={}, reverse_links={})

    def watch(self, path, cb):
        """Start watching for FS changes in C{path} explicitly."""
        self.__watch(path, cb, do_start_timer=True)

    def __watch(self, path, cb, do_start_timer=False):
        """All internal operations to start watching a directory for changes.
        @note: if C{do_start_timer=True}, it automatically starts the
            sync dir poll timer on the very first attempt to watch a dir.
        @type do_start_timer: bool
        """
        cls = self.__class__

        if path in self.__watched_dir_to_cb:
            logger.warning(u'Path already watched: %r', path)
            return
        # Start ("explicitly") watch for the path itself.
        logger.debug(u'Starting to (expl.) watch for %r with %r', path, cb)
        self.__watched_dir_to_cb[path] = cb
        # remapped_cb = self._remapping_path_wrapper(path, cb)

        # Explicitly watched paths have their cb's passed without alteration.
        self.__inner_fsnotify_manager.watch(path, cb)

        # Maps full path of symlink to SymlinkInfo,..
        links_in_this_path_pair = self.__rescan_watched_dir_for_map(path)
        assert isinstance(links_in_this_path_pair, LinksMapPair), \
               repr(links_in_this_path_pair)

        cls._inmerge_link_map_pair(into=self.__links_map_pair,
                                   merge=links_in_this_path_pair)

        # ... and start ("implicitly") watch for every symlink
        # (its target, actually)
        for link, linkinfo in links_in_this_path_pair.links.iteritems():
            assert path == linkinfo.base_dir, \
                   (path, linkinfo.base_dir)
            logger.debug(u'Starting to (impl.) watch %r in %r with %r',
                         linkinfo.linked_path, path, cb)
            self.__implicitly_watched_dirs.add(linkinfo.linked_path)
            # For every watched symlink, we call our own cb.
            self.__inner_fsnotify_manager.watch(
                linkinfo.linked_path,
                partial(self.__cb_with_symlink_reversing,
                        original_cb=cb,
                        linked_base_dir=linkinfo.linked_path))

        # The service starts the calls when it starts.
        # self.__links_map_pair should already be initialized before starting
        # this service!
        if not self.__poll_sync_dir_timer.running and do_start_timer:
            logger.debug(u'Started to poll watched directories (%r)',
                         self.__watched_dir_to_cb.keys())
            self.__poll_sync_dir_timer.startService()

    @contract_epydoc
    def __cb_with_symlink_reversing(self, event, original_cb, linked_base_dir):
        r"""
        >>> from uhost.fs_notify.events import \
        ...     CreateEvent, DeleteEvent, MoveEvent, Event
        >>> src_path = '/mnt/net/homes/john/hello'
        >>> dst_path = '/mnt/net/homes/john/goodby'
        >>> create_event = CreateEvent(filepath.FilePath(src_path))
        >>> move_event = MoveEvent(filepath.FilePath(src_path),
        ...                        filepath.FilePath(dst_path))
        >>> delete_event = DeleteEvent(filepath.FilePath(dst_path))

        >>> def printer(event):
        ...     print event

        >>> class DummySyncDirFSNotifyProxy(SyncDirFSNotifyProxy):
        ...    def __init__(self):
        ...        self._SyncDirFSNotifyProxy__links_map_pair =\
        ...            LinksMapPair(links={
        ...                             '/home/john':
        ...                                 SymlinkInfo(
        ...                                     base_dir='/home',
        ...                                     linked_path=
        ...                                         '/mnt/net/homes/john')},
        ...                         reverse_links={
        ...                             '/mnt/net/homes/john': {'/home/john'}}
        ...                         )
        ...    def cb_with_symlink_reversing(self, event, original_cb,
        ...                                  linked_base_dir):
        ...        self._SyncDirFSNotifyProxy__cb_with_symlink_reversing(
        ...            event, original_cb, linked_base_dir)

        >>> d = DummySyncDirFSNotifyProxy()

        >>> d.cb_with_symlink_reversing(create_event, printer,
        ...     '/mnt/net/homes/john')  # doctest: +ELLIPSIS
        CreateEvent(path=FilePath('/home/john/hello'), ts=...)

        >>> d.cb_with_symlink_reversing(
        ... move_event, printer, '/mnt/net/homes/john'
        ... ) # doctest:+NORMALIZE_WHITESPACE +ELLIPSIS
        MoveEvent(from_path=FilePath('/home/john/hello'),
                  to_path=FilePath('/home/john/goodby'),
                  ts=...)

        >>> d.cb_with_symlink_reversing(
        ... delete_event, printer, '/mnt/net/homes/john'
        ... )  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
        DeleteEvent(path=FilePath('/home/john/goodby'),
                    ts=...)

        @type original_cb: col.Callable
        @type linked_base_dir: basestring
        @type event: Event
        """
        try:
            link_sources = self.__links_map_pair.reverse_links[linked_base_dir]
        except:
            logger.exception(u"Seems like we don't watch for %r already",
                             linked_base_dir)
        else:
            for link_source in link_sources:
                substituted_path = reprefix(event.path.path,
                                            old_prefix=linked_base_dir,
                                            new_prefix=link_source)

                if isinstance(event, MoveEvent):
                    substituted_to_path = reprefix(event.to_path.path,
                                                   old_prefix=linked_base_dir,
                                                   new_prefix=link_source)
                new_event = event.copy()
                new_event.path = filepath.FilePath(substituted_path)
                if isinstance(event, MoveEvent):
                    new_event.to_path = filepath.FilePath(substituted_to_path)

                logger.debug(u'Calling cb while substituting %r to %r: %r',
                             linked_base_dir, link_source, event)

                original_cb(new_event)

    @staticmethod
    def _inmerge_link_map_pair(into, merge):
        r"""
        Merge to C{merge} pair into C{into} pair (attention,
        it is modified inline!)

        >>> a1 = LinksMapPair(links={
        ...                       '/etc/networking':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/etc/networking-copy':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/home/john':
        ...                           SymlinkInfo(
        ...                               base_dir='/home',
        ...                               linked_path='/mnt/net/homes/john'),
        ...                   },
        ...                   reverse_links={
        ...                       '/mnt/net/etc/net': {'/etc/networking',
        ...                                            '/etc/networking-copy'},
        ...                       '/mnt/net/homes/john': {'/home/john'},
        ...                   })
        >>> a2 = LinksMapPair(links={
        ...                       '/etc/samba':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/smb'),
        ...                       '/etc/networking-copy2':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/home/alex':
        ...                           SymlinkInfo(
        ...                               base_dir='/home',
        ...                               linked_path='/mnt/net/homes/alex'),
        ...                   },
        ...                   reverse_links={
        ...                       '/mnt/net/etc/smb':
        ...                           {'/etc/samba'},
        ...                       '/mnt/net/etc/net':
        ...                           {'/etc/networking-copy2'},
        ...                       '/mnt/net/homes/alex':
        ...                           {'/home/alex'},
        ...                   })
        >>> SyncDirFSNotifyProxy._inmerge_link_map_pair(into=a2, merge=a1)
        >>> a1  # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
        LinksMapPair(links={'/home/john':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/john'),
                            '/etc/networking-copy':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/etc/networking':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net')},
            reverse_links={'/mnt/net/etc/net':
                              set(['/etc/networking-copy', '/etc/networking']),
                           '/mnt/net/homes/john':
                               set(['/home/john'])})
        >>> a2  # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
        LinksMapPair(links={'/home/john':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/john'),
                            '/etc/networking':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/etc/samba':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/smb'),
                            '/etc/networking-copy':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/home/alex':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/alex'),
                            '/etc/networking-copy2':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net')},
            reverse_links={'/mnt/net/etc/net':
                               set(['/etc/networking-copy',
                                    '/etc/networking',
                                    '/etc/networking-copy2']),
                           '/mnt/net/homes/john':
                               set(['/home/john']),
                           '/mnt/net/etc/smb':
                               set(['/etc/samba']),
                           '/mnt/net/homes/alex':
                               set(['/home/alex'])})

        @param into: target pair to merge the data (C{merge} arg) into.
        @type into: LinksMapPair

        @param merge: the pair being merged into C{into} pair.
        @type merge: LinksMapPair
        """
        into.links.update(merge.links)
        # Merging new_rev_links_map is a bit more complex.
        for k, v in merge.reverse_links.iteritems():
            into.reverse_links.setdefault(k, set()).update(v)

    @exceptions_logged(logger)
    def __on_poll_sync_dir_timer(self):
        cls = self.__class__
        assert not in_main_thread()

        logger.debug(u'Polling watched directories (%r)',
                     self.__watched_dir_to_cb.keys())

        # Loop over the watched directories, recreate the symlink map
        new_links_map_pair = LinksMapPair({}, {})
        for watched_dir in self.__watched_dir_to_cb.iterkeys():
            links_map_pair_for_watched_dir = \
                self.__rescan_watched_dir_for_map(watched_dir)

            cls._inmerge_link_map_pair(into=new_links_map_pair,
                                       merge=links_map_pair_for_watched_dir)

        # Update the map with the total result
        logger.debug(u'The total symlink map is %r', new_links_map_pair)
        links_set_old = set(self.__links_map_pair.links)
        links_set_new = set(new_links_map_pair.links)
        removed_links = links_set_old.difference(links_set_new)
        created_links = links_set_new.difference(links_set_old)
        self.__links_map_pair = new_links_map_pair

        if created_links:
            logger.debug(u'New links: %r', created_links)

        for link in created_links:
            linkinfo = new_links_map_pair.links[link]
            cb = self.__watched_dir_to_cb[linkinfo.base_dir]
            self.__implicitly_watched_dirs.add(linkinfo.linked_path)
            dirs, files = _get_dir_content_recursively(link)
            for path in chain(dirs, files, [linkinfo.linked_path]):
                cb(CreateEvent(filepath.FilePath(path)))
            self.__inner_fsnotify_manager.watch(
                linkinfo.linked_path,
                partial(self.__cb_with_symlink_reversing,
                        original_cb=cb,
                        linked_base_dir=linkinfo.linked_path))

        if removed_links:
            logger.warning(
                u'These links were removed,'
                u' but original path maybe still watched %r,'
                u' unwatching is not implemented.', removed_links)

        #@todo: TODO: handle removed_links

    def __rescan_watched_dir_for_map(self, watched_dir):
        """
        Rescan anew the given directory and get the bidirectional mapping
        of all the found symlinks.

        @rtype: LinksMapPair
        """
        pair = LinksMapPair({}, {})

        logger.debug(u'Rescanning %r for symlinks', watched_dir)
        try:
            # Make sure the result is in Unicode
            dir_contents = [
                os.path.join(watched_dir, rel)
                for rel in os.listdir(unicode(watched_dir))
            ]
        except:
            logger.exception(u'Cannot list %r', watched_dir)
        else:
            # For every item inside the directory, check whether it is
            # a symlink/junction point
            for item in dir_contents:
                try:
                    if host_system.is_link_to_dir(item):
                        dest = os.path.abspath(
                            host_system.read_link_to_dir(item))

                        logger.debug(u'Found %r -> %r', item, dest)

                        SyncDirFSNotifyProxy._inmerge_link_map_pair(
                            into=pair,
                            merge=LinksMapPair(links={
                                item:
                                SymlinkInfo(base_dir=watched_dir,
                                            linked_path=dest)
                            },
                                               reverse_links={dest: {item}}))

                except:
                    logger.exception(u'Cannot test %r in %r', item,
                                     watched_dir)

        return pair
Example #14
0
class SyncDirFSNotifyProxy(AbstractFSNotifyManager):
    """
    This proxy tracks the top-level directory, the symlinks in it (note:
    in top-level directory only!), and remaps events occuring in the symlinked
    directory to the final path, and vice versa.
    It uses a different, OS-specific implementation of the FS Notify Manager,
    to track

    @type __inner_fsnotify_manager: AbstractFSNotifyManager

    @ivar __watched_dir_to_cb: mapping from basestring to Callbable
    @ivar __implicitly_watched_dirs: set of basestring

    # @ivar __watched_dir_to_cb: mapping from basestring to WatchInfo
    # @ivar __implicitly_watched_dirs: mapping from basestring to WatchInfo

    @ivar __links_map_pair: a pair of:
        1. C{col.Mapping} - the mapping from the symlink source (inside some
           of the watched dirs) to the information about the symlink
           (C{SymlinkInfo}).
        2. C{col.Mapping} -  the mapping from the symlink destination
           to the set of symlink sources.
    @type __links_map_pair: LinksMapPair
    """

    __slots__ = ('__inner_fsnotify_manager',
                 '__watched_dir_to_cb', '__implicitly_watched_dirs',
                 '__poll_sync_dir_timer',
                 '__links_map_pair')


    @contract_epydoc
    def __init__(self, fsnotify_manager):
        """Constructor.

        @type fsnotify_manager: AbstractFSNotifyManager
        """
        self.__inner_fsnotify_manager = fsnotify_manager
        self.__watched_dir_to_cb = {}  # mapping from path to callback
        self.__implicitly_watched_dirs = set()
        self.__poll_sync_dir_timer = \
            TimerService(POLL_SYNC_DIR_FOR_SYMLINKS_PERIOD.total_seconds(),
                         lambda: callInThread(
                                     self.__on_poll_sync_dir_timer))
        self.__links_map_pair = LinksMapPair(links={}, reverse_links={})


    def watch(self, path, cb):
        """Start watching for FS changes in C{path} explicitly."""
        self.__watch(path, cb, do_start_timer=True)


    def __watch(self, path, cb, do_start_timer=False):
        """All internal operations to start watching a directory for changes.
        @note: if C{do_start_timer=True}, it automatically starts the
            sync dir poll timer on the very first attempt to watch a dir.
        @type do_start_timer: bool
        """
        cls = self.__class__

        if path in self.__watched_dir_to_cb:
            logger.warning(u'Path already watched: %r', path)
            return
        # Start ("explicitly") watch for the path itself.
        logger.debug(u'Starting to (expl.) watch for %r with %r', path, cb)
        self.__watched_dir_to_cb[path] = cb
        # remapped_cb = self._remapping_path_wrapper(path, cb)

        # Explicitly watched paths have their cb's passed without alteration.
        self.__inner_fsnotify_manager.watch(path, cb)

        # Maps full path of symlink to SymlinkInfo,..
        links_in_this_path_pair = self.__rescan_watched_dir_for_map(path)
        assert isinstance(links_in_this_path_pair, LinksMapPair), \
               repr(links_in_this_path_pair)

        cls._inmerge_link_map_pair(into=self.__links_map_pair,
                                   merge=links_in_this_path_pair)

        # ... and start ("implicitly") watch for every symlink
        # (its target, actually)
        for link, linkinfo in links_in_this_path_pair.links.iteritems():
            assert path == linkinfo.base_dir, \
                   (path, linkinfo.base_dir)
            logger.debug(u'Starting to (impl.) watch %r in %r with %r',
                         linkinfo.linked_path, path, cb)
            self.__implicitly_watched_dirs.add(linkinfo.linked_path)
            # For every watched symlink, we call our own cb.
            self.__inner_fsnotify_manager.watch(
                linkinfo.linked_path,
                partial(self.__cb_with_symlink_reversing,
                        original_cb=cb,
                        linked_base_dir=linkinfo.linked_path))

        # The service starts the calls when it starts.
        # self.__links_map_pair should already be initialized before starting
        # this service!
        if not self.__poll_sync_dir_timer.running and do_start_timer:
            logger.debug(u'Started to poll watched directories (%r)',
                         self.__watched_dir_to_cb.keys())
            self.__poll_sync_dir_timer.startService()


    @contract_epydoc
    def __cb_with_symlink_reversing(self, event, original_cb, linked_base_dir):
        r"""
        >>> from uhost.fs_notify.events import \
        ...     CreateEvent, DeleteEvent, MoveEvent, Event
        >>> src_path = '/mnt/net/homes/john/hello'
        >>> dst_path = '/mnt/net/homes/john/goodby'
        >>> create_event = CreateEvent(filepath.FilePath(src_path))
        >>> move_event = MoveEvent(filepath.FilePath(src_path),
        ...                        filepath.FilePath(dst_path))
        >>> delete_event = DeleteEvent(filepath.FilePath(dst_path))

        >>> def printer(event):
        ...     print event

        >>> class DummySyncDirFSNotifyProxy(SyncDirFSNotifyProxy):
        ...    def __init__(self):
        ...        self._SyncDirFSNotifyProxy__links_map_pair =\
        ...            LinksMapPair(links={
        ...                             '/home/john':
        ...                                 SymlinkInfo(
        ...                                     base_dir='/home',
        ...                                     linked_path=
        ...                                         '/mnt/net/homes/john')},
        ...                         reverse_links={
        ...                             '/mnt/net/homes/john': {'/home/john'}}
        ...                         )
        ...    def cb_with_symlink_reversing(self, event, original_cb,
        ...                                  linked_base_dir):
        ...        self._SyncDirFSNotifyProxy__cb_with_symlink_reversing(
        ...            event, original_cb, linked_base_dir)

        >>> d = DummySyncDirFSNotifyProxy()

        >>> d.cb_with_symlink_reversing(create_event, printer,
        ...     '/mnt/net/homes/john')  # doctest: +ELLIPSIS
        CreateEvent(path=FilePath('/home/john/hello'), ts=...)

        >>> d.cb_with_symlink_reversing(
        ... move_event, printer, '/mnt/net/homes/john'
        ... ) # doctest:+NORMALIZE_WHITESPACE +ELLIPSIS
        MoveEvent(from_path=FilePath('/home/john/hello'),
                  to_path=FilePath('/home/john/goodby'),
                  ts=...)

        >>> d.cb_with_symlink_reversing(
        ... delete_event, printer, '/mnt/net/homes/john'
        ... )  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
        DeleteEvent(path=FilePath('/home/john/goodby'),
                    ts=...)

        @type original_cb: col.Callable
        @type linked_base_dir: basestring
        @type event: Event
        """
        try:
            link_sources = self.__links_map_pair.reverse_links[linked_base_dir]
        except:
            logger.exception(u"Seems like we don't watch for %r already",
                             linked_base_dir)
        else:
            for link_source in link_sources:
                substituted_path = reprefix(event.path.path,
                                            old_prefix=linked_base_dir,
                                            new_prefix=link_source)

                if isinstance(event, MoveEvent):
                    substituted_to_path = reprefix(event.to_path.path,
                                                   old_prefix=linked_base_dir,
                                                   new_prefix=link_source)
                new_event = event.copy()
                new_event.path = filepath.FilePath(substituted_path)
                if isinstance(event, MoveEvent):
                    new_event.to_path = filepath.FilePath(substituted_to_path)

                logger.debug(u'Calling cb while substituting %r to %r: %r',
                             linked_base_dir, link_source, event)

                original_cb(new_event)


    @staticmethod
    def _inmerge_link_map_pair(into, merge):
        r"""
        Merge to C{merge} pair into C{into} pair (attention,
        it is modified inline!)

        >>> a1 = LinksMapPair(links={
        ...                       '/etc/networking':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/etc/networking-copy':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/home/john':
        ...                           SymlinkInfo(
        ...                               base_dir='/home',
        ...                               linked_path='/mnt/net/homes/john'),
        ...                   },
        ...                   reverse_links={
        ...                       '/mnt/net/etc/net': {'/etc/networking',
        ...                                            '/etc/networking-copy'},
        ...                       '/mnt/net/homes/john': {'/home/john'},
        ...                   })
        >>> a2 = LinksMapPair(links={
        ...                       '/etc/samba':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/smb'),
        ...                       '/etc/networking-copy2':
        ...                           SymlinkInfo(
        ...                               base_dir='/etc',
        ...                               linked_path='/mnt/net/etc/net'),
        ...                       '/home/alex':
        ...                           SymlinkInfo(
        ...                               base_dir='/home',
        ...                               linked_path='/mnt/net/homes/alex'),
        ...                   },
        ...                   reverse_links={
        ...                       '/mnt/net/etc/smb':
        ...                           {'/etc/samba'},
        ...                       '/mnt/net/etc/net':
        ...                           {'/etc/networking-copy2'},
        ...                       '/mnt/net/homes/alex':
        ...                           {'/home/alex'},
        ...                   })
        >>> SyncDirFSNotifyProxy._inmerge_link_map_pair(into=a2, merge=a1)
        >>> a1  # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
        LinksMapPair(links={'/home/john':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/john'),
                            '/etc/networking-copy':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/etc/networking':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net')},
            reverse_links={'/mnt/net/etc/net':
                              set(['/etc/networking-copy', '/etc/networking']),
                           '/mnt/net/homes/john':
                               set(['/home/john'])})
        >>> a2  # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE
        LinksMapPair(links={'/home/john':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/john'),
                            '/etc/networking':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/etc/samba':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/smb'),
                            '/etc/networking-copy':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net'),
                            '/home/alex':
                                SymlinkInfo(base_dir='/home',
                                            linked_path='/mnt/net/homes/alex'),
                            '/etc/networking-copy2':
                                SymlinkInfo(base_dir='/etc',
                                            linked_path='/mnt/net/etc/net')},
            reverse_links={'/mnt/net/etc/net':
                               set(['/etc/networking-copy',
                                    '/etc/networking',
                                    '/etc/networking-copy2']),
                           '/mnt/net/homes/john':
                               set(['/home/john']),
                           '/mnt/net/etc/smb':
                               set(['/etc/samba']),
                           '/mnt/net/homes/alex':
                               set(['/home/alex'])})

        @param into: target pair to merge the data (C{merge} arg) into.
        @type into: LinksMapPair

        @param merge: the pair being merged into C{into} pair.
        @type merge: LinksMapPair
        """
        into.links.update(merge.links)
        # Merging new_rev_links_map is a bit more complex.
        for k, v in merge.reverse_links.iteritems():
            into.reverse_links.setdefault(k, set()).update(v)


    @exceptions_logged(logger)
    def __on_poll_sync_dir_timer(self):
        cls = self.__class__
        assert not in_main_thread()

        logger.debug(u'Polling watched directories (%r)',
                     self.__watched_dir_to_cb.keys())

        # Loop over the watched directories, recreate the symlink map
        new_links_map_pair = LinksMapPair({}, {})
        for watched_dir in self.__watched_dir_to_cb.iterkeys():
            links_map_pair_for_watched_dir = \
                self.__rescan_watched_dir_for_map(watched_dir)

            cls._inmerge_link_map_pair(into=new_links_map_pair,
                                       merge=links_map_pair_for_watched_dir)

        # Update the map with the total result
        logger.debug(u'The total symlink map is %r', new_links_map_pair)
        links_set_old = set(self.__links_map_pair.links)
        links_set_new = set(new_links_map_pair.links)
        removed_links = links_set_old.difference(links_set_new)
        created_links = links_set_new.difference(links_set_old)
        self.__links_map_pair = new_links_map_pair

        if created_links:
            logger.debug(u'New links: %r', created_links)

        for link in created_links:
            linkinfo = new_links_map_pair.links[link]
            cb = self.__watched_dir_to_cb[linkinfo.base_dir]
            self.__implicitly_watched_dirs.add(linkinfo.linked_path)
            dirs, files = _get_dir_content_recursively(link)
            for path in chain(dirs, files, [linkinfo.linked_path]):
                cb(CreateEvent(filepath.FilePath(path)))
            self.__inner_fsnotify_manager.watch(
                linkinfo.linked_path,
                partial(self.__cb_with_symlink_reversing,
                        original_cb=cb,
                        linked_base_dir=linkinfo.linked_path))


        if removed_links:
            logger.warning(u'These links were removed,'
                               u' but original path maybe still watched %r,'
                               u' unwatching is not implemented.',
                           removed_links)

        #@todo: TODO: handle removed_links



    def __rescan_watched_dir_for_map(self, watched_dir):
        """
        Rescan anew the given directory and get the bidirectional mapping
        of all the found symlinks.

        @rtype: LinksMapPair
        """
        pair = LinksMapPair({}, {})

        logger.debug(u'Rescanning %r for symlinks', watched_dir)
        try:
            # Make sure the result is in Unicode
            dir_contents = [os.path.join(watched_dir, rel)
                                for rel in os.listdir(unicode(watched_dir))]
        except:
            logger.exception(u'Cannot list %r', watched_dir)
        else:
            # For every item inside the directory, check whether it is
            # a symlink/junction point
            for item in dir_contents:
                try:
                    if host_system.is_link_to_dir(item):
                        dest = os.path.abspath(
                                   host_system.read_link_to_dir(item))

                        logger.debug(u'Found %r -> %r', item, dest)

                        SyncDirFSNotifyProxy._inmerge_link_map_pair(
                            into=pair,
                            merge=LinksMapPair(
                                      links={item: SymlinkInfo(
                                                       base_dir=watched_dir,
                                                       linked_path=dest)},
                                      reverse_links={dest: {item}}))

                except:
                    logger.exception(u'Cannot test %r in %r',
                                     item, watched_dir)

        return pair
Example #15
0
class FeedPollerService(Service):
    """
    Polls AtomHopper feeds
    """
    def __init__(self,
                 agent,
                 url,
                 event_listeners,
                 interval=DEFAULT_INTERVAL,
                 state_store=None,
                 TimerService=TimerService,
                 coiterate=coiterate):
        """
        :param agent: a :class:`twisted.web.client.Agent` to use to poll

        :param url: the url to poll

        :param event_listeners: listeners that handle a particular event
        :type event_listeners: `iterable` of `callables` that take an event
            as an argument

        :param interval: how often to poll, given in seconds - defaults to 10
        :type interval: ``int`` or ``float``

        :param state_store: where to store the current polling state
        :type state_store: :class:`otter.indexer.state.IStateStore` provider

        :param TimerService: factory (not instance) that produces something
            like a :class:`twisted.application.internet.TimerService` -
            defaults to :class:`twisted.application.internet.TimerService`
            (this parameter is mainly used for dependency injection for
            testing)
        :type TimerService: ``callable``

        :param coiterate: function that is used to coiterate tasks - defaults
            to :func:`twisted.internet.task.coiterate` - (this parameter is
            mainly used for dependency injection for testing)
        :type coiterate: ``callable``
        """
        self._url = url
        self._interval = interval

        self._timer_service = TimerService(interval, self._do_poll)

        self._next_url = None

        self._agent = agent
        self._state_store = state_store or DummyStateStore()

        self._event_listeners = event_listeners
        self._poll_timer = timer('FeedPollerService.poll.{0}'.format(url))
        self._fetch_timer = timer('FeedPollerService.fetch.{0}'.format(url))

        self._coiterate = coiterate

    def startService(self):
        """
        Start the feed polling service - called by the twisted
        application when starting up
        """
        self._timer_service.startService()

    def stopService(self):
        """
        Stop the feed polling service - called by the twisted
        application when shutting down

        :return: ``Deferred``
        """
        return self._timer_service.stopService()

    def _fetch(self, url):
        """
        Get atom feed from AtomHopper url
        """
        def _parse(data):
            e = parse(data)
            return e

        def _gotResponse(resp):
            br = _BodyReceiver()

            resp.deliverBody(br)

            return br.finish

        log.msg(format="Fetching url: %(url)r", url=url)
        d = self._agent.request('GET', url, Headers({}), None)
        d.addCallback(_gotResponse)
        d.addCallback(_parse)

        return d

    def _do_poll(self):
        """
        Do one interation of polling AtomHopper.
        """
        start = time.time()

        def _get_next_url(feed):
            self._fetch_timer.update(time.time() - start)
            # next is previous, because AtomHopper is backwards in time
            next_url = previous_link(feed)

            if next_url is not None:
                self._next_url = next_url

            log.msg(format="URLS: %(url)r\n\t->%(next_url)s",
                    url=self._url,
                    next_url=self._next_url)

            sd = self._state_store.save_state(self._next_url)
            sd.addCallback(lambda _: feed)
            return sd

        def _dispatch_entries(feed):
            # Actually sort by updated date.
            sorted_entries = sorted(entries(feed),
                                    key=lambda x: parse_date(updated(x)))
            return self._coiterate(
                chain.from_iterable(((el(entry)
                                      for el in self._event_listeners)
                                     for entry in sorted_entries)))

        def _finish_iteration(ignore):
            self._poll_timer.update(time.time() - start)

        d = self._state_store.get_state()
        d.addCallback(
            lambda saved_url: self._next_url or saved_url or self._url)
        d.addCallback(self._fetch)
        d.addCallback(_get_next_url)
        d.addCallback(_dispatch_entries)
        d.addErrback(log.err)
        d.addBoth(_finish_iteration)
        return d
Example #16
0
 def startService(self):
     if self.log_file is not None:
         return
     self.log_file = open(self.log_path, 'r')
     TimerService.startService(self)
Example #17
0
class AcmeIssuingService(Service):
    """
    A service for keeping certificates up to date by using an ACME server.

    :param .ICertificateStore cert_store: The certificate store containing the
        certificates to manage.
    :param ~txacme.client.Client client: The ACME client to use.  Typically
        constructed with `Client.from_url <txacme.client.Client.from_url>`.
    :param clock: ``IReactorTime`` provider; usually the reactor, when not
        testing.

    :type responders: List[`.IResponder`]
    :param responders: Challenge responders.  Usually only one responder is
        needed; if more than one responder for the same type is provided, only
        the first will be used.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    cert_store = attr.ib()
    _client = attr.ib()
    _clock = attr.ib()
    _responders = attr.ib()
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))
    _waiting = attr.ib(default=attr.Factory(list))
    ready = False

    def _now(self):
        """
        Get the current time.
        """
        return clock_now(self._clock)

    def _check_certs(self):
        """
        Check all of the certs in the store, and reissue any that are expired
        or close to expiring.
        """
        def check(certs):
            panicing = set()
            expiring = set()
            for server_name, objects in certs.items():
                if len(objects) == 0:
                    panicing.add(server_name)
                for o in filter(lambda o: isinstance(o, Certificate), objects):
                    cert = x509.load_pem_x509_certificate(
                        o.as_bytes(), default_backend())
                    until_expiry = cert.not_valid_after - self._now()
                    if until_expiry <= self.panic_interval:
                        panicing.add(server_name)
                    elif until_expiry <= self.reissue_interval:
                        expiring.add(server_name)
            d1 = (
                gatherResults(
                    [self._issue_cert(server_name)
                     .addErrback(self._panic, server_name)
                     for server_name in panicing],
                    consumeErrors=True)
                .addCallback(done_panicing))
            d2 = gatherResults(
                [self._issue_cert(server_name)
                 .addErrback(
                     lambda f: log.failure(
                         u'Error issuing certificate for: {server_name!r}',
                         f, server_name=server_name))
                 for server_name in expiring],
                consumeErrors=True)
            return gatherResults([d1, d2], consumeErrors=True)

        def done_panicing(ignored):
            self.ready = True
            for d in list(self._waiting):
                d.callback(None)
            self._waiting = []

        return (
            self._register()
            .addCallback(lambda _: self.cert_store.as_dict())
            .addCallback(check))

    def _issue_cert(self, server_name):
        """
        Issue a new cert for a particular name.
        """
        key = self._generate_key()
        objects = [
            Key(key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption()))]

        def answer_and_poll(authzr):
            def got_challenge(r):
                responder, response = r

                def stop_responding(result):
                    return responder.stop_responding(response)

                return (
                    poll_until_valid(authzr, self._clock, self._client)
                    .addBoth(tap(stop_responding)))
            return (
                answer_challenge(authzr, self._client, self._responders)
                .addCallback(got_challenge))

        def got_cert(certr):
            objects.append(
                Certificate(
                    x509.load_der_x509_certificate(
                        certr.body, default_backend())
                    .public_bytes(serialization.Encoding.PEM)))
            return certr

        def got_chain(chain):
            for certr in chain:
                got_cert(certr)
            return objects

        return (
            self._client.request_challenges(fqdn_identifier(server_name))
            .addCallback(answer_and_poll)
            .addCallback(lambda ign: self._client.request_issuance(
                CertificateRequest(
                    csr=csr_for_names([server_name], key))))
            .addCallback(got_cert)
            .addCallback(self._client.fetch_chain)
            .addCallback(got_chain)
            .addCallback(partial(self.cert_store.store, server_name)))

    def _register(self):
        """
        Register if needed.
        """
        def _registered(ign):
            self._registered = True
        if self._registered:
            return succeed(None)
        else:
            return (
                self._client.register()
                .addCallback(self._client.agree_to_tos)
                .addCallback(_registered))

    def when_certs_valid(self):
        """
        Get a notification once the startup check has completed.

        When the service starts, an initial check is made immediately; the
        deferred returned by this function will only fire once reissue has been
        attempted for any certificates within the panic interval.

        ..  note:: The reissue for any of these certificates may not have been
            successful; the panic callback will be invoked for any certificates
            in the panic interval that failed reissue.

        :rtype: ``Deferred``
        :return: A deferred that fires once the initial check has resolved.
        """
        if self.ready:
            return succeed(None)
        d = Deferred()
        self._waiting.append(d)
        return d

    def startService(self):
        Service.startService(self)
        self._registered = False
        self._timer_service = TimerService(
            self.check_interval.total_seconds(), self._check_certs)
        self._timer_service.clock = self._clock
        self._timer_service.startService()

    def stopService(self):
        Service.stopService(self)
        self.ready = False
        self._registered = False
        for d in list(self._waiting):
            d.cancel()
        self._waiting = []
        return self._timer_service.stopService()
Example #18
0
class Server(ServerInterface.ServerBase):

    m_isRunning = False

    m_config = None
    m_timer = None

    m_port = 0
    m_connectorServer = None

    def init(self, subtype, conf):
        cfgip = str(conf.get("configsvr", "host"))
        cfgport = int(conf.get("configsvr", "port"))
        self.m_config = ConfigClient.ConfigClent(self, subtype,
                                                 Base.SVR_TYPE_CENTER, cfgip,
                                                 cfgport)

        self.m_connectorServer = ConnectorServer.ConnectorServer(self)

        #gDBManager.init(conf)

    def run(self):
        self.m_config.connect(self.configCallBack)

        self.m_timer = TimerService(1, self.timer)
        self.m_timer.startService()

        # 要放在最后一步
        from twisted.internet import reactor
        self.m_isRunning = True
        logging.info("reactor run")
        reactor.run()

    def timer(self):
        pass

    def configCallBack(self, flag):
        if flag:
            configstr = self.m_config.getConfig()
            configstr = "{" + configstr + "}"
            tab = eval(configstr)
            if tab.has_key('dbip') and tab.has_key('dbport') and tab.has_key(
                    'dbuser') and tab.has_key('dbpwd') and tab.has_key(
                        'dbname'):
                gDBManager.init(tab['dbip'], tab['dbport'], tab['dbuser'],
                                tab['dbpwd'], tab['dbname'])
            else:
                logging.error("db config error")
                self.stop()
                return
            self.m_connectorServer.begin(self.m_config.getPort())

        else:
            logging.error("connect config error and return")
            self.stop()

    def stop(self):
        self.m_timer.stopService()
        if self.m_isRunning:
            from twisted.internet import reactor
            if not reactor._stopped:
                logging.info("stop reactor")
                reactor.stop()
            else:
                logging.info("try stop ractor,but is stopped")
        else:
            logging.info("try stop svr,but is not running")

    def newClient(self, conn):
        logging.info("conn ip=%s,appid=%d" %
                     (conn.transport.hostname, conn.m_numid))

    def recvFromClient(self, conn, packlen, appid, numid, xyid, data):
        self.selectProtocol(conn, packlen, appid, numid, xyid, data)

    def loseClient(self, conn):
        logging.info("conn ip=%s" % (conn.transport.hostname))

    def selectProtocol(self, conn, packlen, appid, numid, xyid, data):
        logging.debug("packlen=%d,appid=%d,srcappid=%d,numid=%d,xyid=%d" %
                      (packlen, appid, conn.m_numid, numid, xyid))
        if xyid == ProtocolSRS.XYID_SRS_REQ_LOGIN:
            req = ProtocolSRS.ReqLogin()
            ret = req.make(data)
            logging.info("ReqLogin:connid=%d,userid=%s,pwd=%s" %
                         (req.connid, req.userid, req.password))

            resp = ProtocolSRS.RespLogin()
            resp.connid = req.connid

            sql = "select numid,passwd from players where userid='%s'" % req.userid
            ret, row, rslt = gDBManager.select(sql)
            if not ret:
                resp.flag = resp.FLAG.DBERR
                logging.error("select ret err,sql=%s" % sql)
            elif row <= 0:
                resp.flag = resp.FLAG.NOUSER
                logging.info("userid=%s select no data" % req.userid)
            else:
                if str(rslt[0][1]) == req.password:
                    resp.flag = resp.FLAG.SUCCESS
                    resp.numid = int(rslt[0][0])
                    logging.info("userid=%s login success,numid=%d" %
                                 (req.userid, resp.numid))
                else:
                    resp.flag = resp.FLAG.PWDERR
                    logging.info("userid=%s pwd err" % req.userid)

            buf = resp.pack()
            conn.sendData(buf)

        else:
            logging.warning("unknown xy,xyid=%d" % xyid)
Example #19
0
class MetricsService(Service, object):
    """
    Service collects metrics on continuous basis
    """

    def __init__(self, reactor, config, log, clock=None, collect=None):
        """
        Initialize the service by connecting to Cassandra and setting up
        authenticator

        :param reactor: Twisted reactor for connection purposes
        :param dict config: All the config necessary to run the service.
            Comes from config file
        :param IReactorTime clock: Optional reactor for timer purpose
        """
        self._client = connect_cass_servers(reactor, config['cassandra'])
        self.log = log
        self.reactor = reactor
        self._divergent_groups = {}
        self.divergent_timeout = get_in(
            ['metrics', 'divergent_timeout'], config, 3600)
        self._service = TimerService(
            get_in(['metrics', 'interval'], config, default=60),
            collect or self.collect,
            reactor,
            config,
            self.log,
            client=self._client,
            authenticator=generate_authenticator(reactor, config['identity']))
        self._service.clock = clock or reactor

    @defer.inlineCallbacks
    def collect(self, *a, **k):
        try:
            metrics = yield collect_metrics(*a, **k)
            self._divergent_groups, to_log = unchanged_divergent_groups(
                self.reactor, self._divergent_groups, self.divergent_timeout,
                metrics)
            for group, duration in to_log:
                self.log.err(
                    ValueError(""),  # Need to give an exception to log err
                    ("Group {group_id} of {tenant_id} remains diverged "
                     "and unchanged for {divergent_time}"),
                    tenant_id=group.tenant_id, group_id=group.group_id,
                    desired=group.desired, actual=group.actual,
                    pending=group.pending,
                    divergent_time=str(timedelta(seconds=duration)))
        except Exception:
            self.log.err(None, "Error collecting metrics")

    def startService(self):
        """
        Start this service by starting internal TimerService
        """
        Service.startService(self)
        return self._service.startService()

    def stopService(self):
        """
        Stop service by stopping the timerservice and disconnecting cass client
        """
        Service.stopService(self)
        d = self._service.stopService()
        return d.addCallback(lambda _: self._client.disconnect())
class TimerServiceTests(TestCase):
    """
    Tests for L{twisted.application.internet.TimerService}.

    @type timer: L{TimerService}
    @ivar timer: service to test

    @type clock: L{task.Clock}
    @ivar clock: source of time

    @type deferred: L{Deferred}
    @ivar deferred: deferred returned by L{TimerServiceTests.call}.
    """

    def setUp(self):
        """
        Set up a timer service to test.
        """
        self.timer = TimerService(2, self.call)
        self.clock = self.timer.clock = task.Clock()
        self.deferred = Deferred()


    def call(self):
        """
        Function called by L{TimerService} being tested.

        @returns: C{self.deferred}
        @rtype: L{Deferred}
        """
        return self.deferred


    def test_startService(self):
        """
        When L{TimerService.startService} is called, it marks itself
        as running, creates a L{task.LoopingCall} and starts it.
        """
        self.timer.startService()
        self.assertTrue(self.timer.running, "Service is started")
        self.assertIsInstance(self.timer._loop, task.LoopingCall)
        self.assertIdentical(self.clock, self.timer._loop.clock)
        self.assertTrue(self.timer._loop.running, "LoopingCall is started")


    def test_startServiceRunsCallImmediately(self):
        """
        When L{TimerService.startService} is called, it calls the function
        immediately.
        """
        result = []
        self.timer.call = (result.append, (None,), {})
        self.timer.startService()
        self.assertEqual([None], result)


    def test_startServiceUsesGlobalReactor(self):
        """
        L{TimerService.startService} uses L{internet._maybeGlobalReactor} to
        choose the reactor to pass to L{task.LoopingCall}
        uses the global reactor.
        """
        otherClock = task.Clock()
        def getOtherClock(maybeReactor):
            return otherClock
        self.patch(internet, "_maybeGlobalReactor", getOtherClock)
        self.timer.startService()
        self.assertIdentical(otherClock, self.timer._loop.clock)


    def test_stopServiceWaits(self):
        """
        When L{TimerService.stopService} is called while a call is in progress.
        the L{Deferred} returned doesn't fire until after the call finishes.
        """
        self.timer.startService()
        d = self.timer.stopService()
        self.assertNoResult(d)
        self.assertEqual(True, self.timer.running)
        self.deferred.callback(object())
        self.assertIdentical(self.successResultOf(d), None)


    def test_stopServiceImmediately(self):
        """
        When L{TimerService.stopService} is called while a call isn't in progress.
        the L{Deferred} returned has already been fired.
        """
        self.timer.startService()
        self.deferred.callback(object())
        d = self.timer.stopService()
        self.assertIdentical(self.successResultOf(d), None)


    def test_failedCallLogsError(self):
        """
        When function passed to L{TimerService} returns a deferred that errbacks,
        the exception is logged, and L{TimerService.stopService} doesn't raise an error.
        """
        self.timer.startService()
        self.deferred.errback(Failure(ZeroDivisionError()))
        errors = self.flushLoggedErrors(ZeroDivisionError)
        self.assertEqual(1, len(errors))
        d = self.timer.stopService()
        self.assertIdentical(self.successResultOf(d), None)


    def test_pickleTimerServiceNotPickleLoop(self):
        """
        When pickling L{internet.TimerService}, it won't pickle
        L{internet.TimerService._loop}.
        """
        # We need a pickleable callable to test pickling TimerService. So we
        # can't use self.timer
        timer = TimerService(1, fakeTargetFunction)
        timer.startService()
        dumpedTimer = pickle.dumps(timer)
        timer.stopService()
        loadedTimer = pickle.loads(dumpedTimer)
        nothing = object()
        value = getattr(loadedTimer, "_loop", nothing)
        self.assertIdentical(nothing, value)


    def test_pickleTimerServiceNotPickleLoopFinished(self):
        """
        When pickling L{internet.TimerService}, it won't pickle
        L{internet.TimerService._loopFinished}.
        """
        # We need a pickleable callable to test pickling TimerService. So we
        # can't use self.timer
        timer = TimerService(1, fakeTargetFunction)
        timer.startService()
        dumpedTimer = pickle.dumps(timer)
        timer.stopService()
        loadedTimer = pickle.loads(dumpedTimer)
        nothing = object()
        value = getattr(loadedTimer, "_loopFinished", nothing)
        self.assertIdentical(nothing, value)
Example #21
0
 def startService(self):
     self.command_helper.responder(self.complete_command)(self.complete)
     self.command_helper.responder(self.failed_command)(self.failed)
     self.command_helper.register()
     TimerService.startService(self)
Example #22
0
class MetricsService(Service, object):
    """
    Service collects metrics on continuous basis
    """

    def __init__(self, reactor, config, log, clock=None, collect=None):
        """
        Initialize the service by connecting to Cassandra and setting up
        authenticator

        :param reactor: Twisted reactor for connection purposes
        :param dict config: All the config necessary to run the service.
            Comes from config file
        :param IReactorTime clock: Optional reactor for timer purpose
        """
        self._client = connect_cass_servers(reactor, config['cassandra'])
        self.log = log
        self.reactor = reactor
        self._divergent_groups = {}
        self.divergent_timeout = get_in(
            ['metrics', 'divergent_timeout'], config, 3600)
        self._service = TimerService(
            get_in(['metrics', 'interval'], config, default=60),
            collect or self.collect,
            reactor,
            config,
            self.log,
            client=self._client,
            authenticator=generate_authenticator(reactor, config['identity']))
        self._service.clock = clock or reactor

    @defer.inlineCallbacks
    def collect(self, *a, **k):
        try:
            metrics = yield collect_metrics(*a, **k)
            self._divergent_groups, to_log = unchanged_divergent_groups(
                self.reactor, self._divergent_groups, self.divergent_timeout,
                metrics)
            for group, duration in to_log:
                self.log.err(
                    ValueError(""),  # Need to give an exception to log err
                    ("Group {group_id} of {tenant_id} remains diverged "
                     "and unchanged for {divergent_time}"),
                    tenant_id=group.tenant_id, group_id=group.group_id,
                    desired=group.desired, actual=group.actual,
                    pending=group.pending,
                    divergent_time=str(timedelta(seconds=duration)))
        except Exception:
            self.log.err(None, "Error collecting metrics")

    def startService(self):
        """
        Start this service by starting internal TimerService
        """
        Service.startService(self)
        return self._service.startService()

    def stopService(self):
        """
        Stop service by stopping the timerservice and disconnecting cass client
        """
        Service.stopService(self)
        d = self._service.stopService()
        return d.addCallback(lambda _: self._client.disconnect())
Example #23
0
class RestoreRequestProcessor(object):
    """
    The processor for the externally initiated restore requests.
    """
    __slots__ = ('__server_process', '__restore_timer')


    def __init__(self, server_process):
        assert isinstance(server_process, ServerProcess), \
               repr(server_process)
        self.__server_process = server_process

        self.__restore_timer = \
            TimerService(WEB_RESTORE_PERIOD.total_seconds(),
                         exceptions_logged(logger)(callInThread),
                         self.__poll_restore_requests_in_thread)


    def __poll_restore_requests_in_thread(self):
        """Perform another iteration of polling the restore requests."""
        assert not in_main_thread()

        poll_uuid = gen_uuid()
        logger.debug('Polling restore requests (%s)', poll_uuid)

        restore_request = True
        while restore_request is not None:
            with ds.FDB() as fdbw:
                restore_request = \
                    FDBQueries.RestoreRequests \
                              .atomic_start_oldest_restore_request(fdbw=fdbw)

            logger.debug('Poll (%s) returned %r', poll_uuid, restore_request)
            if restore_request is not None:
                # We've indeed have some restore request that needs processing.

                # Create new "virtual" dataset with all the data
                # to be restored.
                with db.RDB() as rdbw:
                    new_ds_uuid = \
                        Queries.Datasets.restore_files_to_dataset_clone(
                            restore_request.base_ds_uuid,
                            restore_request.paths,
                            restore_request.ts_start,
                            rdbw)

                # Now we know the new dataset to be restored.
                # Btw, write it into the docstore.
                # Doesn't need to be atomic, as only a single node
                # may be processing it at a time.
                with ds.FDB() as fdbw:
                    FDBQueries.RestoreRequests.set_ds_uuid(
                        _id=restore_request._id,
                        new_ds_uuid=new_ds_uuid,
                        fdbw=fdbw)

                # After creating the dataset, let's restore it to all host
                # which are alive.
                _syncer = self.__server_process.app.syncer
                _syncer.restore_dataset_to_lacking_hosts(
                    me=self.__server_process.me,
                    host=None,
                    ds_uuid=new_ds_uuid)

        logger.debug('Polling restore requests (%s) - done', poll_uuid)


    def start(self):
        """Start the processor."""
        assert in_main_thread()
        self.__restore_timer.startService()


    def stop(self):
        """Stop the processor."""
        assert in_main_thread()
        self.__restore_timer.stopService()
Example #24
0
class AcmeIssuingService(Service):
    """
    A service for keeping certificates up to date by using an ACME server.

    :param .ICertificateStore cert_store: The certificate store containing the
        certificates to manage.
    :type client_creator: Callable[[], Deferred[`txacme.client.Client`]]
    :param client_creator: A callable called with no arguments
        for creating the ACME client.  For example, ``partial(Client.from_url,
        reactor=reactor, url=LETSENCRYPT_STAGING_DIRECTORY, key=acme_key,
        alg=RS256)``.
    :param clock: ``IReactorTime`` provider; usually the reactor, when not
        testing.

    :type responders: List[`.IResponder`]
    :param responders: Challenge responders.  Usually only one responder is
        needed; if more than one responder for the same type is provided, only
        the first will be used.
    :param str email: An (optional) email address to use during registration.
    :param ~datetime.timedelta check_interval: How often to check for expiring
        certificates.
    :param ~datetime.timedelta reissue_interval: If a certificate is expiring
        in less time than this interval, it will be reissued.
    :param ~datetime.timedelta panic_interval: If a certificate is expiring in
        less time than this interval, and reissuing fails, the panic callback
        will be invoked.

    :type panic: Callable[[Failure, `str`], Deferred]
    :param panic: A callable invoked with the failure and server name when
        reissuing fails for a certificate expiring in the ``panic_interval``.
        For example, you could generate a monitoring alert.  The default
        callback logs a message at *CRITICAL* level.
    :param generate_key: A 0-arg callable used to generate a private key for a
        new cert.  Normally you would not pass this unless you have specialized
        key generation requirements.
    """
    cert_store = attr.ib()
    _client_creator = attr.ib()
    _clock = attr.ib()
    _responders = attr.ib()
    _email = attr.ib(default=None)
    check_interval = attr.ib(default=timedelta(days=1))
    reissue_interval = attr.ib(default=timedelta(days=30))
    panic_interval = attr.ib(default=timedelta(days=15))
    _panic = attr.ib(default=_default_panic)
    _generate_key = attr.ib(default=partial(generate_private_key, u'rsa'))

    _waiting = attr.ib(default=attr.Factory(list), init=False)
    _issuing = attr.ib(default=attr.Factory(dict), init=False)
    ready = False

    def _now(self):
        """
        Get the current time.
        """
        return clock_now(self._clock)

    def _check_certs(self):
        """
        Check all of the certs in the store, and reissue any that are expired
        or close to expiring.
        """
        log.info('Starting scheduled check for expired certificates.')

        def check(certs):
            panicing = set()
            expiring = set()
            for server_name, objects in certs.items():
                if len(objects) == 0:
                    panicing.add(server_name)
                for o in filter(lambda o: isinstance(o, Certificate), objects):
                    cert = x509.load_pem_x509_certificate(
                        o.as_bytes(), default_backend())
                    until_expiry = cert.not_valid_after - self._now()
                    if until_expiry <= self.panic_interval:
                        panicing.add(server_name)
                    elif until_expiry <= self.reissue_interval:
                        expiring.add(server_name)

            log.info(
                'Found {panicing_count:d} overdue / expired and '
                '{expiring_count:d} expiring certificates.',
                panicing_count=len(panicing),
                expiring_count=len(expiring))

            d1 = (
                gatherResults(
                    [self._with_client(self._issue_cert, server_name)
                     .addErrback(self._panic, server_name)
                     for server_name in panicing],
                    consumeErrors=True)
                .addCallback(done_panicing))
            d2 = gatherResults(
                [self.issue_cert(server_name)
                 .addErrback(
                     lambda f: log.failure(
                         u'Error issuing certificate for: {server_name!r}',
                         f, server_name=server_name))
                 for server_name in expiring],
                consumeErrors=True)
            return gatherResults([d1, d2], consumeErrors=True)

        def done_panicing(ignored):
            self.ready = True
            for d in list(self._waiting):
                d.callback(None)
            self._waiting = []

        return (
            self._ensure_registered()
            .addCallback(lambda _: self.cert_store.as_dict())
            .addCallback(check)
            .addErrback(
                lambda f: log.failure(
                    u'Error in scheduled certificate check.', f)))

    def issue_cert(self, server_name):
        """
        Issue a new cert for a particular name.

        If an existing cert exists, it will be replaced with the new cert.  If
        issuing is already in progress for the given name, a second issuing
        process will *not* be started.

        :param str server_name: The name to issue a cert for.

        :rtype: ``Deferred``
        :return: A deferred that fires when issuing is complete.
        """
        def finish(result):
            _, waiting = self._issuing.pop(server_name)
            for d in waiting:
                d.callback(result)

        # d_issue is assigned below, in the conditional, since we may be
        # creating it or using the existing one.
        d = Deferred(lambda _: d_issue.cancel())
        if server_name in self._issuing:
            d_issue, waiting = self._issuing[server_name]
            waiting.append(d)
        else:
            d_issue = self._with_client(self._issue_cert, server_name)
            waiting = [d]
            self._issuing[server_name] = (d_issue, waiting)
            # Add the callback afterwards in case we're using a client
            # implementation that isn't actually async
            d_issue.addBoth(finish)
        return d

    def _with_client(self, f, *a, **kw):
        """
        Construct a client, and perform an operation with it.
        """
        return self._client_creator().addCallback(f, *a, **kw)

    def _issue_cert(self, client, server_name):
        """
        Issue a new cert for a particular name.
        """
        log.info(
            'Requesting a certificate for {server_name!r}.',
            server_name=server_name)
        key = self._generate_key()
        objects = [
            Key(key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption()))]

        def answer_and_poll(authzr):
            def got_challenge(stop_responding):
                return (
                    poll_until_valid(authzr, self._clock, client)
                    .addBoth(tap(lambda _: stop_responding())))
            return (
                answer_challenge(authzr, client, self._responders)
                .addCallback(got_challenge))

        def got_cert(certr):
            objects.append(
                Certificate(
                    x509.load_der_x509_certificate(
                        certr.body, default_backend())
                    .public_bytes(serialization.Encoding.PEM)))
            return certr

        def got_chain(chain):
            for certr in chain:
                got_cert(certr)
            log.info(
                'Received certificate for {server_name!r}.',
                server_name=server_name)
            return objects

        return (
            client.request_challenges(fqdn_identifier(server_name))
            .addCallback(answer_and_poll)
            .addCallback(lambda ign: client.request_issuance(
                CertificateRequest(
                    csr=csr_for_names([server_name], key))))
            .addCallback(got_cert)
            .addCallback(client.fetch_chain)
            .addCallback(got_chain)
            .addCallback(partial(self.cert_store.store, server_name)))

    def _ensure_registered(self):
        """
        Register if needed.
        """
        if self._registered:
            return succeed(None)
        else:
            return self._with_client(self._register)

    def _register(self, client):
        """
        Register and agree to the TOS.
        """
        def _registered(regr):
            self._regr = regr
            self._registered = True
        regr = messages.NewRegistration.from_data(email=self._email)
        return (
            client.register(regr)
            .addCallback(client.agree_to_tos)
            .addCallback(_registered))

    def when_certs_valid(self):
        """
        Get a notification once the startup check has completed.

        When the service starts, an initial check is made immediately; the
        deferred returned by this function will only fire once reissue has been
        attempted for any certificates within the panic interval.

        ..  note:: The reissue for any of these certificates may not have been
            successful; the panic callback will be invoked for any certificates
            in the panic interval that failed reissue.

        :rtype: ``Deferred``
        :return: A deferred that fires once the initial check has resolved.
        """
        if self.ready:
            return succeed(None)
        d = Deferred()
        self._waiting.append(d)
        return d

    def startService(self):
        Service.startService(self)
        self._registered = False
        self._timer_service = TimerService(
            self.check_interval.total_seconds(), self._check_certs)
        self._timer_service.clock = self._clock
        self._timer_service.startService()

    def stopService(self):
        Service.stopService(self)
        self.ready = False
        self._registered = False
        for d in list(self._waiting):
            d.cancel()
        self._waiting = []
        return self._timer_service.stopService()
Example #25
0
class Server(ServerInterface.ServerBase, ServerInterface.ClientManager):

    m_isRunning = False

    m_config = None
    m_timer = None

    m_port = 0
    m_connectorServer = None
    m_playerManager = None
    #m_gameSvrClient = None

    m_centerCliHost = ""
    m_centerCliPort = 0
    m_centerClients = None

    def init(self, subtype, conf):
        cfgip = str(conf.get("configsvr", "host"))
        cfgport = int(conf.get("configsvr", "port"))
        self.m_config = ConfigClient.ConfigClent(self, subtype,
                                                 Base.SVR_TYPE_SRS, cfgip,
                                                 cfgport)

        self.m_connectorServer = ConnectorServer.ConnectorServer(self)
        self.m_centerClients = ClientManager.ClientManager(self)
        self.m_playerManager = PlayerManager.PlayerManager(self)

    def timer(self):
        self.m_playerManager.timer()

    def run(self):
        self.m_config.connect(self.configCallBack)

        self.m_timer = TimerService(1, self.timer)
        self.m_timer.startService()

        #要放在最后一步
        from twisted.internet import reactor
        self.m_isRunning = True
        logging.info("reactor run")
        reactor.run()

    def configCallBack(self, flag):
        if flag:
            self.m_connectorServer.begin(self.m_config.getPort())

            sql = "SELECT CONCAT(cast(A.id AS CHAR),'$$$',cast(B.ip AS CHAR),'$$$',cast(A. PORT AS CHAR)) FROM config_svr A,config_routing_table B WHERE A.svrtype=2 AND A.svrid=B.id AND A.hide=0"
            self.m_config.GetConfigBySql(sql, self.getGameServerConfigCB)
        else:
            logging.error("connect config error and return")
            self.stop()

    def getGameServerConfigCB(self, flag, retstr):
        if flag and len(retstr) > 0:
            for i in range(len(retstr)):
                logging.info("index=%d,str=%s" % (i, retstr[i]))
                strconfig = str(retstr[i])
                tab = strconfig.split("$$$")
                appid = int(tab[0])
                self.m_centerCliHost = str(tab[1])
                self.m_centerCliPort = int(tab[2])
                self.m_centerClients.addConnect(appid, self.m_centerCliHost,
                                                self.m_centerCliPort)
        else:
            logging.error("get gameserver config error")
            self.stop()

    def stop(self):
        if self.m_timer:
            self.m_timer.stopService()
        if self.m_isRunning:
            from twisted.internet import reactor
            if not reactor._stopped:
                logging.info("stop reactor")
                reactor.stop()
            else:
                logging.info("try stop ractor,but is stopped")
        else:
            logging.info("try stop svr,but is not running")

    # Client
    def newClient(self, conn):
        self.m_playerManager.newClient(conn)

    def recvFromClient(self, conn, packlen, appid, numid, xyid, data):
        self.m_playerManager.recvFromClient(conn, packlen, appid, numid, xyid,
                                            data)

    def loseClient(self, conn):
        self.m_playerManager.loseClient(conn)

    # GameSver
    def recvData(self, packlen, appid, srcappid, numid, xyid, data):
        self.m_playerManager.recvFromServer(packlen, appid, srcappid, numid,
                                            xyid, data)

    def sendToServer(self, data):
        self.m_centerClients.sendData(data)
Example #26
0
def _start_dummy_data_transfer_ventilator():
    #from random import choice

    def restore_started(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid,
                                   'started',
                                   extra={
                                       'result': 'started',
                                       'ds_uuid': uuid,
                                       'tr_uuid': None,
                                       'target_dir': None
                                   })

    def restore_running(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid,
                                   'running',
                                   extra={
                                       'result': 'running',
                                       'ds_uuid': uuid,
                                       'tr_uuid': None,
                                       'target_dir': None
                                   })

    def restore_finished(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid,
                                   'finished',
                                   extra={
                                       'result': 'success',
                                       'ds_uuid': uuid,
                                       'tr_uuid': None,
                                       'target_dir': None
                                   })

    def restore_failed(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid,
                                   'failed',
                                   extra={
                                       'result': 'fail',
                                       'ds_uuid': uuid,
                                       'tr_uuid': None,
                                       'target_dir': None
                                   })

    def restore_progress(uuid, transfered, total, transfered_bytes,
                         total_bytes):
        logger_status_restore_progress.info('dummy restore progresses with' +
                                            ' different transfered and total' +
                                            ' uuid: %s' + ' transfered: %s' +
                                            ' total %s',
                                            uuid,
                                            transfered,
                                            total,
                                            extra={
                                                'progresses': [{
                                                    'uuid':
                                                    uuid,
                                                    'num':
                                                    transfered,
                                                    'of':
                                                    total,
                                                    'num_bytes':
                                                    transfered_bytes,
                                                    'of_bytes':
                                                    total_bytes
                                                }]
                                            })

    def backup_started(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid,
                                  'started',
                                  extra={
                                      'status': 'started',
                                      'ds_uuid': uuid,
                                      'result_str': '',
                                      'relaunch_in_mins': None
                                  })

    def backup_running(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid,
                                  'running',
                                  extra={
                                      'status': 'running',
                                      'ds_uuid': uuid,
                                      'result_str': '',
                                      'relaunch_in_mins': None
                                  })

    def backup_finished(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid,
                                  'finished',
                                  extra={
                                      'status': 'ok',
                                      'ds_uuid': uuid,
                                      'result_str': '',
                                      'relaunch_in_mins': None
                                  })

    def backup_failed(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid,
                                  'failed',
                                  extra={
                                      'status': 'fail',
                                      'ds_uuid': uuid,
                                      'result_str': '',
                                      'relaunch_in_mins': None
                                  })

    def backup_progress(uuid, transfered, total, transfered_bytes,
                        total_bytes):
        logger_status_backup_progress.info('dummy backup progress' +
                                           ' uuid: %s' + ' transfered: %s' +
                                           ' total %s',
                                           uuid,
                                           transfered,
                                           total,
                                           extra={
                                               'ds_uuid': uuid,
                                               'num': transfered,
                                               'of': total,
                                               'num_bytes': transfered_bytes,
                                               'of_bytes': total_bytes
                                           })

    def run_good_case():
        backup_uuid = uuid4()
        restore_uuid = uuid4()

        restore_total = 1000
        backup_total = 1000

        backup_started(backup_uuid)
        restore_started(restore_uuid)

        @exceptions_logged(logger)
        def _backup_progress(uuid, mult, total):
            backup_running(uuid)
            backup_progress(uuid, int(mult * total), total,
                            100000 * int(mult * total), 100000 * total)

        @exceptions_logged(logger)
        def _restore_progress(uuid, mult, total):
            restore_progress(uuid, int(mult * total), total,
                             100000 * int(mult * total), 100000 * total)
            restore_running(uuid)

        for i in xrange(1, 11):
            reactor.callLater(2 * i, _backup_progress, backup_uuid,
                              i * 10. / 100.0, backup_total)

            reactor.callLater(2 * i, _restore_progress, restore_uuid,
                              i * 10. / 100.0, restore_total)

        reactor.callLater(30, backup_finished, backup_uuid)
        reactor.callLater(30, restore_failed, restore_uuid)

    def run_bad_case():
        backup_uuid = uuid4()
        restore_uuid = uuid4()

        restore_total = 1000
        backup_total = 1000

        backup_started(backup_uuid)
        restore_started(restore_uuid)

        def _backup_progress(uuid, mult, total):
            backup_running(uuid)
            backup_progress(uuid, int(mult * total), total,
                            100000 * int(mult * total), 100000 * total)

        def _restore_progress(uuid, mult, total):
            restore_progress(uuid, int(mult * total), total,
                             100000 * int(mult * total), 100000 * total)
            restore_running(uuid)

        for i in xrange(1, 11):
            reactor.callLater(2 * i, _backup_progress, backup_uuid,
                              i * 10. / 100.0, backup_total)

            reactor.callLater(2 * i, _restore_progress, restore_uuid,
                              i * 9. / 100.0, restore_total)

        reactor.callLater(30, backup_finished, backup_uuid)
        reactor.callLater(30, restore_failed, restore_uuid)

    @exceptions_logged(logger)
    def run_case():
        if run_case.next == 'good':
            run_case.next = 'bad'
            run_good_case()
        else:
            run_case.next = 'good'
            run_bad_case()

    run_case.next = 'good'

    t = TimerService(50, run_case)
    t.startService()
Example #27
0
def aLongMethod(filename, file, sp, protocol):
    import time
    time.sleep(2)
    protocol.message(b"hello!!!")
    #flen = os.path.getsize(filename)
    #if sp > 0 and sp < flen:
    #    myfile.seek(sp+1)
    #    for line in myfile:
    #       protocol.send(line);
    #    myfile.seek(0, os.SEEK_END)
    #    sp = myfile.tell()
    reactor.callInThread(aLongMethod, filename, file, sp, protocol)

factory = Factory()
factory.clients = []
factory.protocol = FlowPush
s = TimerService(2, watch)
s.startService()
reactor.listenTCP(8080, factory)

#other = ReadThread()
#other.start()

print "Flowpussh server started!"
FLOWFILE = "/root/flows.txt"
myfile = open(FLOWFILE, "rt")
sp = os.path.getsize(FLOWFILE)
#reactor.callInThread(aLongMethod, FLOWFILE, myfile, sp, factory.protocol)
reactor.run()

Example #28
0
 def stopService(self):
     self.command_helper.de_register()
     self.command_helper.remove_responder(self.complete_command, self.complete)
     self.command_helper.remove_responder(self.failed_command, self.failed)
     TimerService.startService(self)
Example #29
0
class TrafficMeter(object):
    """
    Traffic meter, measuring all the traffic to some direction.

    @note: At the moment, restart of the service (after it has been started)
           is not supported.

    @ivar __start_time: the time of the first registered traffic "tick".
    @type __start_time: datetime

    @ivar __ticks: the sequence of TrafficTick objects
                   (always sorted by the time, increasing!)
                   registering each successfully passed bunch of bytes.
    @invariant: consists_of(self.__ticks, TrafficTick)
    """
    __slots__ = ('__direction_incoming', '__total_bytes', '__ticks',
                 '__ticks_lock', '__start_time', '__timer_service',
                 '__started')


    def __init__(self, is_direction_incoming):
        """
        @param is_direction_incoming: whether the measured direction
            is incoming (otherwise outgoing).
        @type is_direction_incoming: bool
        """
        self.__direction_incoming = is_direction_incoming
        self.__total_bytes = 0
        self.__ticks = deque()
        self.__ticks_lock = Lock()
        self.__start_time = None
        self.__timer_service = TimerService(
                                   TRAFFIC_NOTIFICATION_PERIOD.total_seconds(),
                                   self.__on_timer)
        self.__started = False


    @property
    def started(self):
        return self.__started


    def start(self):
        """
        Start monitoring and reporting.
        """
        with self.__ticks_lock:
            assert not self.__started
            self.__timer_service.startService()
            self.__started = True
            logger.debug('%s traffic meter started', self._name)


    @property
    def _name(self):
        return 'Inbound' if self.__direction_incoming else 'Outbound'


    def stop(self):
        """
        Stop monitoring and reporting.
        """
        with self.__ticks_lock:
            assert self.__started
            self.__started = False
            self.__timer_service.stopService()
            logger.debug('%s traffic meter stopped', self._name)


    @exceptions_logged(logger)
    def __on_timer(self):
        _now = datetime.utcnow()
        _oldest_time_to_consider = _now - THROUGHPUT_SMA_PERIOD

        # Even if the timer is started, we don't report throughput
        # until the first ticks are registered.
        if self.__ticks:
            with self.__ticks_lock:
                # Remove (leftmost) ticks which are too old to consider.
                while (self.__ticks and
                       self.__ticks[0].time < _oldest_time_to_consider):
                    self.__ticks.popleft()

                # All the remaining ticks now serve for the throughput
                # calculation, and comprise the current SMA window.
                sma_kilobytes = sum(i.bytes for i in self.__ticks) / 1000.0

                # (btw, done with the lock, remaining calculation
                # can be unlocked)

            # How much time passed since the traffic meter has been started?
            uptime_td = TimeDeltaEx.from_timedelta(_now - self.__start_time)

            # If the meter has just been started,
            # we calculate the throughput dividing to the actual uptime
            # rather than the period window.
            sma_td = min(uptime_td, THROUGHPUT_SMA_PERIOD)
            sma_seconds = sma_td.in_seconds()

            kBps = sma_kilobytes / sma_seconds

            # Measurement unit is "kilobytes per second"
            # (not "kibi", not "bits"!)
            logger_traffic_meter.info('%s: %.02f kBps',
                                      'In' if self.__direction_incoming
                                           else 'Out',
                                      kBps,
                                      extra={'is_incoming':
                                                 self.__direction_incoming,
                                             'kBps': kBps})


    @contract_epydoc
    def add_tick(self, bytes):
        """
        Call this function manually to register a new data tick
        passing C{bytes} bytes.

        @param bytes: how many bytes passed in the tick.
        @type bytes: numbers.Number
        """
        assert self.__started
        assert isinstance(bytes, numbers.Number), repr(bytes)

        _now = datetime.utcnow()
        # logger.debug("%s traffic %i bytes", self._name, bytes)
        with self.__ticks_lock:
            self.__ticks.append(TrafficTick(time=_now, bytes=bytes))
            self.__total_bytes += bytes
            if self.__start_time is None:
                self.__start_time = _now
Example #30
0
File: mocks.py Project: shvar/redfs
def _start_dummy_data_transfer_ventilator():
    #from random import choice

    def restore_started(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid, 'started',
                                   extra={'result': 'started',
                                          'ds_uuid': uuid,
                                          'tr_uuid': None,
                                          'target_dir': None})

    def restore_running(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid, 'running',
                                   extra={'result': 'running',
                                           'ds_uuid': uuid,
                                           'tr_uuid': None,
                                           'target_dir': None})

    def restore_finished(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid, 'finished',
                                   extra={'result': 'success',
                                          'ds_uuid': uuid,
                                          'tr_uuid': None,
                                          'target_dir': None})

    def restore_failed(uuid):
        logger_status_restore.info('dummy restore uuid: %s, status: %s',
                                   uuid, 'failed',
                                   extra={'result': 'fail',
                                          'ds_uuid': uuid,
                                          'tr_uuid': None,
                                          'target_dir': None})

    def restore_progress(uuid, transfered, total,
                         transfered_bytes, total_bytes):
        logger_status_restore_progress.info('dummy restore progresses with'
                                          + ' different transfered and total'
                                          + ' uuid: %s'
                                          + ' transfered: %s'
                                          + ' total %s',
                                            uuid, transfered, total,
                                            extra={'progresses': [
                                                {'uuid': uuid,
                                                'num': transfered,
                                                'of': total,
                                                'num_bytes': transfered_bytes,
                                                'of_bytes': total_bytes}
                                                ]})

    def backup_started(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid, 'started',
                                  extra={'status': 'started',
                                         'ds_uuid': uuid,
                                         'result_str': '',
                                         'relaunch_in_mins': None})

    def backup_running(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid, 'running',
                                  extra={'status': 'running',
                                         'ds_uuid': uuid,
                                         'result_str': '',
                                         'relaunch_in_mins': None})

    def backup_finished(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid, 'finished',
                                  extra={'status': 'ok',
                                         'ds_uuid': uuid,
                                         'result_str': '',
                                         'relaunch_in_mins': None})

    def backup_failed(uuid):
        logger_status_backup.info('dummy backup uuid: %s, status: %s',
                                  uuid, 'failed',
                                  extra={'status': 'fail',
                                        'ds_uuid': uuid,
                                        'result_str': '',
                                        'relaunch_in_mins': None})

    def backup_progress(uuid, transfered, total,
                        transfered_bytes, total_bytes):
        logger_status_backup_progress.info('dummy backup progress'
                                         + ' uuid: %s'
                                         + ' transfered: %s'
                                         + ' total %s',
                                           uuid, transfered, total,
                                           extra={'ds_uuid': uuid,
                                                  'num': transfered,
                                                  'of': total,
                                                  'num_bytes': transfered_bytes,
                                                  'of_bytes': total_bytes})

    def run_good_case():
        backup_uuid = uuid4()
        restore_uuid = uuid4()

        restore_total = 1000
        backup_total = 1000

        backup_started(backup_uuid)
        restore_started(restore_uuid)

        @exceptions_logged(logger)
        def _backup_progress(uuid, mult, total):
            backup_running(uuid)
            backup_progress(uuid, int(mult * total), total,
                            100000 * int(mult * total), 100000 * total)

        @exceptions_logged(logger)
        def _restore_progress(uuid, mult, total):
            restore_progress(uuid, int(mult * total), total,
                             100000 * int(mult * total), 100000 * total)
            restore_running(uuid)

        for i in xrange(1, 11):
            reactor.callLater(2 * i, _backup_progress,
                              backup_uuid, i * 10. / 100.0, backup_total)

            reactor.callLater(2 * i, _restore_progress,
                              restore_uuid, i * 10. / 100.0, restore_total)

        reactor.callLater(30, backup_finished, backup_uuid)
        reactor.callLater(30, restore_failed, restore_uuid)


    def run_bad_case():
        backup_uuid = uuid4()
        restore_uuid = uuid4()

        restore_total = 1000
        backup_total = 1000

        backup_started(backup_uuid)
        restore_started(restore_uuid)

        def _backup_progress(uuid, mult, total):
            backup_running(uuid)
            backup_progress(uuid, int(mult * total), total,
                            100000 * int(mult * total), 100000 * total)

        def _restore_progress(uuid, mult, total):
            restore_progress(uuid, int(mult * total), total,
                             100000 * int(mult * total), 100000 * total)
            restore_running(uuid)

        for i in xrange(1, 11):
            reactor.callLater(2 * i, _backup_progress, backup_uuid,
                              i * 10. / 100.0, backup_total)

            reactor.callLater(2 * i, _restore_progress, restore_uuid,
                              i * 9. / 100.0, restore_total)

        reactor.callLater(30, backup_finished, backup_uuid)
        reactor.callLater(30, restore_failed, restore_uuid)

    @exceptions_logged(logger)
    def run_case():
        if run_case.next == 'good':
            run_case.next = 'bad'
            run_good_case()
        else:
            run_case.next = 'good'
            run_bad_case()
    run_case.next = 'good'


    t = TimerService(50, run_case)
    t.startService()
Example #31
0
 def startService(self):
     log.msg("Starting up script runner service ...")
     return TimerService.startService(self)
Example #32
0
class BackupScheduler(object):
    """The container for the backup schedules.

    Internally, it keeps the list of schedules in always-sorted state;
    they are sorted by the expected time of fire (then by UUID;
    but two schedules with the same UUID always match).

    @todo: The scheduler must be dynamically updated:
           1. if some "backup scheduled" settings are changed;
           2. if some "timezone" setting is changed.

    @note: schedule support is disabled, so this class is not used anymore.
    """
    __slots__ = ('server_process', 'lock', '__schedules',
                 '__schedules_by_host_uuid', '__schedule_check_timer',
                 '__last_reread_from_db')

    @exceptions_logged(logger)
    def __init__(self, server_process):
        assert isinstance(server_process, ServerProcess), \
               repr(server_process)

        self.server_process = server_process
        self.lock = RLock()

        self.__schedule_check_timer = \
            TimerService(BACKUP_SCHEDULES_CHECK_PERIOD.total_seconds(),
                         self.__on_schedule_check_timer)

        self.reread_cache()

    def __repr__(self):
        return u'<BackupScheduler: {} schedule(s)>'\
                   .format(len(self.__schedules))

    @property
    def app(self):
        """
        @rtype: NodeApp
        """
        return self.server_process.app

    @exceptions_logged(logger)
    def __reset(self):
        self.__schedules = []
        self.__schedules_by_host_uuid = defaultdict(set)

    @exceptions_logged(logger)
    def start(self):
        self.__schedule_check_timer.startService()

    @exceptions_logged(logger)
    def stop(self):
        """
        @todo: It is nowhere stopped at the moment, should it be?
        """
        self.__schedule_check_timer.stopService()

    @contract_epydoc
    def add(self, schedule):
        """
        @type schedule: BackupSchedule
        """
        with self.lock:
            assert schedule not in self.__schedules, repr(schedule)
            bisect.insort(self.__schedules, schedule)
            self.__schedules_by_host_uuid[schedule.host_uuid].add(schedule)
            assert self.__schedules == sorted(self.__schedules), 'Not sorted!'

    @contract_epydoc
    def remove(self, schedule):
        """
        @type schedule: BackupSchedule
        """
        with self.lock:
            assert schedule in self.__schedules, repr(schedule)
            index = bisect.bisect(self.__schedules, schedule) - 1
            assert schedule == self.__schedules[index], \
                   (index, schedule, self.__schedules)
            del self.__schedules[index]
            self.__schedules_by_host_uuid[schedule.host_uuid].remove(schedule)
            assert self.__schedules == sorted(self.__schedules), 'Not sorted!'

    @contract_epydoc
    def get_schedules_by_host_uuid(self, host_uuid):
        """
        @type host_uuid: UUID
        @rtype: frozenset
        """
        return frozenset(self.__schedules_by_host_uuid[host_uuid])

    @contract_epydoc
    def get_schedules_older_than(self, dt):
        """
        @param dt: The time, the schedules older than, are returned.
        @type dt: datetime

        @precondition: not is_naive_dt(dt) # repr(dt)

        @returns: The schedules which are older than the "dt".
        @rtype: list
        @postcondition: consists_of(result, BackupSchedule)
        """
        with self.lock:
            i = bisect.bisect(self.__schedules,
                              BackupSchedule._dummy_schedule_for_time(dt))
            return self.__schedules[:i]

    @contract_epydoc
    def reread_cache_for_host(self, host_uuid):
        """
        Reread all the schedules for a single host from the DB.
        It is assumed they've just been updated, so they should not be
        sent back.

        @param host_uuid: UUID of the host which schedules need to be updated.
        @type host_uuid: UUID
        """
        logger.debug('Rereading the schedules on %r for host %r',
                     self.server_process.me, host_uuid)

        with self.lock:
            old_schedules = self.get_schedules_by_host_uuid(host_uuid)
            logger.debug('Removing schedules for host %s: %r', host_uuid,
                         old_schedules)
            for schedule in old_schedules:
                self.remove(schedule)

            with db.RDB() as rdbw:
                new_schedules = \
                    Queries.Settings.get_setting(
                        host_uuid=host_uuid,
                        setting_name=Queries.Settings.BACKUP_SCHEDULE,
                        rdbw=rdbw)

                tz_name = Queries.Settings.get_setting(
                    host_uuid=host_uuid,
                    setting_name=Queries.Settings.TIMEZONE,
                    rdbw=rdbw)

            if tz_name:
                try:
                    tz_info = pytz.timezone(tz_name)
                except:
                    logger.error(
                        'Cannot parse timezone %r for host %r, '
                        'fallback to UTC', tz_name, host_uuid)
                    tz_info = pytz.utc
            else:
                tz_info = pytz.utc

            logger.debug('Adding new schedules for host %s: %r', host_uuid,
                         new_schedules)
            for schedule in new_schedules:
                self.add(BackupSchedule.from_dict(host_uuid, tz_info,
                                                  schedule))

    def reread_cache(self):
        """
        Reread the cache/info from the database.
        """
        self.__last_reread_from_db = utcnow()
        logger.debug('Rereading the schedules on %r', self.server_process.me)

        with self.lock:
            self.__reset()
            for host_uuid, (schedules, tz_name) \
                    in TrustedQueries.TrustedSettings.get_all_schedules() \
                                                     .iteritems():
                #
                if tz_name:
                    try:
                        tz_info = pytz.timezone(tz_name)
                    except:
                        logger.error(
                            'Cannot parse timezone %r for host %r, '
                            'fallback to UTC', tz_name, host_uuid)
                        tz_info = pytz.utc
                else:
                    tz_info = pytz.utc

                for schedule in schedules:
                    self.add(
                        BackupSchedule.from_dict(host_uuid, tz_info, schedule))

        logger.debug('Reread %i schedules on %r', len(self.__schedules),
                     self.server_process.me)

    @exceptions_logged(logger)
    def __on_schedule_check_timer(self):
        """
        Called whenever the schedule check timer is fired.
        """
        pass

    @exceptions_logged(logger)
    def __check_schedules(self):

        _now = utcnow()

        with self.lock:
            #
            # First, do we need to reread the schedules from the DB.
            #
            assert isinstance(self.__last_reread_from_db, datetime), \
                   repr(self.__last_reread_from_db)
            maxdelta = timedelta(
                seconds=BACKUP_SCHEDULES_REREAD_PERIOD.total_seconds())
            if _now - self.__last_reread_from_db > maxdelta:
                self.reread_cache()

            #
            # Now, we can check the schedules.
            #
            if self.__schedules:
                logger.debug(
                    'Checking for suspect schedules '
                    'at %s among %i schedules...', _now, len(self.__schedules))
            suspect_schedules = self.get_schedules_older_than(_now)
            if suspect_schedules:
                logger.debug(
                    'On node %r, the following (%i) schedules '
                    'have passed their time:\n%s', self.server_process.me,
                    len(suspect_schedules),
                    '\n'.join(repr(s) for s in suspect_schedules))

                # But what hosts are actually alive at the moment?
                alive_host_uuids = \
                    [h.uuid
                         for h in self.app.known_hosts.alive_peers()]
                if alive_host_uuids:
                    logger.debug('Alive hosts at the moment are: %r',
                                 alive_host_uuids)
                # The schedule will fire a backup only if both its time is out,
                # and its host is alive.
                process_schedules = {
                    sch
                    for sch in suspect_schedules
                    if sch.host_uuid in alive_host_uuids
                }

                if process_schedules:
                    processed_host_uuids = set()
                    logger.debug(
                        'The following (%i) schedules will fire:\n%s',
                        len(suspect_schedules),
                        '\n'.join(repr(sch) for sch in process_schedules))

                    # Loop over schedules, and run the backup transactions.
                    for schedule in process_schedules:
                        logger.debug('Firing a backup for schedule %r',
                                     schedule)

                        # TODO: Add "if user is suspended" check when
                        # TODO: BackupScheduler will be uncommented.
                        raise NotImplementedError
                        self.start_scheduled_backup(schedule)

                        new_schedule = schedule.copy()
                        new_schedule.advance_by_period()
                        logger.debug('%r advanced to %r', schedule,
                                     new_schedule)

                        # Remove old schedule; add new if needed.
                        self.remove(schedule)
                        if new_schedule.next_backup_datetime is not None:
                            self.add(new_schedule)

                        processed_host_uuids.add(new_schedule.host_uuid)

                    # We've done with the backup transactions.
                    # Would be cool to update the settings.
                    for host_uuid in processed_host_uuids:
                        schedules = self.get_schedules_by_host_uuid(host_uuid)
                        logger.debug(
                            'Updating the schedules '
                            'for host %s:\n%r', host_uuid, schedules)
                        with db.RDB() as rdbw:
                            TrustedQueries.TrustedSettings.set_setting(
                                rdbw=rdbw,
                                host_uuid=host_uuid,
                                setting_name=Queries.Settings.BACKUP_SCHEDULE,
                                setting_value=[s.to_dict() for s in schedules],
                                setting_time=_now.replace(tzinfo=None))
                        self.send_updated_schedules_to_host(host_uuid)

    def start_scheduled_backup(self, schedule):
        """Given a schedule, start an appropriate backup transaction."""
        assert isinstance(schedule, BackupSchedule), repr(schedule)

        _manager = self.server_process.tr_manager

        b_tr = _manager.create_new_transaction(
            name='BACKUP',
            src=self.server_process.me,
            dst=self.app.known_hosts[schedule.host_uuid],
            parent=None,
            # BACKUP-specific
            schedule_uuid=schedule.uuid,
            schedule_name=schedule.name,
            schedule_paths=schedule.paths)

    @contract_epydoc
    def send_updated_schedules_to_host(self, host_uuid):
        """
        Given an UUID of the host,
        start an appropriate "UPDATE_CONFIGURATION" transaction.

        @type host_uuid: UUID
        """
        _setting_name = Queries.Settings.BACKUP_SCHEDULE

        with db.RDB() as rdbw:
            settings = {
                _setting_name:
                Queries.Settings.get_setting(host_uuid=host_uuid,
                                             setting_name=_setting_name,
                                             direct=True,
                                             with_time=True,
                                             rdbw=rdbw)
            }

        uc_tr = self.server_process.tr_manager.create_new_transaction(
            name='UPDATE_CONFIGURATION',
            src=self.server_process.me,
            dst=self.app.known_hosts[host_uuid],
            parent=None,
            # UPDATE_CONFIGURATION-specific
            settings=settings)
Example #33
0
class RestoreRequestProcessor(object):
    """
    The processor for the externally initiated restore requests.
    """
    __slots__ = ('__server_process', '__restore_timer')

    def __init__(self, server_process):
        assert isinstance(server_process, ServerProcess), \
               repr(server_process)
        self.__server_process = server_process

        self.__restore_timer = \
            TimerService(WEB_RESTORE_PERIOD.total_seconds(),
                         exceptions_logged(logger)(callInThread),
                         self.__poll_restore_requests_in_thread)

    def __poll_restore_requests_in_thread(self):
        """Perform another iteration of polling the restore requests."""
        assert not in_main_thread()

        poll_uuid = gen_uuid()
        logger.debug('Polling restore requests (%s)', poll_uuid)

        restore_request = True
        while restore_request is not None:
            with ds.FDB() as fdbw:
                restore_request = \
                    FDBQueries.RestoreRequests \
                              .atomic_start_oldest_restore_request(fdbw=fdbw)

            logger.debug('Poll (%s) returned %r', poll_uuid, restore_request)
            if restore_request is not None:
                # We've indeed have some restore request that needs processing.

                # Create new "virtual" dataset with all the data
                # to be restored.
                with db.RDB() as rdbw:
                    new_ds_uuid = \
                        Queries.Datasets.restore_files_to_dataset_clone(
                            restore_request.base_ds_uuid,
                            restore_request.paths,
                            restore_request.ts_start,
                            rdbw)

                # Now we know the new dataset to be restored.
                # Btw, write it into the docstore.
                # Doesn't need to be atomic, as only a single node
                # may be processing it at a time.
                with ds.FDB() as fdbw:
                    FDBQueries.RestoreRequests.set_ds_uuid(
                        _id=restore_request._id,
                        new_ds_uuid=new_ds_uuid,
                        fdbw=fdbw)

                # After creating the dataset, let's restore it to all host
                # which are alive.
                _syncer = self.__server_process.app.syncer
                _syncer.restore_dataset_to_lacking_hosts(
                    me=self.__server_process.me,
                    host=None,
                    ds_uuid=new_ds_uuid)

        logger.debug('Polling restore requests (%s) - done', poll_uuid)

    def start(self):
        """Start the processor."""
        assert in_main_thread()
        self.__restore_timer.startService()

    def stop(self):
        """Stop the processor."""
        assert in_main_thread()
        self.__restore_timer.stopService()