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()
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)
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)
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)
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)
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)))
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)
def startService(self): if self.log_file is not None: return self.log_file = open(self.log_path, 'r') TimerService.startService(self)
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()
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
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
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
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
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
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()
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)
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)
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)
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()
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()
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)
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()
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()
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)
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
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()
def startService(self): log.msg("Starting up script runner service ...") return TimerService.startService(self)
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)